Call Image.Thumbnail with the file and the largest width you want, then save. It is a resize with the defaults a published copy needs: the photo is turned upright, never enlarged, converted to sRGB and stripped of EXIF, so no GPS position leaks. The program below uses a public test photo that is stored sideways, the way phones store many portraits.
dotnet new console -o Thumbnail && cd Thumbnail dotnet add package RecursiveAI.OneImage --version 1.10.4 repo=https://raw.githubusercontent.com/recurser/exif-orientation-examples curl -L -o phone.jpg $repo/master/Portrait_6.jpg
OneImage needs the .NET 10 SDK. Put this in Program.cs and run dotnet run:
using OneImage; ImageInfo stored = Image.Identify("phone.jpg"); Console.WriteLine($"phone.jpg: stored {stored.Width}x{stored.Height}, " + $"orientation {stored.Metadata.Orientation}"); // At most 400 px wide, upright, sRGB, EXIF/XMP/IPTC removed. Image.Thumbnail("phone.jpg", 400).Save("thumb.jpg"); // A 200x200 square that fills the box, for avatars and grids, as WebP. Image.Thumbnail("phone.jpg", 200, 200, fit: Fit.Cover).Save("square.webp"); foreach (string file in new[] { "thumb.jpg", "square.webp" }) { ImageInfo info = Image.Identify(file); string exif = info.Metadata.ReadExif() is null ? "no EXIF" : "EXIF kept"; long kb = new FileInfo(file).Length / 1024; Console.WriteLine($"{file}: {info.Width}x{info.Height}, {exif}, {kb} KB"); }
phone.jpg: stored 1800x1200, orientation 6 thumb.jpg: 400x600, no EXIF, 33 KB square.webp: 200x200, no EXIF, 3 KB
The photo is stored as 1800×1200 with an EXIF orientation of 6 (“rotate 90° to show”), and both thumbnails come out upright. In order, Thumbnail applies the orientation, resizes (decoding a JPEG at 1/2, 1/4 or 1/8 scale when that is enough), converts to sRGB with a compact embedded profile, and removes EXIF, XMP and IPTC. The output format follows the extension.
Thumbnails for a folder
Image.TryLoad returns false for files that aren’t images or are damaged, so one bad file doesn’t stop the batch:
using OneImage; Directory.CreateDirectory("photos"); File.Copy("phone.jpg", "photos/phone.jpg", overwrite: true); File.WriteAllText("photos/notes.txt", "not an image"); Directory.CreateDirectory("thumbs"); foreach (string path in Directory.EnumerateFiles("photos")) { if (!Image.TryLoad(path, out Image? image)) { Console.WriteLine($"skipped {Path.GetFileName(path)}"); continue; } string name = Path.GetFileNameWithoutExtension(path) + ".webp"; string target = Path.Combine("thumbs", name); Image.Thumbnail(image, 320).Save(target); Console.WriteLine($"{target}: {Image.Identify(target).Width} px wide"); }
skipped notes.txt thumbs/phone.webp: 320 px wide
Thumbnail also takes a Stream or a byte[], for uploads and blob storage.
Options
height:andfit:set a box:Fit.Coverfills it and crops, withanchor: Anchor.Smartto keep the busiest part;Fit.Padfits and pads.upscale: trueallows enlarging a source smaller than the box.keepMetadata: truekeeps EXIF, XMP and IPTC.
Limits
- It never enlarges by default. A 300 px source asked for at 400 stays 300 px wide. For an exact size, use
Resize. - It strips metadata. Copyright and caption fields in EXIF or IPTC go too, unless you pass
keepMetadata: true. - SVG, PDF and PSD need their add-on package registered first; see SVG to PNG and PDF to image.
To serve thumbnails from URLs like photo.jpg?width=400, see Resize images on the fly in ASP.NET Core. Every parameter is in the guide.