Изменение размера изображений - WIC vs GDI

EDIT: С тех пор я понял, что ни одна из этих реализаций не подходит для серверных сценариев: они не поддерживаются Microsoft. Так что не используйте их!

Поэтому я представил две отдельные реализации ниже. Я думаю, что я наполнил реализацию Windows Imaging Component (WIC).

Некоторые комментарии:

  • Реализация GDI кажется более быстрой, чем WIC - WIC @0.26s/photo, GDI @0.14s/photo)
  • Реализация WIC не видит повышения производительности при многопоточности, GDI падает до ~ 0,10 с/фото
  • Только поддержка WIC поддерживается для обработки на стороне сервера, однако, если она не mutli-thread, то она недостаточно масштабируется
  • Запуск на i7, фотография, о которой идет речь, была типичным 1.2MB-изображением, созданным цифровым цифровым фотоаппаратом Olympus
  • Я черпал вдохновение из http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx

Может ли кто-нибудь увидеть что-нибудь очевидное?

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Mrwa.Bms.Common.Imaging
{
 /// <summary>
 /// Generates JPEG image previews for any supplied .NET image supported files
 /// </summary>
 /// <remarks>
 /// WIC = Windows Imaging Component
 /// http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx
 /// </remarks>
 public class WicImagePreviewGenerator : IImagePreviewGenerator
 {
 private const int ScreenDpi = 96;
 private BitmapFrame _imageFrame;

 public WicImagePreviewGenerator(Stream stream)
 {
 Contract.Requires(stream != null);

 try
 {
 if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin);

 var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
 _imageFrame = decoder.Frames[0];
 }
 catch (NotSupportedException ex)
 {
 throw new ArgumentException("The image is corrupt.", "stream", ex);
 }
 }

 public ImagePreviewGeneratorDto Generate(
 int pixelSize, int jpegQuality = 80, int dpi = 72,
 ************************************** resizeQuality = **************************************.HighQuality)
 {
 int previewWidth;
 int previewHeight;
 CalculateDimensions(pixelSize, out previewWidth, out previewHeight);

 // create a new target drawing canvas
 var width = (int) (previewWidth*(ScreenDpi/(decimal) dpi));
 var height = (int) (previewHeight*(ScreenDpi/(decimal) dpi));
 var drawing = new ImageDrawing(
 _imageFrame,
 new Rect(0, 0, width, height));

 var group = new DrawingGroup();
 RenderOptions.SetBitmapScalingMode(group, GetScalingMode(resizeQuality));
 group.Children.Add(drawing);

 // generate the preview image frame
 BitmapFrame previewFrame;
 var previewVisual = new DrawingVisual();
 using (var previewContext = previewVisual.RenderOpen())
 {
 previewContext.DrawDrawing(group);
 previewContext.Close();

 var previewBitmap = new RenderTargetBitmap(
 previewWidth, previewHeight,
 dpi, dpi,
 PixelFormats.Default);
 previewBitmap.Render(previewVisual);
 previewFrame = BitmapFrame.Create(previewBitmap);
 }

 // generate the result as a JPG
 using (var content = new MemoryStream())
 {
 var previewEncoder = new JpegBitmapEncoder { QualityLevel = jpegQuality };
 previewEncoder.Frames.Add(previewFrame);
 previewEncoder.Save(content);
 content.Flush();

 return new ImagePreviewGeneratorDto
 {
 Preview = content.ToArray(),
 Width = previewWidth,
 Height = previewHeight
 };
 }
 }

 // not used - retained for reference only
 public IEnumerable<byte> GenerateOptimised(int pixelSize, int jpegQuality = 80)
 {
 int previewWidth;
 int previewHeight;
 CalculateDimensions(pixelSize, out previewWidth, out previewHeight);

 var transform = new TransformedBitmap(
 _imageFrame, new ScaleTransform(previewWidth, previewHeight, 0, 0));

 var previewFrame = BitmapFrame.Create(transform);

 // generate the result as a JPG
 using (var result = new MemoryStream())
 {
 var previewEncoder = new JpegBitmapEncoder { QualityLevel = jpegQuality };
 previewEncoder.Frames.Add(previewFrame);
 previewEncoder.Save(result);

 return result.ToArray();
 }
 }

 private static BitmapScalingMode GetScalingMode(************************************** previewQuality)
 {
 switch (previewQuality)
 {
 case **************************************.HighQuality:
 return BitmapScalingMode.HighQuality;
 case **************************************.HighSpeed:
 return BitmapScalingMode.LowQuality;
 default:
 throw new NotSupportedException("Invalid preview quality specified.");
 }
 }

 private void CalculateDimensions(int pixelSize, out int width, out int height)
 {
 var originalWidth = _imageFrame.PixelWidth;
 var originalHeight = _imageFrame.PixelHeight;

 // scale: reduce the longest side down to 'X' pixels and maintain the aspect ratio
 if (originalWidth <= pixelSize && originalHeight <= pixelSize)
 {
 width = originalWidth;
 height = originalHeight;
 }
 else if (originalWidth >= originalHeight)
 {
 width = pixelSize;
 height = (int)((pixelSize / (decimal)originalWidth) * originalHeight);
 }
 else
 {
 width = (int)((pixelSize / (decimal)originalHeight) * originalWidth);
 height = pixelSize;
 }
 }

 #region IDisposable

 private bool _disposed;

 ~WicImagePreviewGenerator()
 {
 Dispose(false);
 }

 public void Dispose()
 {
 Dispose(true);
 GC.SuppressFinalize(this);
 }

 protected virtual void Dispose(bool disposing)
 {
 if (_disposed) return;
 if (disposing)
 {
 // free managed resources
 _imageFrame = null;
 }

 // free unmanaged resources

 _disposed = true;
 }

 #endregion
 }
}

using System;
using System.Diagnostics.Contracts;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace Mrwa.Bms.Common.Imaging
{
 /// <summary>
 /// Generates JPEG image previews for any supplied .NET image supported files
 /// </summary>
 /// <remarks>
 /// Feel free to use this Client side. Not officially supported for back-end scenarios.
 /// </remarks>
 public class GdiPlusImagePreviewGenerator : IImagePreviewGenerator
 {
 private Image _image;

 public GdiPlusImagePreviewGenerator(Stream stream)
 {
 Contract.Requires(stream != null);

 try
 {
 if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin);
 _image = Image.FromStream(stream);
 }
 catch (ArgumentException ex)
 {
 throw new ArgumentException("The image is corrupt.", "stream", ex);
 }
 }

 private void CalculateDimensions(int pixelSize, out int width, out int height)
 {
 var originalWidth = _image.Width;
 var originalHeight = _image.Height;

 // scale: reduce the longest side down to 'X' pixels and maintain the aspect ratio
 if (originalWidth <= pixelSize && originalHeight <= pixelSize)
 {
 width = originalWidth;
 height = originalHeight;
 }
 else if (originalWidth >= originalHeight)
 {
 width = pixelSize;
 height = (int)((pixelSize / (decimal)originalWidth) * originalHeight);
 }
 else
 {
 width = (int)((pixelSize / (decimal)originalHeight) * originalWidth);
 height = pixelSize;
 }
 }

 /// <remarks>
 /// Not changing the colour depth; apparently the conversion can be quite poor
 /// Don't forget to dispose of the stream
 /// </remarks>
 public ImagePreviewGeneratorDto Generate(
 int pixelSize, int jpegQuality = 80, int dpi = 72,
 ************************************** resizeQuality = **************************************.HighQuality)
 {
 int previewWidth;
 int previewHeight;
 CalculateDimensions(pixelSize, out previewWidth, out previewHeight);

 // resize the image (in terms of pixels) and standardise the DPI
 using (var previewImage = new Bitmap(previewWidth, previewHeight))
 {
 previewImage.SetResolution(dpi, dpi);
 using (var graphics = Graphics.FromImage(previewImage))
 {
 switch (resizeQuality)
 {
 case **************************************.HighQuality:
 graphics.SmoothingMode = SmoothingMode.HighQuality;
 graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
 graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
 break;
 case **************************************.HighSpeed:
 graphics.SmoothingMode = SmoothingMode.HighSpeed;
 graphics.InterpolationMode = InterpolationMode.Low;
 graphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
 break;
 default:
 throw new NotSupportedException("Invalid Preview Quality Enum supplied.");
 }

 graphics.DrawImage(_image, new Rectangle(0, 0, previewWidth, previewHeight));
 }

 // convert to a JPG and reduce the quality
 using (var content = new MemoryStream())
 {
 var jpegEncoder = GetEncoder(ImageFormat.Jpeg);

 previewImage.Save(content, jpegEncoder,
 new EncoderParameters
 {
 Param = new[] { new EncoderParameter(Encoder.Quality, jpegQuality) },
 });
 content.Flush();

 // return the stream
 return new ImagePreviewGeneratorDto
 {
 Preview = content.ToArray(),
 Width = previewWidth,
 Height = previewHeight
 };
 }
 }
 }

 private static ImageCodecInfo GetEncoder(ImageFormat format)
 {
 var codecs = ImageCodecInfo.GetImageDecoders();
 return codecs.FirstOrDefault(codec => codec.FormatID == format.Guid);
 }

 #region IDisposable

 private bool _disposed;

 ~GdiPlusImagePreviewGenerator()
 {
 Dispose(false);
 }

 public void Dispose()
 {
 Dispose(true);
 GC.SuppressFinalize(this);
 }

 protected virtual void Dispose(bool disposing)
 {
 if (_disposed) return;
 if (disposing)
 {
 // free managed resources
 if (_image != null)
 {
 _image.Dispose();
 _image = null;
 }
 }

 // free unmanaged resources

 _disposed = true;
 }

 #endregion
 }
}
</byte>
1 ответ

Я заметил, что BitmapCacheOption.None значительно повышает производительность при работе с пакетной обработкой.

licensed under cc by-sa 3.0 with attribution.