Scott Hanselman

How do you use System.Drawing in .NET Core?

September 12, 2018 Comment on this post [9] Posted in DotNetCore | Open Source
Sponsored By

I've been doing .NET image processing since the beginning. In fact I wrote about it over 13 years ago on this blog when I talked about Compositing two images into one from the ASP.NET Server Side and in it I used System.Drawing to do the work. For over a decade folks using System.Drawing were just using it as a thin wrapper over GDI (Graphics Device Interface) which were very old Win32 (Windows) unmanaged drawing APIs. We use them because they work fine.

.NET Conf: Join us this week! September 12-14, 2018 for .NET Conf! It's a FREE, 3 day virtual developer event co-organized by the .NET Community and Microsoft. Watch all the sessions here. Join a virtual attendee party after the last session ends on Day 1 where you can win prizes! Check out the schedule here and attend a local event in your area organized by .NET community influencers all over the world.

DotNetBotFor a while there was a package called CoreCompat.System.Drawing that was a .NET Core port of a Mono version of System.Drawing.

However, since then Microsoft has released System.Drawing.Common to provide access to GDI+ graphics functionality cross-platform.

There is a lot of existing code - mine included - that makes assumptions that .NET would only ever run on Windows. Using System.Drawing was one of those things. The "Windows Compatibility Pack" is a package meant for developers that need to port existing .NET Framework code to .NET Core. Some of the APIs remain Windows only but others will allow you to take existing code and make it cross-platform with a minimum of trouble.

Here's a super simple app that resizes a PNG to 128x128. However, it's a .NET Core app and it runs in both Windows and Linux (Ubuntu!)

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;

namespace imageresize
{
class Program
{
static void Main(string[] args)
{
int width = 128;
int height = 128;
var file = args[0];
Console.WriteLine($"Loading {file}");
using(FileStream pngStream = new FileStream(args[0],FileMode.Open, FileAccess.Read))
using(var image = new Bitmap(pngStream))
{
var resized = new Bitmap(width, height);
using (var graphics = Graphics.FromImage(resized))
{
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.DrawImage(image, 0, 0, width, height);
resized.Save($"resized-{file}", ImageFormat.Png);
Console.WriteLine($"Saving resized-{file} thumbnail");
}
}
}
}
}

Here it is running on Ubuntu:

Resizing Images on Ubuntu

NOTE that on Ubuntu (and other Linuxes) you may need to install some native dependencies as System.Drawing sits on top of native libraries

sudo apt install libc6-dev 
sudo apt install libgdiplus

There's lots of great options for image processing on .NET Core now! It's important to understand that this System.Drawing layer is great for existing System.Drawing code, but you probably shouldn't write NEW image management code with it. Instead, consider one of the great other open source options.

  • ImageSharp - A cross-platform library for the processing of image files; written in C#
    • Compared to System.Drawing ImageSharp has been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments.

Here's how you'd resize something with ImageSharp:

using (Image<Rgba32> image = Image.Load("foo.jpg"))
{
image.Mutate(x => x
.Resize(image.Width / 2, image.Height / 2)
.Grayscale());
image.Save("bar.jpg"); // Automatic encoder selected based on extension.
}
  • Magick.NET -A .NET library on top of ImageMagick
  • SkiaSharp - A .NET wrapper on top of Google's cross-platform Skia library

It's awesome that there are so many choices with .NET Core now!


Sponsor: Rider 2018.2 is here! Publishing to IIS, Docker support in the debugger, built-in spell checking, MacBook Touch Bar support, full C# 7.3 support, advanced Unity support, and more.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
September 12, 2018 9:53
You know what new programmers always ask me? They ask "is there a guide that tells us how to create a simple GUI 'Hello World!' app in .NET Core, from the start to the creation of gold master disk?"

I myself never read such a guide. I have experiences developing in C# with .NET Framework and, before that, developing in Delphi, so I can put 2 and 2 together to get 4. But not them.
September 12, 2018 11:45
Brilliant - I guess this is for .NET Core 3 path for including UI bits but lower level stuff is available for current releases.
September 12, 2018 17:46
We used ImageSharp in our project, even when its beta now. It seems to be promising.
September 12, 2018 21:33
Been following your posts about .Net Core. Good stuff.

I've always wondered about the warning that warning that classes in System.Drawing shouldn't be used within services, included ASP.NET ones. Do developers just do it anyway?

Caution:
Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components.


September 13, 2018 6:00
I'm definitely more a fan of the ImageSharp approach, keeping everything in managed code. No need for system library dependencies! I wouldn't want the hassle of having to maintain my own Docker image with the system libraries installed. I'd rather just base my own services' images off something official like "dotnet:2.2-aspnetcore-runtime".
September 14, 2018 9:18
Scott,

Having image processing background on Unix, Linux, Windows, .net, Win32 and Java; the simplest in the long run is to run a command line image processing tool like ImagaMagick instead of a library in the same process as your c# application (due to memory corruption, memory leaks, handle leaks, hung processes, etc.)

Image files, and by the way video and audio media files, have been largely ignored by .net with only minimal support for identifying the file, its codec parameters, its size, duration, bit rate, bits per pixel, chroma format, EXIF information, frames per second, etc.

.NET needs fully managed image file handling and codecs at some point now or in the future to right the mess of wrapped 20 year old win32 codecs.

Test cases which should be trivial in .net:
- Open PNG file, print out to the console the codec information, image dimensions, bits per pixel, etc. found in ImageMagick's identify -verbose command line command. This would be most of the identify commands output other than histogram. Same for bmp, tiff, jpeg, jpeg 2000
- Open Tiff file with Group iv compression, save with a different compression method in a tiff file
- Open PNG file, change pixel at 50, 200 to black, save as PNG file

Im
September 14, 2018 12:47
Hi Scott,

Thanks for the shout-out to ImageSharp! I saw a nice uptake in traffic since you published this post and it's nice to see a couple of fans in the comments :)

We're getting very close to releasing an RC1 now with only a few open issues to fix before we can. The team (Scott Williams, Anton Firsov, Dirk Lemstra, and myself) are very proud of what we've built so far and are confident that we have provided something that can democratize image processing and really help developers to get their work done with little stress.

To answer a few things I saw in the comments.

I've always wondered about the warning that warning that classes in System.Drawing shouldn't be used within services, included ASP.NET ones. Do developers just do it anyway?


Brian, yes developers still do it anyway and I'm guilty of helping out having previously built the ImageProcessor libraries. The warning is well founded though as you can potentially lock up the entire server if something goes wrong.

.NET needs fully managed image file handling and codecs at some point now or in the future to right the mess of wrapped 20 year old win32 codecs.


Im that's exactly what ImageSharp does. We've got fully managed codecs for Bmp, Gif, Jpeg, and Png, and we've got Tiff in the works. We have comprehensive metadata API's and can report common and format specific information with the following method:
IImageInfo.Identify(Stream stream)


I must say though, none of this is trivial, Image decoders have to be incredibly robust in order to handle the mistakes of encoders from the past and juggling performance with flexibility is always a challenge.



September 15, 2018 8:03
Hi James,

Dirk Lemstra is the developer for both ImageSharp and Magick.NET. Which library is recommended for high performance website (say if we are building a website similar to Imgur)?

Thank you :)
September 17, 2018 14:15
@Brian Kowald the System.Drawing issue in ASP.NET has caught me out in the past - I was getting out of memory exceptions when I tried to resize a small image. I had to rewrite some of my code to get around it.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.