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

26

有没有人知道如何在完全不会损失任何质量的情况下调整透明图片(主要是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支持的库,像我的这个http://imageresizing.net。它已经存在了4年,并且得到了良好的维护和支持。 - Lilith River
5个回答

51

如果在缩放后没有对文件类型的保留要求,我建议采用以下方法。

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 不兼容。有关详细信息,请参见此博客文章

编辑:我忘记在示例中释放位图了;已经更正。


哇,马库斯 - 非常好。我在网上放了一个样本:http://www.thewallcompany.dk/test/ 就我所看到的 - 与手动调整大小的 gif 图像相比,绝对没有任何损失。非常感谢。 - MartinHN
它只适用于正方形的图像,并且不按比例调整大小。 - eKek0
1
以下是调整图像大小时需要注意的TODO清单:http://nathanaeljones.com/163/20-image-resizing-pitfalls/此外,如果有人需要支持透明度的GIF量化,请参考我实现的方法(Google搜索“ASP.NET图像调整模块”)。 - Lilith River
为了易于阅读,您可以将using语句组合在一个花括号中,例如:\n using()\n using()\n { // 您的语句。 } - user94893
您的图片链接似乎已经失效了。如果您仍然拥有原始图片,请重新上传到 stack.imgur。 - Ilmari Karonen

5

这是我在几个应用程序中使用的基本调整大小函数,利用了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格式,但你可以尝试一下。

注意:这个函数的创作并非完全出自于我。我从网上找到了一些样例,将它们拼凑在一起并使其符合我自己的需求。


3
我认为问题在于您正在进行基于扫描线的调整大小,无论您如何调整,都会导致锯齿状。 良好的图像调整大小质量需要您做更多的工作,以确定调整大小后像素覆盖的预调整大小像素的平均颜色。
这个网站的管理员在博客文章中讨论了一些图像调整算法。 您可能需要使用双立方图像缩放算法。 更好的图像调整大小

3
嘿,“管理这个网站的人” :) - Shawn Miller

1

对于任何试图使用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 );
}

0

虽然PNG比GIF更好,但有时需要保持GIF格式。

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

量化是指选择最能保留和表示图像的256(或更少)种颜色,并将RGB值转换回索引。当执行调整大小操作时,理想的调色板会发生变化,因为您正在混合颜色并改变平衡。

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

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

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

imageresizing.net项目中实现透明GIF和动画GIF支持时,我选择了八叉树。一旦掌握了图像调色板的控制权,透明度支持并不难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接