调整图像大小 - 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的主要内容,如果未能解决你的问题,请参考以下文章