English 中文(简体)
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!

最佳回答

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!

问题回答

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.

I think this from the System.Drawing page on MSDN might be relevant:

The System.Drawing namespace provides access to GDI+ basic graphics functionality. More advanced functionality is provided in the System.Drawing.Drawing2D, System.Drawing.Imaging, and System.Drawing.Text namespaces. The Graphics class provides methods for drawing to the display device. Classes such as Rectangle and Point encapsulate GDI+ primitives. The Pen class is used to draw lines and curves, while classes derived from the abstract class Brush are used to fill the interiors of shapes.

By using System.Drawing you are closer to the actual basic graphics functionality than if you go via System.Windows.Media which:

Defines objects that enable integration of rich media, including drawings, text, and audio/video content within Windows Presentation Foundation (WPF) applications.

System.Drawing is still supported, so I d stick with that.

I found an interesting situation in your code. Remove using from following line:

using(DrawingContext drawingContext = drawingVisual.RenderOpen())

I m not sure why this speed up code, but you can give it a try.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签