dlemstra / Magick.NET

The .NET library for ImageMagick
Apache License 2.0
3.42k stars 413 forks source link

TIFF loading performance is very low #644

Closed tonyp7 closed 4 years ago

tonyp7 commented 4 years ago

Prerequisites

Description

When loading a large (10mb) TIFF image, creating the MagickImage object takes roughly 1200ms on my machine (still around ~1000 using the OpenMP version); vs the sub 20ms it takes with System.Imaging on my machine. This is weird because in my experience the ImageMagick JPEG decompressor for instance systematically performs better than Microsoft's libraries while keeping in the same order of magnitude. Here we're talking 2 orders of magnitude difference which leads me to believe something is wrong.

Steps to Reproduce

I have zipped and uploaded the image here for convenience

string imgLocationTIFF = @"c:\path\to\image.tif";
Bitmap bmp;
ImageMagick.MagickImage imgM;
Image img;
Stopwatch stopwatch = new Stopwatch();

stopwatch.Start();
imgM = new ImageMagick.MagickImage(imgLocationTIFF);
bmp = imgM.ToBitmap();
stopwatch.Stop();
rtfBox.Text += String.Format("ImageMagick TIFF: {0}\r\n", stopwatch.ElapsedMilliseconds);

stopwatch = new Stopwatch();
stopwatch.Start();
img = Image.FromFile(imgLocationTIFF);
bmp = (Bitmap)img;
stopwatch.Stop();
rtfBox.Text += String.Format("Microsoft TIFF: {0}\r\n", stopwatch.ElapsedMilliseconds);

Produces:

Magick.NET-Q8-x64
ImageMagick TIFF: 1376
Microsoft TIFF: 13

Magick.NET-Q8-OpenMP-x64
ImageMagick TIFF: 927
Microsoft TIFF: 12

I have tried "helping" the lib by doing

imgM = new ImageMagick.MagickImage(System.IO.File.ReadAllBytes(imgLocationTIFF), ImageMagick.MagickFormat.Tif);

But the results are the same

System Configuration

dlemstra commented 4 years ago

Can you share your tiff file? And I wonder if the Bitmap coder will decode the image or wait with that until the pixels are accessed.

tonyp7 commented 4 years ago

I have noticed this difference as I was blitting the bitmap in the picturebox, so I am certain this is not the case and the issue is with the loading itself.

I have built a very simple project that shows the behavior so that it's convenient: https://idyl.io/wp-content/uploads/2020/05/WindowsFormsApp1.zip

The image is included with the zip

dlemstra commented 4 years ago

What is the output of:

stopwatch.Start();
imgM = new ImageMagick.MagickImage(imgLocationTIFF);
rtfBox.Text += String.Format("ImageMagick TIFF: {0}\r\n", stopwatch.ElapsedMilliseconds);
bmp = imgM.ToBitmap();
stopwatch.Stop();
rtfBox.Text += String.Format("ImageMagick ToBitmap: {0}\r\n", stopwatch.ElapsedMilliseconds);

It is very like that the conversion to Bitmap is not optimal.

tonyp7 commented 4 years ago

I think you are right

Watch.Reset();
Watch.Start();
MagickImage img = new MagickImage(imgLocationTIFF);
myPictureBox.Size = new Size(img.Width, img.Height);
System.Diagnostics.Debug.WriteLine("Load time: " + Watch.ElapsedMilliseconds.ToString());
Watch.Reset();
Watch.Start();
myPictureBox.BMP = img.ToBitmap();
System.Diagnostics.Debug.WriteLine("To Bitmap time: " + Watch.ElapsedMilliseconds.ToString());
myPictureBox.Invalidate(); //force a repaint
Watch.Stop();

Load time: 323 To Bitmap time: 1103

But both methods are trailing far behind System.Drawing with TIF. I'll to have to check on the native ImageMagick to see if the bottlenet is in the library itself or if'ts in the wrapper

dlemstra commented 4 years ago

The issue seems to be that you have an image with a Gray color space. There does not seem to be a special case for a image with a Gray color space. And what happens is that your images get transformed from 1 channel into 3 channels and that is one of the performance issues. This will be fixed in the next release.

Another thing is that your image is 1 bit per channel and when the image is read this gets changed into 8 bits per channel. Even though you only have one channel this will still have some performance impact. The Bitmap class supports 1 bit gray and that is probably why it is performing better.

tonyp7 commented 4 years ago

Thank you I appreciate the time you take to look into all these

dlemstra commented 4 years ago

Some improvements have been made in the latest release. But the code has been moved to a separate package: https://www.nuget.org/packages/Magick.NET.SystemDrawing/.

tonyp7 commented 4 years ago

This seems to break existing code: constructor MagickImage(System.Drawing.Bitmap) no longer exists even with Magick.NET.SystemDrawing installed. ToBitmap works fine.

dlemstra commented 4 years ago

The problem here is that you cannot write extension methods for constructor. And because the new SystemDrawing library only knows the core library I had to solve this differently. You now only have the following options:

var factory = new MagickFactory();
factory.CreateImage(bitmap);

// This will change in the next release to:
// factory.Image.Create(bitmap);

using(var image = new MagickImage())
{
    image.Read(bitmap);
}
tonyp7 commented 4 years ago

Alright that makes sense. I’ll integrate the System.Drawing codebase directly into my project to avoid distributing additional DLLs but otherwise I think you can close this issue if nothing else is expected!

dlemstra commented 4 years ago

Make sure you keep the copyright header when you integrate those file. Will close this issue now.

p.s. I noticed that you also committed the folder packages in your project. It is a binary folder that you want to include in the .gitignore list.