.NET Core Image Processing

Image processing, and in particular image resizing, is a common requirement for web applications. As such, I wanted to paint a panorama of the options that exist for .NET Core to process images. For each option, I’ll give a code sample for image resizing, and I’ll outline interesting features. I’ll conclude with a comparison of the performance of the libraries, in terms of speed, size, and quality of the output.

CoreCompat.System.Drawing

If you have existing code relying on System.Drawing, using this library is clearly your fastest path to .NET Core and cross-platform bliss: the performance and quality are fine, and the API is exactly the same. The built-in System.Drawing APIs are the easiest way to process images with .NET Framework, but they rely on the GDI+ features from Windows, which are not included in .NET Core, and are a client technology that was never designed for multi-threaded server environments. There are locking issues that may make this solution unsuitable for your applications.

CoreCompat.System.Drawing is a .NET Core port of the Mono implementation of System.Drawing. Like System.Drawing in .NET Framework and in Mono, CoreCompat.System.Drawing also relies on GDI+ on Windows. Caution is therefore advised, for the same reasons.

Also be careful when using the library cross-platform, to include the runtime.osx.10.10-x64.CoreCompat.System.Drawing and / or runtime.linux-x64.CoreCompat.System.Drawing packages.

ImageSharp

ImageSharp is a brand new, pure managed code, and cross-platform image processing library. Its performance is not as good as that of libraries relying on native OS-specific dependencies, but it remains very reasonable. Its only dependency is .NET itself, which makes it extremely portable: there is no additional package to install, just reference ImageSharp itself, and you’re done.

If you decide to use ImageSharp, don’t include the package that shows on NuGet: that’s going to be an empty placeholder until the first official release of ImageSharp ships. For the moment, you need to get a nightly build from a MyGet feed. This can be done by adding the following NuGet.config to the root directory of the project:

Resizing an image with ImageSharp is very simple.

For a new codebase, the library is surprisingly complete. It includes all the filters you’d expect to treat images, and even includes very comprehensive support for reading and writing EXIF tags (that code is shared with Magick.NET):

Note that the latest builds of ImageSharp are more modular than they used to, and if you’re going to use image formats such as Jpeg, or image processing capabilities such as Resize, you need to import additional packages in addition to the core ImageSharp package (respectively ImageSharp.Processing and ImageSharp.Formats.Jpeg).

Magick.NET

Magick.NET is the .NET wrapper for the popular ImageMagick library. ImageMagick is an open-source, cross-platform library that focuses on image quality, and on offering a very wide choice of supported image formats. It also has the same support for EXIF as ImageSharp.

The .NET Core build of Magick.NET currently only supports Windows. The author of the library, Dirk Lemstra is looking for help with converting build scripts for the native ImageMagick dependency, so if you have some expertise building native libraries on Mac or Linux, this is a great opportunity to help an awesome project.

Magick.NET has the best image quality of all the libraries discussed in this post, as you can see in the samples below, and it performs relatively well. It also has a very complete API, and the best support for exotic file formats.

SkiaSharp

SkiaSharp is the .NET wrapper for Google’s Skia cross-platform 2D graphics library, that is maintained by the Xamarin team. SkiaSharp is now compatible with .NET Core, and is extremely fast. As it relies on native libraries, its installation can be tricky, but I was able to make it work easily on Windows and macOS. Linux is currently more challenging, as it’s necessary to build some native libraries from code, but the team is working on ironing out those speedbumps, so SkiaSharp should soon be a very interesting option.

FreeImage-dotnet-core

This library is to the native FreeImage library what Magick.NET is to ImageMagick: a .NET Core wrapper. It offers a nice choice of image formats, good performance, and good visual quality. Cross-platform support at this point is not perfect however, as I was unable to save images to disk on Linux and macOS. Hopefully that is fixed soon.

MagicScaler

MagicScaler is a Windows-only library that relies on Windows Image Components (WIC) for handling the images, but applies its own algorithms for very high quality resampling. It’s not a general purpose 2D library, but one that focuses exclusively on image resizing. As you can see in the gallery below, the results are impressive: the library is extremely fast, and achieves unparalleled quality. The lack of cross-platform support is going to be a showstopper to many applications, but if you can afford to run on Windows only, and only need image resizing, this is a superb choice.

Performance comparison

The first benchmark loads, resizes, and saves images on disk as Jpegs with a a quality of 75. I used 12 images with a good variety of subjects, and details that are not too easy to resize, so that defects are easy to spot. The images are roughly one megapixel JPEGs, except for one of the images that is a little smaller. Your mileage may vary, depending on what type of image you need to work with. I’d recommend you try to reproduce these results with a sample of images that corresponds to your own use case.

For the second benchmark, an empty megapixel image is resized to a 150 pixel wide thumbnail, without disk access.

The benchmarks use .NET Core 1.0.3 (the latest LTS at this date) for CoreCompat.System.Drawing, ImageSharp, and Magick.NET, and Mono 4.6.2 for SkiaSharp.

I ran the benchmarks on Windows on a HP Z420 workstation with a quad-core Xeon E5-1620 processor, 16GB of RAM, and the built-in Radeon GPU. For Linux, the results are for the same machine as Windows, but in a 4GB VM, so lower performance does not mean anything regarding Windows vs. Linux performance, and only library to library comparison should be considered meaningful. The macOS numbers are on an iMac with a 1.4GHz Core i5 processor, 8GB of RAM, and the built-in Intel HD Graphics 5000 GPU, running macOS Sierra.

Results are going to vary substantially depending on hardware: usage and performance of the GPU and of SIMD depends on both what’s available on the machine, and on the usage the library is making of it. Developers wanting to get maximum performance should further experiment. I should mention that I had to disable OpenCL on Magick.NET (OpenCL.IsEnabled = false;), as I was getting substantially worse performance with it enabled on that workstation than on my laptop.

Image Resizing Performance (Windows)

Library Load, resize, save (ms per image) Resize (ms per image)
CoreCompat.System.Drawing 34 ± 1 16.0 ± 0.6
ImageSharp 45 ± 1 10.1 ± 0.2
Magick.NET 56 ± 2 24.1 ± 0.3
SkiaSharp 20 ± 1 7.8 ± 0.1
FreeImage 39 ± 1 12.9 ± 0.1
MagicScaler 20 ± 1 n/a

For both metrics, lower is better.

Image Resizing Performance (macOS)

Library Load, resize, save (ms per image) Resize (ms per image)
CoreCompat.System.Drawing 93 ± 1 71.5 ± 0.3
ImageSharp 70.3 ± 0.3 27.6 ± 0.2
SkiaSharp 15.9 ± 0.1 7.6 ± 0.1
FreeImage n/a 12.8 ± 0.1

For both metrics, lower is better.

Image Resizing Performance (Linux)

Library Load, resize, save (ms per image) Resize (ms per image)
CoreCompat.System.Drawing 114 ± 5 92 ± 1
ImageSharp 384 ± 5 128 ± 1
FreeImage n/a 29.7 ± 2

For both metrics, lower is better.

File Size

Library File Size (average kB per image)
CoreCompat.System.Drawing 4.0
ImageSharp 4.0
Magick.NET 4.2
SkiaSharp 3.6
FreeImage 3.6
MagicScaler 4.3

Lower is better. Note that file size is affected by the quality of the subsampling that’s being performed, so size comparisons should take into account the visual quality of the end result.

Quality comparison

Here are the resized images. As you can see, the quality varies a lot from one image to the next, and between libraries. Some images show dramatic differences in sharpness, and some moiré effects can be seen in places. You should make a choice based on the constraints of your project, and on the performance vs. quality trade-offs you’re willing to make.

CoreCompat.System.Drawing ImageSharp Magick.NET SkiaSharp FreeImage MagicScaler
DSCN0533-SystemDrawing DSCN0533-ImageSharp DSCN0533-MagickNET DSCN0533-SkiaSharpBitmap DSCN0533-FreeImage DSCN0533-MagicScaler
IMG_2301-SystemDrawing IMG_2301-ImageSharp IMG_2301-MagickNET IMG_2301-SkiaSharpBitmap IMG_2301-FreeImage IMG_2301-MagicScaler
IMG_2317-SystemDrawing IMG_2317-ImageSharp IMG_2317-MagickNET IMG_2317-SkiaSharpBitmap IMG_2317-FreeImage IMG_2317-MagicScaler
IMG_2325-SystemDrawing IMG_2325-ImageSharp IMG_2325-MagickNET IMG_2325-SkiaSharpBitmap IMG_2325-FreeImage IMG_2325-MagicScaler
IMG_2351-SystemDrawing IMG_2351-ImageSharp IMG_2351-MagickNET IMG_2351-SkiaSharpBitmap IMG_2351-FreeImage IMG_2351-MagicScaler
IMG_2443-SystemDrawing IMG_2443-ImageSharp IMG_2443-MagickNET IMG_2443-SkiaSharpBitmap IMG_2443-FreeImage IMG_2443-MagicScaler
IMG_2445-SystemDrawing IMG_2445-ImageSharp IMG_2445-MagickNET IMG_2445-SkiaSharpBitmap IMG_2445-FreeImage IMG_2445-MagicScaler
IMG_2446-SystemDrawing IMG_2446-ImageSharp IMG_2446-MagickNET IMG_2446-SkiaSharpBitmap IMG_2446-FreeImage IMG_2446-MagicScaler
IMG_2525-SystemDrawing IMG_2525-ImageSharp IMG_2525-MagickNET IMG_2525-SkiaSharpBitmap IMG_2525-FreeImage IMG_2525-MagicScaler
IMG_2565-SystemDrawing IMG_2565-ImageSharp IMG_2565-MagickNET IMG_2565-SkiaSharpBitmap IMG_2565-FreeImage IMG_2565-MagicScaler
IMG_2734-SystemDrawing IMG_2734-ImageSharp IMG_2734-MagickNET IMG_2734-SkiaSharpBitmap IMG_2734-FreeImage IMG_2734-MagicScaler
sample-SystemDrawing sample-ImageSharp sample-MagickNET sample-SkiaSharpBitmap sample-FreeImage sample-MagicScaler

Conclusions

There is today a good choice of libraries for image processing on .NET Core, that can fit different requirements, with even more great choices coming in the near future.

If performance is your priority, CoreCompat.System.Drawing is a good choice today if the possible lock issues in Windows server scenarios are not a showstopper for your application. SkiaSharp, when Linux setup issues are resolved, will be a fantastic choice that offers performance without compromising quality.

If quality is your top-priority, and you can run on Windows only, MagicScaler is fantastic, without a performance compromise.

If file type support is your priority, Magick.NET is the winner. Cross-platform support is not quite there yet, however, but you can help.

Finally, the only pure managed code library available at this point, ImageSharp, is an excellent choice. Its performance is close to that of Magick.NET, and the fact that it has no native dependencies means that the library is guaranteed to work everywhere .NET Core works. The library is still in alpha, and significant performance improvements are in store, notably with future usage of Span<T> and ref returns.

Acknowledgements

All four libraries in this post are open-source, and only exist thanks to the talent and generosity of their authors, contributors and maintainers. In particular,

Sample code

My sample code can be found on GitHub. The repository includes the sample images I’ve been using in this post.

UPDATE 1/20/2017 11:09AM PST: updating ImageSharp to the latest daily build (1.0.0-alpha1-00053) considerably improved output quality.

UPDATE 2/14/2017: updated all libraries to their latest version, ran SkiaSharp on .NET Core instead of Mono, updated sample code for better quality, added FreeImage and MagicScaler libraries, re-ran all benchmarks.

UPDATE 2/16/2017: fixed an anomaly in Magick.NET Windows performance.