Migrate from ImageSharp to OneImage
Port ImageSharp code to OneImage step by step: Mutate, ResizeOptions and ResizeMode, encoders, metadata, DrawImage, ProcessPixelRows, errors and ImageSharp.Web.
Moving from ImageSharp to OneImage is mostly mechanical. image.Mutate(x => x.Resize(...)) becomes a chain of calls on an immutable Image, encoders become SaveOptions, and Image<Rgba32> becomes plain Image. This page puts the common ImageSharp calls next to their OneImage equivalents and then lists the behavior changes to check after porting. The ImageSharp code follows Six Labors’ documentation and was compiled against ImageSharp 3.1.12 and 4.1.3. The OneImage code was run against OneImage 1.6.0 on .NET 10, on the same inputs.
Teams usually migrate because of the license. ImageSharp 3.x and later use the Six Labors Split License, and 3.2.0 and 4.x check for a license key when you build in Release. Our ImageSharp license guide goes through who pays. Security fixes are the other reason. ImageSharp 3.1.12, the last 3.x release that builds without a key, falls within the affected range (2.0.0 through 4.1.1) of GHSA-j3p4-wp97-rph4, rated high and published October 7, 2026. GitHub lists 4.1.2 as the first patched version.
Swap the package
dotnet remove package SixLabors.ImageSharp dotnet add package RecursiveAI.OneImage
OneImage targets .NET 10. Replace using SixLabors.ImageSharp; and its sub-namespaces (.Processing, .Formats.Jpeg, .PixelFormats and so on) with a single using OneImage;. It is licensed MIT OR Apache-2.0, with no key and no native binaries.
What changes in the programming model
| ImageSharp | OneImage |
|---|---|
image.Mutate(x => ...) changes the image in place | Each operation returns a new immutable Image; chain them |
image.Clone(x => ...) | Not needed: keep the original and derive new images from it |
Image<Rgba32>, Image.Load<Rgba32>(...) | Image and Image.Load(...); pixel types appear only when you read or write pixels |
using Image image = ... | Image has no Dispose; only the mutable Canvas does |
| Operations run when called | Operations are recorded and computed tile by tile at Save |
AutoOrient() when you call it | EXIF orientation applied at Load |
Load, resize and save
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using Image image = Image.Load("photo.jpg"); image.Mutate(x => x.Resize(800, 0)); // 0 keeps the aspect ratio image.Save("photo-800.jpg");
using OneImage;
Image.Load("photo.jpg").Resize(width: 800).Save("photo-800.jpg");Both wrote an 800×801 JPEG from our 3000×3002 photo. Leave out height: (rather than passing 0) to keep the aspect ratio. The output format follows the file extension in both libraries.
ResizeOptions and ResizeMode
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using Image image = Image.Load("photo.jpg"); image.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(800, 600), Mode = ResizeMode.Max })); image.Save("photo-max.jpg");
using OneImage; // ResizeMode.Max is Fit.Contain, the default. upscale: false stops at the original size. Image.Load("photo.jpg") .Resize(width: 800, height: 600, fit: Fit.Contain, upscale: false) .Save("photo-max.jpg");
Both wrote 600×600, fitted inside 800×600. ResizeOptions becomes named arguments on Resize: Mode is fit:, Position is anchor:, PadColor is background: and Sampler is filter:. The modes map like this, per OneImage’s migration reference:
ResizeMode | OneImage |
|---|---|
Max | Fit.Contain, the default (also spelled Fit.Max) |
Crop | Fit.Cover (also spelled Fit.Crop) |
Pad | Fit.Pad with background: |
BoxPad | Fit.BoxPad |
Min | Fit.Min |
Stretch | Fit.Stretch |
Manual with TargetRectangle | ResizeInto(canvasWidth, canvasHeight, rect) |
Two details catch people out:
- The default mode differs. ImageSharp’s resize documentation notes that
ResizeOptions.Modedefaults toCrop. OneImage’sResizedefaults toFit.Contain. If your code set aSizewithout aMode, passfit: Fit.Cover. - To prevent upscaling, pass
upscale: false. It stops any fit mode at the original size.Image.Thumbnailnever enlarges unless you passupscale: true.
Resampling filters keep their names: KnownResamplers.Lanczos3 becomes ResamplingFilter.Lanczos3, and the same goes for CatmullRom, Spline and the others. ImageSharp’s documented default is Bicubic. OneImage’s default, Auto, uses Lanczos3 when shrinking and Catmull-Rom when enlarging. More resize options are in our guide to resizing images in C#.
Crop, rotate, flip and AutoOrient
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using Image image = Image.Load("portrait.jpg"); image.Mutate(x => x .AutoOrient() .Crop(new Rectangle(100, 80, 600, 600)) .Rotate(90) .Flip(FlipMode.Horizontal)); image.Save("edited.png");
using OneImage; // No AutoOrient(): Load has already applied the EXIF orientation. Image.Load("portrait.jpg") .Crop(100, 80, 600, 600) .Rotate(90) .Flip(FlipAxis.Horizontal) .Save("edited.png");
Both produced a 600×600 crop of our phone-style test photo, matching each other to within one level per channel on average. Rectangle arguments become four integers (or an ImageRect), and FlipMode becomes FlipAxis. Delete AutoOrient() calls. If one stays in a pipeline that has already been oriented at load, it does nothing, but code that rotated by the EXIF tag by hand will turn the picture twice. See EXIF orientation in C# and cropping images in C#.
Encoders and quality
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Webp; using Image image = Image.Load("photo.jpg"); image.Save("photo-q85.jpg", new JpegEncoder { Quality = 85 }); using var webp = new MemoryStream(); image.Save(webp, new WebpEncoder { Quality = 80 }); Console.WriteLine($"{webp.Length} bytes of WebP");
using OneImage; Image image = Image.Load("photo.jpg"); image.Save("photo-q85.jpg", new SaveOptions { EncoderOptions = new JpegEncoderOptions { Quality = 85 } }); using var webp = new MemoryStream(); image.Save(webp, WebPFormat.Instance, new SaveOptions { EncoderOptions = new WebPEncoderOptions { Quality = 80 } }); Console.WriteLine($"{webp.Length} bytes of WebP");
Encoder objects become option objects inside SaveOptions: JpegEncoder becomes JpegEncoderOptions, WebpEncoder becomes WebPEncoderOptions (lossless is Lossless = true), and PngEncoder becomes PngEncoderOptions. When saving to a stream, name the format with JpegFormat.Instance, PngFormat.Instance, WebPFormat.Instance and so on. Quality scales are not interchangeable between encoders, so check output sizes and looks on your own images. Our WebP guide covers the WebP settings.
Identify
using SixLabors.ImageSharp;
ImageInfo info = Image.Identify("photo.jpg");
Console.WriteLine($"{info.Width}x{info.Height}");using OneImage;
ImageInfo info = Image.Identify("photo.jpg");
Console.WriteLine($"{info.Width}x{info.Height}");The same call with the same name, and both print 3000x3002. OneImage’s Identify reports the size as stored, before orientation, and its Frames list gives the frame count.
Strip metadata
using SixLabors.ImageSharp; using Image image = Image.Load("portrait.jpg"); image.Metadata.ExifProfile = null; image.Metadata.IptcProfile = null; image.Metadata.XmpProfile = null; image.Save("portrait-stripped.jpg");
using OneImage; // Removes EXIF, XMP, IPTC and the ICC profile; StripMetadata(keepIcc: true) keeps the profile. Image.Load("portrait.jpg").StripMetadata().Save("portrait-stripped.jpg");
Both outputs had no EXIF block. The OneImage file was 800×1200 and upright. The ImageSharp one was 1200×800, the stored layout, because this code never calls AutoOrient() before the tag is removed. To read EXIF values, OneImage has Metadata.ReadExif(), which returns typed orientation, camera, capture time and GPS fields.
DrawImage becomes Composite
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using Image image = Image.Load("photo.jpg"); using Image logo = Image.Load("logo.png"); image.Mutate(x => x.DrawImage(logo, new Point(20, 20), 0.8f)); image.Save("watermarked.jpg");
using OneImage;
Image.Load("photo.jpg")
.Composite(Image.Load("logo.png"), x: 20, y: 20, opacity: 0.8f)
.Save("watermarked.jpg");DrawImage(foreground, point, opacity) maps to Composite(overlay, x:, y:, opacity:). ImageSharp’s PixelColorBlendingMode and PixelAlphaCompositionMode map to the blend: and alpha: parameters, with the same member names.
ProcessPixelRows becomes Canvas.Lock
using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Image<Rgba32> image = Image.Load<Rgba32>("logo.png"); image.ProcessPixelRows(accessor => { for (int y = 0; y < accessor.Height; y++) { Span<Rgba32> row = accessor.GetRowSpan(y); for (int x = 0; x < row.Length; x++) { ref Rgba32 pixel = ref row[x]; if (pixel.A == 0) { pixel = new Rgba32(0, 0, 0, 0); } } } }); image.Save("logo-clean.png");
using OneImage; using Canvas canvas = Image.Load("logo.png").ToCanvas(); // T must match the canvas format: this PNG decodes to Rgba8, so T is Rgba32. using (PixelLock<Rgba32> pixels = canvas.Lock<Rgba32>(new ImageRect(0, 0, canvas.Width, canvas.Height), LockAccess.ReadWrite)) { for (int y = 0; y < pixels.Height; y++) { Span<Rgba32> row = pixels.GetRow(y); for (int x = 0; x < row.Length; x++) { if (row[x].A == 0) { row[x] = new Rgba32(0, 0, 0, 0); } } } } canvas.Snapshot().Save("logo-clean.png");
The two outputs were pixel-for-pixel identical. ProcessPixelRows and GetRowSpan become a Canvas, OneImage’s mutable, tiled image, with a PixelLock whose GetRow returns a Span. The pixel type must match the canvas format: JPEGs decode to Rgb24, and PNGs with alpha to Rgba32. For read-only work, image.ToArray<Rgba32>() or CopyPixelsTo converts any format.
Exceptions
using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; try { using Image image = Image.Load("upload.bin"); image.Mutate(x => x.Resize(800, 0)); image.Save("upload.jpg"); } catch (UnknownImageFormatException) { Console.WriteLine("Not an image."); } catch (InvalidImageContentException) { Console.WriteLine("Damaged image."); }
using OneImage; try { // Load reads the headers; damaged pixel data can surface at Save, so keep both inside the try. Image.Load("upload.bin").Resize(width: 800).Save("upload.jpg"); } catch (ImageFormatException) { Console.WriteLine("Not an image, or a damaged one."); } catch (ImageLimitException) { Console.WriteLine("Over the size limits."); }
Given a text file, the first printed “Not an image.” and the second “Not an image, or a damaged one.” ImageSharp’s troubleshooting page describes UnknownImageFormatException (no registered format matched) and InvalidImageContentException (the format was recognized but the data is invalid). OneImage reports both as ImageFormatException. Its size limits are on by default and checked from the headers, and going over one throws ImageLimitException. All of them derive from ImageException. Because pixels are decoded lazily, damaged pixel data can surface at Save, so keep the save inside the try, or load with new LoadOptions { Validate = true } to fail at Load.
ImageSharp.Web becomes OneImage.Web
For URL-driven resizing in ASP.NET Core, replace SixLabors.ImageSharp.Web with RecursiveAI.OneImage.Web. Its compatibility notes say ImageSharp.Web 3.2.0’s query spellings (rmode, ranchor, rsampler, bgcolor, quality and others) are accepted, so existing URLs keep working:
using OneImage.Web; var builder = WebApplication.CreateBuilder(args); // Was: builder.Services.AddImageSharp(); builder.Services.AddOneImage(options => options.UseImageSharpDefaults()); var app = builder.Build(); // Was: app.UseImageSharp(); app.UseOneImage(); // before UseStaticFiles, as with ImageSharp.Web app.UseStaticFiles(); app.Run();
The AddImageSharp() and UseImageSharp() lines are from ImageSharp.Web’s getting-started page. UseImageSharpDefaults() keeps ImageSharp.Web’s behavior of cropping and enlarging when a width and height come without a mode. With it, ?width=400&height=300 returned 400×300 from our square photo, ?width=400&height=300&rmode=max returned 300×300, and ?width=300&format=webp&quality=80 returned a WebP. Signed URLs need new tokens, because HMAC tokens aren’t interchangeable. Custom IImageWebProcessor classes move into the OnBeforeSaveAsync hook.
Behavior to check after porting
- Orientation. Photos come out upright without
AutoOrient(). Remove hand-written EXIF rotation. - Resize output. Resampling runs in linear light on premultiplied alpha, so gradients and edges can differ slightly from ImageSharp’s. Compare a few outputs, and pass
linear: falseto filter the stored values instead. - Error timing. Wrap
Saveas well asLoad, or load withValidate = true. - Limits. Very large or suspicious files throw
ImageLimitException. Raise the limits for trusted data that needs it. - File lifetime.
Image.Load(path)rereads the file when pixels are needed, so keep it unchanged until you have saved. Load from aStreamorbyte[]to detach from the file. - Formats. OneImage reads and writes PNG, JPEG, WebP, GIF, TIFF, BMP and ICO, with add-on packages for SVG, PDF and Photoshop files. Check your inputs against that list.
The project’s full migration reference covers more of the API side by side, including filters, color adjustments, histograms and perspective transforms. To compare libraries before you decide, see ImageSharp alternatives.