调整 PNG 图像的大小而不丢失完全透明像素的颜色数据

Resize PNG image without losing color data from fully transparent pixels

我一直在尝试寻找一种方法来调整 PNG 图像的大小而不丢失完全透明像素的颜色数据。这是我用来实现此目的的代码:

//sourceImage is also a bitmap read in earlier
using (var scaledBitmap = new Bitmap(desiredWidth, desiredHeight, PixelFormat.Format32bppArgb)){
    using (var g = Graphics.FromImage(scaledBitmap)){

        var attr = new ImageAttributes();
        attr.SetWrapMode(WrapMode.TileFlipXY);
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.CompositingMode    = CompositingMode.SourceCopy;
        g.InterpolationMode  = InterpolationMode.HighQualityBicubic;
        g.SmoothingMode      = SmoothingMode.AntiAlias;
        g.PixelOffsetMode    = PixelOffsetMode.HighQuality;

        g.DrawImage(sourceImage, new Rectangle(0, 0, desiredWidth, desiredHeight), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attr);
    }
    scaledBitmap.Save(memoryStream, ImageFormat.Png);
   //use new bitmap here
}

但此示例的问题在于,像素的 alpha 值为零的任何地方,像素都会变为黑色。

我的名声太低了,无法 post 遗憾地内嵌这些图片。

Images

第一张图片是我用来测试的图片。为了快速参考,我保存了一份没有 alpha 通道的副本(第二张图片)。整个图像是纯色,alpha 遮罩图像的一部分以创建一个圆圈。

第三张图片是上述代码生成的图片。我又保存了两份。在第四张图片中,很明显数据在转换过程中丢失了。我希望看到的是相同的单一颜色填充整个图像。

通过阅读,我在 PNG 文件规范中发现了这一点:

The alpha channel can be regarded either as a mask that temporarily hides transparent parts of the image, or as a means for constructing a non-rectangular image. In the first case, the colour values of fully transparent pixels should be preserved for future use. In the second case, the transparent pixels carry no useful data and are simply there to fill out the rectangular image area required by PNG. In this case, fully transparent pixels should all be assigned the same colour value for best compression.

因此看起来由 PNG 编码器决定如何处理 alpha 为零的颜色值。有什么方法可以告诉 GDI+ 的编码器不要将颜色数据清零吗?

我需要保留颜色数据的原因是为了进一步动态调整图像大小。如果图像可见部分的边缘没有均匀的颜色,它们非常容易出现振铃伪影。 Unity forums 上也有一个 post 关于这个问题。

如果其他人遇到过这种情况以及您是如何处理的,请告诉我。此外,如果有人知道任何实现 C# PNG 编码器的库不那么严苛,我们将不胜感激。

谢谢!

虽然我意识到这对某些人来说可能还不够,但就我而言,答案是在他们的论坛上使用 ImageMagick's .NET wrapper. I was also able to find a thread 来解决我遇到的确切问题。

这似乎是调整图像大小时的常见错误,ImageMagick 已在其库中修复了此问题。

using ImageMagick;

using (var sourceImage = new MagickImage(texture)){ //texture is of type Stream
    sourceImage.Resize(desiredWidth, desiredHeight);
    sourceImage.Write(memoryStream, MagickFormat.Png);
}
// use memoryStream here

如您所见,由于 ImageMagick 内置了对调整大小操作的支持,这也大大简化了我的代码。

如果没有 ImageMagick,您只需使用以下代码即可,无需更改 CompositingQuality 或任何其他图形属性:

Bitmap NewImage = new Bitmap(OriginalImage.Width, OriginalImage.Height, OriginalImage.PixelFormat);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(NewImage)) {
    g.DrawImage(OriginalImage, new Rectangle(0, 0, NewWidth, NewHeight), 0, 0, OriginalImage.Width, OriginalImage.Height, GraphicsUnit.Pixel);
}
NewImage.Save("myTransparent.png");