使用 C# 调整透明图像的大小

Posted

技术标签:

【中文标题】使用 C# 调整透明图像的大小【英文标题】:Resize transparent images using C# 【发布时间】:2010-09-07 00:45:10 【问题描述】:

有没有人有秘诀来调整透明图像(主要是 GIF)的大小没有任何质量损失 - 曾经有什么?

我尝试了一堆东西,我得到的最接近的还不够好。

看看我的主图:

http://www.thewallcompany.dk/test/main.gif

然后是缩放的图像:

http://www.thewallcompany.dk/test/ScaledImage.gif

//Internal resize for indexed colored images
void IndexedRezise(int xSize, int ySize)

  BitmapData sourceData;
  BitmapData targetData;

  AdjustSizes(ref xSize, ref ySize);

  scaledBitmap = new Bitmap(xSize, ySize, bitmap.PixelFormat);
  scaledBitmap.Palette = bitmap.Palette;
  sourceData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
    ImageLockMode.ReadOnly, bitmap.PixelFormat);
  try
  
    targetData = scaledBitmap.LockBits(new Rectangle(0, 0, xSize, ySize),
      ImageLockMode.WriteOnly, scaledBitmap.PixelFormat);
    try
    
      xFactor = (Double)bitmap.Width / (Double)scaledBitmap.Width;
      yFactor = (Double)bitmap.Height / (Double)scaledBitmap.Height;
      sourceStride = sourceData.Stride;
      sourceScan0 = sourceData.Scan0;
      int targetStride = targetData.Stride;
      System.IntPtr targetScan0 = targetData.Scan0;
      unsafe
      
        byte* p = (byte*)(void*)targetScan0;
        int nOffset = targetStride - scaledBitmap.Width;
        int nWidth = scaledBitmap.Width;
        for (int y = 0; y < scaledBitmap.Height; ++y)
        
          for (int x = 0; x < nWidth; ++x)
          
            p[0] = GetSourceByteAt(x, y);
            ++p;
          
          p += nOffset;
        
      
    
    finally
    
      scaledBitmap.UnlockBits(targetData);
    
  
  finally
  
    bitmap.UnlockBits(sourceData);
  

我正在使用上面的代码来做索引调整大小。

有人有改进的想法吗?

【问题讨论】:

我建议使用修复 GDI 糟糕的 GIF 支持的库。和我一样,imageresizing.net 它已经存在 4 年了,并且得到了很好的维护和支持。 【参考方案1】:

如果缩放后​​不需要保留文件类型,我推荐以下方法。

using (Image src = Image.FromFile("main.gif"))
using (Bitmap dst = new Bitmap(100, 129))
using (Graphics g = Graphics.FromImage(dst))

   g.SmoothingMode = SmoothingMode.AntiAlias;
   g.InterpolationMode = InterpolationMode.HighQualityBicubic;
   g.DrawImage(src, 0, 0, dst.Width, dst.Height);
   dst.Save("scale.png", ImageFormat.Png);

结果将具有非常好的抗锯齿边缘

删除了已被广告替换的图片小屋图片

如果您必须将图像导出为 gif 格式,您可以试一试; GDI+ 不能很好地处理 gif。请参阅this blog post 了解更多信息

编辑:我忘了处理示例中的位图;已经更正了

【讨论】:

哇,马库斯——太好了。我在网上放了一个示例:http://www.thewallcompany.dk/test/ 据我所知 - 与手动调整大小的 gif 图像相比,绝对没有损失。非常感谢。 它只适用于类似正方形的图像,不能按比例调整大小 这是调整图像大小时的 TODO 项列表:nathanaeljones.com/163/20-image-resizing-pitfalls 另外,如果有人需要,我已经实现了支持透明度的 GIF 量化(谷歌“ASP.NET 图像调整模块”) 为了便于阅读,您可以将 using 语句分组在一个大括号中,例如 using()\n using()\n // 您的语句。 您的图片链接似乎已损坏。如果您还有原图,请重新上传到 stack.imgur。【参考方案2】:

虽然 PNG 肯定比 GIF 更好,但偶尔也有需要保持 GIF 格式的用例。

使用 GIF 或 8 位 PNG,您必须解决量化问题。

量化是您选择哪 256 种(或更少)颜色将最好地保留和表示图像,然后将 RGB 值转换回索引。当您执行调整大小操作时,理想的调色板会随着您混合颜色和改变平衡而改变。

对于轻微的调整大小,例如 10-30%,您可以保留原始调色板。

但是,在大多数情况下,您需要重新量化。

主要的两种算法是八叉树和 nQuant。八叉树速度非常快并且做得非常好,特别是如果您可以叠加智能抖动算法。 nQuant 需要至少 80MB 的 RAM 来执行编码(它构建完整的直方图),并且通常慢 20-30 倍(平均图像上每个编码 1-5 秒)。但是,它有时会产生比 Octree 更高的图像质量,因为它不会“舍入”值来保持一致的性能。

在imageresizing.net 项目中实现透明 GIF 和动画 GIF 支持时,我选择了 Octree。一旦您控制了图像调色板,透明度支持就不难了。

【讨论】:

【参考方案3】:

对于任何可能尝试使用 Markus Olsson 的解决方案来动态调整图像大小并将其写入响应流的人。

这不起作用:

Response.ContentType = "image/png";
dst.Save( Response.OutputStream, ImageFormat.Png );

但这会:

Response.ContentType = "image/png";
using (MemoryStream stream = new MemoryStream())

    dst.Save( stream, ImageFormat.Png );

    stream.WriteTo( Response.OutputStream );

【讨论】:

【参考方案4】:

这是我在一些利用 GDI+ 的应用程序中使用的基本调整大小功能

/// <summary>
///    Resize image with GDI+ so that image is nice and clear with required size.
/// </summary>
/// <param name="SourceImage">Image to resize</param>
/// <param name="NewHeight">New height to resize to.</param>
/// <param name="NewWidth">New width to resize to.</param>
/// <returns>Image object resized to new dimensions.</returns>
/// <remarks></remarks>
public static Image ImageResize(Image SourceImage, Int32 NewHeight, Int32 NewWidth)

   System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(NewWidth, NewHeight, SourceImage.PixelFormat);

   if (bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format1bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format4bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format8bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Undefined | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.DontCare | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppArgb1555 | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppGrayScale) 
   
      throw new NotSupportedException("Pixel format of the image is not supported.");
   

   System.Drawing.Graphics graphicsImage = System.Drawing.Graphics.FromImage(bitmap);

   graphicsImage.SmoothingMode = Drawing.Drawing2D.SmoothingMode.HighQuality;
   graphicsImage.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
   graphicsImage.DrawImage(SourceImage, 0, 0, bitmap.Width, bitmap.Height);
   graphicsImage.Dispose();
   return bitmap; 

我不记得它是否适用于 GIF,但您可以尝试一下。

注意:我不能完全归功于此功能。我从其他一些在线示例中拼凑了一些东西,并使其满足我的需求 8^D

【讨论】:

【参考方案5】:

我认为问题在于您正在执行基于扫描线的调整大小,无论您如何调整它都会导致锯齿。良好的图像调整质量要求您做更多的工作来确定调整后像素覆盖的预调整大小像素的平均颜色。

运行这个网站的人有一篇博客文章,讨论了一些图像大小调整算法。您可能需要双三次图像缩放算法。

Better Image Resizing

【讨论】:

嘿,“经营这个网站的人”:)

以上是关于使用 C# 调整透明图像的大小的主要内容,如果未能解决你的问题,请参考以下文章

没有 Imagick 的图像调整大小并失去透明度

我想用C#获取图像不透明区域的大小

PHP调整图像大小并保存为透明PNG

在 CodeIgniter 中调整 PNG 的大小,同时保留透明度

如何改进我的 php 图像调整器以支持 alpha png 和透明 GIF

使用gd在php中调整图像大小后的黑色背景