2013-11-15 4 views
0

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

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

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

  • Реализация GDI, кажется, чтобы быть быстрее, чем WIC - WIC @ 0.26s/фото, GDI @ 0.14s/фото)
  • WIC реализация не видит никаких преимуществ по производительности когда многопоточный, GDI падает до ~ 0,10 с/фото
  • Поддерживается только WIC для обработки на стороне сервера, однако, если он не работает, то он не масштабируется хорошо
  • Запуск на i7, фото, о котором идет речь, было типичным изображением 1,2 МБ, созданным Olympus digicam
  • я обратил свое вдохновение из 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, 
      ImagePreviewGeneratorResizeQualityEnum resizeQuality = ImagePreviewGeneratorResizeQualityEnum.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(ImagePreviewGeneratorResizeQualityEnum previewQuality) 
     { 
      switch (previewQuality) 
      { 
       case ImagePreviewGeneratorResizeQualityEnum.HighQuality: 
        return BitmapScalingMode.HighQuality; 
       case ImagePreviewGeneratorResizeQualityEnum.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, 
      ImagePreviewGeneratorResizeQualityEnum resizeQuality = ImagePreviewGeneratorResizeQualityEnum.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 ImagePreviewGeneratorResizeQualityEnum.HighQuality: 
          graphics.SmoothingMode = SmoothingMode.HighQuality; 
          graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 
          graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; 
          break; 
         case ImagePreviewGeneratorResizeQualityEnum.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 
    } 
} 
+0

_ «Я думаю, что я наполнил реализацию Windows Imaging Component (WIC). [...] Может ли кто-нибудь увидеть что-нибудь очевидное?» _ Я не совсем понимаю вопрос. * Как * ваш код «набит», в чем проблема? – stakx

+0

Еще одно замечание: WIC * может * использоваться на сервере. Но вам нужно использовать WIC напрямую, а не через абстракции, предоставляемые WPF. Вы можете использовать WIC непосредственно в C++/CLI или использовать любую из нескольких библиотек interop, которые находятся там (например, [SharpDX] (http://sharpdx.org/), которая выглядит очень полной и отполированной или (бесстыдное объявление) [моя встроенная библиотека WIC для управляемого кода .NET] (https://github.com/stakx/stakx.WIC)). – stakx

ответ

0

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