oneimage

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

TerminalNuGet
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

ImageSharpOneImage
image.Mutate(x => ...) changes the image in placeEach 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 calledOperations are recorded and computed tile by tile at Save
AutoOrient() when you call itEXIF orientation applied at Load

Load, resize and save

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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:

ResizeModeOneImage
MaxFit.Contain, the default (also spelled Fit.Max)
CropFit.Cover (also spelled Fit.Crop)
PadFit.Pad with background:
BoxPadFit.BoxPad
MinFit.Min
StretchFit.Stretch
Manual with TargetRectangleResizeInto(canvasWidth, canvasHeight, rect)

Two details catch people out:

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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
using SixLabors.ImageSharp;

ImageInfo info = Image.Identify("photo.jpg");
Console.WriteLine($"{info.Width}x{info.Height}");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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");
OneImage1.6.0 · .NET 10
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

ImageSharp3.1 / 4.x
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.");
}
OneImage1.6.0 · .NET 10
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:

Program.csOneImage.Web 1.6.0 · ASP.NET Core
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

  1. Orientation. Photos come out upright without AutoOrient(). Remove hand-written EXIF rotation.
  2. 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: false to filter the stored values instead.
  3. Error timing. Wrap Save as well as Load, or load with Validate = true.
  4. Limits. Very large or suspicious files throw ImageLimitException. Raise the limits for trusted data that needs it.
  5. File lifetime. Image.Load(path) rereads the file when pixels are needed, so keep it unchanged until you have saved. Load from a Stream or byte[] to detach from the file.
  6. 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.