oneimage

Fix the System.Drawing “not supported on this platform” error

Why System.Drawing.Common throws on Linux, macOS and Docker since .NET 6, why libgdiplus no longer helps, and how to port Bitmap image code to cross-platform .NET.

“System.Drawing.Common is not supported on this platform” means your code, or a package it uses, called System.Drawing (Bitmap, Graphics, Image) on Linux, macOS or in a Linux container. Since .NET 6, Microsoft supports System.Drawing.Common only on Windows. The .NET 6 opt-out switch was removed in .NET 7, so installing libgdiplus or changing a setting won’t bring it back. The fix is to keep that code on Windows or replace it with a cross-platform library. For image work (loading, resizing, cropping, converting, watermarking), that is a small port, shown below with OneImage. Every OneImage sample was run against version 1.6.0 on .NET 10.

The errors you may see

The message depends on the System.Drawing.Common version your app ends up with. We ran new Bitmap("photo.jpg") on Ubuntu 26.04 x64 with the .NET 10.0.11 runtime and no libgdiplus installed, against five versions of the package:

System.Drawing.CommonWhat is thrown
6.0.0, 8.0.22TypeInitializationException: “The type initializer for 'Gdip' threw an exception”, with an inner PlatformNotSupportedException: “System.Drawing.Common is not supported on non-Windows platforms. See https://aka.ms/systemdrawingnonwindows for more information.”
7.0.0PlatformNotSupportedException: “System.Drawing.Common is not supported on this platform.”
9.0.11A chain of TypeInitializationExceptions (“The type initializer for 'Windows.Win32.PInvoke' threw an exception”) ending in DllNotFoundException: “Unable to load shared library 'gdiplus.dll' or one of its dependencies.”
10.0.13TypeInitializationException: “The type initializer for 'Windows.Win32.PInvokeGdiPlus' threw an exception”, with an inner DllNotFoundException for gdiplus.dll.

On 9.x and 10.x, the message no longer mentions the platform at all, which is why “Unable to load shared library 'gdiplus.dll'” also leads here. The build warns first, though: every System.Drawing call compiled for all platforms raises CA1416, “This call site is reachable on all platforms. 'Bitmap' is only supported on: 'windows' 6.1 and later.” If you see CA1416 warnings in a project that runs on Linux, this exception is coming.

Why System.Drawing.Common is Windows-only

System.Drawing was designed as a thin wrapper over Windows technologies (GDI+). Off Windows it relied on libgdiplus, a reimplementation of those parts of Windows that came from the Mono project. Microsoft’s .NET 6 breaking-change notice (last updated July 21, 2023) gives the reason. It describes libgdiplus as “around 30,000 lines of C code that’s largely untested” that “lacks a lot of functionality” and depends on native libraries such as cairo and pango. It concludes: “It’s not viable to get libgdiplus to the point where its feature set and quality is on par with the rest of the .NET stack.”

The .NET 6 notice adds that System.Drawing.Common “will continue to evolve only in the context of Windows Forms and GDI+.” It still works on Windows.

Why installing libgdiplus doesn’t fix it

Older answers say to run apt-get install libgdiplus or brew install mono-libgdiplus. That worked before .NET 6, and on .NET 6 together with the switch above. With the switch gone since .NET 7, Microsoft says the package can no longer be used on non-Windows systems, so the install changes nothing. Look for the code that uses System.Drawing instead.

Find what uses System.Drawing

If your own code doesn’t reference System.Drawing, a dependency does. The .NET SDK shows the path to it:

Terminal.NET SDK
dotnet nuget why MyApp.csproj System.Drawing.Common

It prints the dependency graph down to System.Drawing.Common, for each target framework (docs). If a library pulls it in, check whether a newer version of that library has dropped it or offers a cross-platform build. If it’s your code, read on.

Your options

Most System.Drawing code on servers is image processing. Microsoft’s .NET 6 notice says its analysis of NuGet packages found cross-platform use was “mostly for image manipulation”. For that, OneImage is a direct replacement. It is pure C# with no native binaries, licensed MIT OR Apache-2.0, and needs .NET 10.

TerminalNuGet
dotnet add package RecursiveAI.OneImage

Port Bitmap and Graphics code

A typical System.Drawing resize-and-save, here making a 300 px wide JPEG at quality 85:

Before: System.DrawingWindows only
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

using var src = new Bitmap("photo.jpg");
int height = src.Height * 300 / src.Width;

using var dst = new Bitmap(300, height);
using (var g = Graphics.FromImage(dst))
{
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(src, 0, 0, 300, height);
}

ImageCodecInfo jpeg = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
using var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Quality, 85L);
dst.Save("photo-300.jpg", jpeg, parameters);

The same with OneImage:

After: OneImage.NET 10 · any OS
using OneImage;

Image.Load("photo.jpg")
     .Resize(width: 300)
     .Save("photo-300.jpg", new SaveOptions
     {
         EncoderOptions = new JpegEncoderOptions { Quality = 85 }
     });

We compiled the “before” code against System.Drawing.Common 10.0.13. It builds with CA1416 warnings and throws the 10.0.13 error above on Linux. The “after” code ran on the same machine and wrote a 300×300 JPEG from our 3000×3002 test photo.

A few differences to know when porting:

System.Drawing to OneImage cheat sheet

Program.cs.NET 10
using OneImage;

// new Bitmap(path).Width and .Height, without decoding the pixels
ImageInfo info = Image.Identify("photo.jpg");
Console.WriteLine($"{info.Width}x{info.Height}");

Image photo = Image.Load("photo.jpg");

// Bitmap.Clone(rectangle, pixelFormat)
photo.Crop(10, 10, 400, 300).Save("crop.png");

// Bitmap.RotateFlip(RotateFlipType.Rotate90FlipX)
photo.Rotate(90).Flip(FlipAxis.Horizontal).Save("rotated.png");

// Graphics.DrawImage with a ColorMatrix for 80% opacity
photo.Composite(Image.Load("logo.png"), x: 20, y: 20, opacity: 0.8f).Save("watermarked.jpg");

// Bitmap.PropertyItems, typed
string? camera = Image.Load("portrait.jpg").Metadata.ReadExif()?.Make;
Console.WriteLine(camera);

// new Bitmap(new MemoryStream(bytes)) and bitmap.Save(stream, ImageFormat.Png)
byte[] bytes = File.ReadAllBytes("photo.jpg");
var png = new MemoryStream();
Image.Load(bytes).Resize(width: 300).Save(png, PngFormat.Instance);
Console.WriteLine($"{png.Length} bytes of PNG");
System.DrawingOneImage
new Bitmap(path), Image.FromStream(stream)Image.Load(path), Image.Load(stream), Image.Load(bytes)
bitmap.Width, bitmap.Heightimage.Width, image.Height; Image.Identify reads only the headers
Graphics.DrawImage to a smaller BitmapResize(width:, height:, fit:) or Image.Thumbnail
bitmap.Clone(rect, format)Crop(x, y, width, height)
RotateFlipRotate(degrees), Flip(FlipAxis)
Graphics.DrawImage of an overlay with ImageAttributesComposite(overlay, x:, y:, opacity:)
Save(path, ImageFormat.Png)Save("out.png"): the format follows the extension
EncoderParameters with Encoder.QualitySaveOptions { EncoderOptions = new JpegEncoderOptions { Quality = 85 } }
PropertyItemsMetadata.ReadExif() (orientation, make, model, capture time, GPS); StripMetadata()

The cheat sheet printed 3000x3002, the camera make of our test photo and the size of the PNG. It wrote a 400×300 crop, a rotated 3002×3000 PNG and the watermarked JPEG. The project’s migration guide covers more side by side, including pixel access, filters and color adjustments. For resizing in depth, see our guide to resizing images in C#, and for crops, cropping images in C#.

Code that draws shapes and text with Graphics needs more than a rename. OneImage has vector drawing in the core package, and text in the separate OneImage.Text package. Check their docs against what your code draws. Printing (System.Drawing.Printing) and Windows Forms UI belong on Windows.

Run it in Docker without extra packages

The usual Dockerfile fix for System.Drawing, installing libgdiplus and its font dependencies, isn’t needed with OneImage. We published the cheat-sheet program and ran it in the stock mcr.microsoft.com/dotnet/runtime:10.0 image (Ubuntu 24.04), which has no libgdiplus, Skia or fontconfig libraries. It produced the same output as on the host. PortDemo.dll is our test program; use your app’s name:

Dockerfile.NET 10
FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY publish/ .
ENTRYPOINT ["dotnet", "PortDemo.dll"]

For what else OneImage reads and writes, see the formats on the overview page.