Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Image Resizing Performance: System.Drawing vs System.Windows.Media

I've got a situation where I need to resize a large number of images. These images are stored as .jpg files on the file system currently, but I expect to just have byte[] in memory later on in the project. The source image size is variable, but the output should be 3 different predetermined sizes. Aspect ratios should be preserved, padding the original image with white space (ie, a really tall image would be resized to fit within the square target image size, with large areas of white on the left and right).

I initially built the project targeting .NET 2.0, and using System.Drawing classes to perform the load/resize/save. Relevant code includes:

original = Image.FromFile(inputFile); //NOTE: Reused for each of the 3 target sizes
Bitmap resized = new Bitmap(size, size);
//Draw the image to a new image of the intended size
Graphics g = Graphics.FromImage(resized);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.Clear(Color.White);
g.DrawImage(original, center - width / 2f, center - height / 2f, width, height);
g.Dispose();
//Save the new image to the output path
resized.Save(outputFile, ImageFormat.Jpeg);

I wanted to port this project to .NET 3.5, so tried using the System.Windows.Media classes to perform the same function. I got it working, however performance is terrible; processing time per image is about 50x longer. The vast majority of the time is spent loading the image. Relevant code includes:

BitmapImage original = new BitmapImage(); //Again, reused for each of the 3 target sizes
original.BeginInit();
original.StreamSource = new MemoryStream(imageData); //imageData is a byte[] of the data loaded from a FileStream
original.CreateOptions = BitmapCreateOptions.None;
original.CacheOption = BitmapCacheOption.Default;
original.EndInit(); //Here's where the vast majority of the time is spent
original.Freeze();

// Target Rect for the resize operation
Rect rect = new Rect(center - width / 2d, center - height / 2d, width, height);

// Create a DrawingVisual/Context to render with
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
    drawingContext.DrawImage(original, rect);
}

// Use RenderTargetBitmap to resize the original image
RenderTargetBitmap resizedImage = new RenderTargetBitmap(
    size, size,                         // Resized dimensions
    96, 96,                             // Default DPI values
    PixelFormats.Default);              // Default pixel format
resizedImage.Render(drawingVisual);

// Encode the image using the original format and save the modified image
SaveImageData(resizedImage, outputFile);

Am I doing something wrong here, to take so much time? I've tried just using the constructor on BitmapImage that takes a URI, same performance issue there. Anyone done anything like this before, know if there's a more performance-minded way to do this? Or am I just going to need to use System.Drawing still? Thanks!

like image 739
Chadd Nervig Avatar asked Nov 16 '09 23:11

Chadd Nervig


People also ask

What is image processing resize?

Image interpolation occurs when you resize or distort your image from one pixel grid to another. Image resizing is necessary when you need to increase or decrease the total number of pixels, whereas remapping can occur when you are correcting for lens distortion or rotating an image.

How do I resize an image in .NET core?

Resizing an Image can be done in a range of ways. The easiest method is to create a new Bitmap object from the in-memory image. When creating the Bitmap object, you assign the new dimension in the Size parameter. Finally, the resized Bitmap is written to a byte array.


2 Answers

And after typing all that up, it occurred to me that I could load the symbols from MS for the System.Windows.Media classes, and step through where it was slow. Immediately found the cause, and the solution. The input images were saved with a color profile, and it was attempting to load that color profile (from the file system) of each image. By switching from BitmapCreateOptions.None to BitmapCreateOptions.IgnoreColorProfile in the code above, it no longer does that, and performs just as fast as System.Drawing did.

Hope this helps anyone else that runs into this problem!

like image 84
Chadd Nervig Avatar answered Nov 15 '22 17:11

Chadd Nervig


You appear to be doing this the hard way. You can let WPF do the work for you by just setting DecodePixelHeight and DecodePixelWidth. This will cause the resize to happen during the image load:

BitmapImage resizedImage = new BitmapImage
{
  StreamSource = new MemoryStream(imageData),
  CreateOptions = BitmapCreateOptions.IgnoreColorProfile,
  DecodePixelHeight = height,
  DecodePixelWidth = width,
}
resizedImage.BeginInit();  // Needed only so we can call EndInit()
resizedImage.EndInit();    // This does the actual loading and resizing

imageSaveImageData(resizedImage, outputFile);

I also included the IgnoreColorProfile solution you found in my code.

Update I reread your question and realized the reason you're using DrawingVisual is that you need whitespace around your image to make it square. DecodePixelHeight and DecodePixelWidth would not accomplish that goal, so my solution does not answer your question.

I will leave my answer here in case someone who just needs a resize without whitespace comes across this question.

like image 23
Ray Burns Avatar answered Nov 15 '22 17:11

Ray Burns