调整图像大小 - WIC 与 GDI

Posted

技术标签:

【中文标题】调整图像大小 - WIC 与 GDI【英文标题】:Resizing Images - WIC vs GDI 【发布时间】:2013-11-28 11:25:49 【问题描述】:

编辑:从那以后,我发现这些实现都不适用于服务器端场景:Microsoft 不支持它们。所以不要去使用它们!

所以我在下面提供了两个单独的实现。我想我已经完成了 Windows 映像组件 (WIC) 的实现。

一些cmets:

GDI 实施似乎比 WIC 更快 - WIC @ 0.26s/photo, GDI @ 0.14s/photo) 当多线程时,WIC 实现没有看到任何性能提升,GDI 下降到 ~0.10s/photo 服务器端处理仅支持 WIC,但如果它不支持多线程,则无法很好地扩展 在 i7 上运行,有问题的照片是由 Olympus 相机创建的典型 1.2MB 图像 我的灵感来自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
    

【问题讨论】:

“我想我已经完成了 Windows 映像组件 (WIC) 的实现。[...] 谁能看到任何明显的东西?” 我不太明白这个问题. 如何你的代码“塞满”了,有什么问题? 补充一点:WIC可以在服务器上使用。但是您需要直接使用 WIC,而不是通过 WPF 提供的抽象。您可以直接在 C++/CLI 中使用 WIC,也可以使用现有的几个互操作库中的任何一个(例如,SharpDX,它看起来非常完整和优美;或者(无耻广告)my own WIC interop library for managed .NET code)。 【参考方案1】:

我注意到BitmapCacheOption.None 在处理批处理时大大提高了性能。

【讨论】:

以上是关于调整图像大小 - WIC 与 GDI的主要内容,如果未能解决你的问题,请参考以下文章

GDI+TGPImage - 调整图像大小实例

PHP图像动态调整大小与存储调整大小的图像

使用 WIC 从流中加载图像的结果颠倒了

调整图像大小以使大小与指定的纵横比匹配[重复]

PHP:图像调整大小和裁剪为纵向

在调整大小时调整可拖动对象背景图像的大小