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.Common | What is thrown |
|---|---|
| 6.0.0, 8.0.22 | TypeInitializationException: “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.0 | PlatformNotSupportedException: “System.Drawing.Common is not supported on this platform.” |
| 9.0.11 | A 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.13 | TypeInitializationException: “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.”
- .NET 6 made the package Windows-only. The
System.Drawing.EnableUnixSupportruntime switch let apps turn non-Windows support back on temporarily, with the note that “non-Windows bugs will not be fixed.” - .NET 7 removed that switch. Per Microsoft’s .NET 7 notice, “you can no longer use the System.Drawing.Common package on non-Windows operating systems.”
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:
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
- Keep it on Windows. If the code only ever runs on Windows (a Windows Forms app, a Windows service), System.Drawing.Common is still supported there. Deploying to Windows containers or hosts is a valid fix.
- Move to a cross-platform library. Microsoft’s notices list SkiaSharp, ImageSharp (“tiered license”), Aspose.Drawing (“commercial license”) and Microsoft.Maui.Graphics. Our comparison of .NET image libraries covers SkiaSharp, ImageSharp, Magick.NET, NetVips and OneImage, with licenses and native dependencies from each project’s own pages. If you choose SkiaSharp, see SkiaSharp on Linux and Docker for the native package it needs there.
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.
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:
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:
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:
- No
Dispose. A OneImageImageisn’t disposable, so theusingstatements go away. Operations return new immutable images, so you chain them instead of drawing into aGraphics. - Orientation is applied at load. If your old code rotated photos by the EXIF orientation tag, remove that, or they will be turned twice.
- Errors can surface at
Save. Loading is lazy, so wrap the save in yourtryas well, or load withValidate = true.
System.Drawing to OneImage cheat sheet
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.Drawing | OneImage |
|---|---|
new Bitmap(path), Image.FromStream(stream) | Image.Load(path), Image.Load(stream), Image.Load(bytes) |
bitmap.Width, bitmap.Height | image.Width, image.Height; Image.Identify reads only the headers |
Graphics.DrawImage to a smaller Bitmap | Resize(width:, height:, fit:) or Image.Thumbnail |
bitmap.Clone(rect, format) | Crop(x, y, width, height) |
RotateFlip | Rotate(degrees), Flip(FlipAxis) |
Graphics.DrawImage of an overlay with ImageAttributes | Composite(overlay, x:, y:, opacity:) |
Save(path, ImageFormat.Png) | Save("out.png"): the format follows the extension |
EncoderParameters with Encoder.Quality | SaveOptions { EncoderOptions = new JpegEncoderOptions { Quality = 85 } } |
PropertyItems | Metadata.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:
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.