If you are coming from System.Drawing, the biggest adjustment is not learning a brand-new set of image concepts. It is mostly learning that ImageSharp makes a few things explicit that GDI+ used to hide: pixel type, processing pipelines, and encoder choices.
Once that shift lands, most everyday workflows map over cleanly.
System.Drawing concept |
ImageSharp equivalent |
|---|---|
Image / Bitmap |
Image or Image<TPixel> |
Color |
Color or a specific pixel type such as Rgba32 |
PixelFormat |
generic TPixel plus PixelTypeInfo |
GetPixel / SetPixel |
indexers or ProcessPixelRows(...) |
LockBits / UnlockBits |
ProcessPixelRows(...), CopyPixelDataTo(...), LoadPixelData(...), WrapMemory(...), DangerousTryGetSinglePixelMemory(...) |
Image.Save(...) with codec choices |
Save(...), SaveAsJpeg(...), SaveAsPng(...), or explicit encoder types |
Graphics.DrawImage(...) |
Mutate(...) with DrawImage(...) |
A typical System.Drawing workflow translates to:
System.Drawing:
using System.Drawing;
using System.Drawing.Imaging;
using Bitmap source = new("input.jpg");
using Bitmap output = new(400, 300);
using Graphics graphics = Graphics.FromImage(output);
graphics.DrawImage(source, 0, 0, 400, 300);
output.Save("output.png", ImageFormat.Png);ImageSharp:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using Image image = Image.Load("input.jpg");
image.Mutate(context => context.Resize(400, 300));
image.SaveAsPng("output.png");Instead of mutating through a separate Graphics object, ImageSharp uses processing pipelines built with Mutate(...) or Clone(...).
If you used Bitmap.GetPixel() or Bitmap.SetPixel() heavily, the closest ImageSharp equivalent is the indexer:
System.Drawing:
using System.Drawing;
Color pixel = bitmap.GetPixel(10, 20);
bitmap.SetPixel(10, 20, Color.White);ImageSharp:
using SixLabors.ImageSharp.PixelFormats;
Rgba32 pixel = image[10, 20];
image[10, 20] = Rgba32.White;For real throughput, move to ProcessPixelRows(...) instead. That is the ImageSharp replacement for most LockBits-driven loops:
System.Drawing:
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
BitmapData data = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
try
{
int stride = Math.Abs(data.Stride);
int byteCount = stride * data.Height;
byte[] pixels = new byte[byteCount];
Marshal.Copy(data.Scan0, pixels, 0, pixels.Length);
for (int y = 0; y < data.Height; y++)
{
int rowStart = y * stride;
// Format32bppArgb stores one pixel in four bytes, so reverse pixels rather than individual bytes.
for (int x = 0; x < data.Width / 2; x++)
{
int left = rowStart + (x * 4);
int right = rowStart + ((data.Width - x - 1) * 4);
for (int b = 0; b < 4; b++)
{
(pixels[left + b], pixels[right + b]) = (pixels[right + b], pixels[left + b]);
}
}
}
Marshal.Copy(pixels, 0, data.Scan0, pixels.Length);
}
finally
{
bitmap.UnlockBits(data);
}ImageSharp:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Image<Rgba32> image = Image.Load<Rgba32>("input.png");
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> row = accessor.GetRowSpan(y);
row.Reverse();
}
});This is one of the biggest mental shifts.
Color in ImageSharp is a general color value that can convert to any IPixel<TSelf> type. It is not the same thing as the TPixel storage type used by Image<TPixel>.
That means:
- use
Colorwhen you want a pixel-agnostic color value; - use
Rgba32,Bgra32,L8, and similar types when you care about actual in-memory layout.
Instead of storing a runtime PixelFormat enum and branching on it later, ImageSharp encourages you to choose a generic working type:
System.Drawing:
using System.Drawing;
using System.Drawing.Imaging;
using Bitmap bitmap = new("input.tiff");
bool isArgb = bitmap.PixelFormat == PixelFormat.Format32bppArgb;ImageSharp:
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Image<Rgba32> rgba = Image.Load<Rgba32>("input.tiff");
using Image<Bgra32> bgra = rgba.CloneAs<Bgra32>();If you only need metadata about the decoded source layout, ImageInfo.PixelType exposes PixelTypeInfo.
If your old code used LockBits, the best ImageSharp replacement depends on what the code was really trying to do:
- use
ProcessPixelRows(...)for most in-place managed algorithms; - use
CopyPixelDataTo(...)when you need a copied export buffer; - use
LoadPixelData(...)when you want to import raw bytes or pixels into a normal owned image; - use
WrapMemory(...)when you need a zero-copy bridge to existing memory; - use
DangerousTryGetSinglePixelMemory(...)only when you truly need one contiguous ImageSharp-owned buffer.
If your System.Drawing code mainly used Graphics.DrawImage(...), the closest ImageSharp equivalent is DrawImage(...) inside a processing pipeline.
If the old code also draws shapes, paths, or text, you will usually want the companion packages documented elsewhere in this repo:
SixLabors.ImageSharp.DrawingSixLabors.Fonts
For most migrations, the least painful path is:
- Keep the old high-level workflow the same.
- Replace
BitmapwithImage<TPixel>. - Replace
Graphicsoperations withMutate(...)orClone(...). - Replace
LockBitsloops withProcessPixelRows(...). - Standardize on a working pixel format such as
Rgba32unless you have a reason not to.
- Replace
Bitmapwith theImage<TPixel>type that matches your working pixel model. - Replace
LockBitsloops with row-based processing. - Keep rendering concerns in ImageSharp.Drawing when the old code used
Graphics. - Validate behavior on non-Windows systems when the migration goal is cross-platform support.