从 PNG 中移除 gAMA 数据块,用于 ASP.NET 应用程序
用托管代码从 PNG 中移除 gAMA 数据块的代码,以解决伽马校正问题。
引言
随着 Internet Explorer 在 7 及以上版本中终于支持 PNG 透明度,这种有用的格式可以在网站上发挥自己的作用。 然而,还有另一个问题,伽马校正。
当信息可用时,IE 会对 PNG 图像进行伽马校正。 这很棒。 即使它的校正不正确,至少它是在尝试。 问题在于 IE 不会费心对其他任何内容进行伽马校正。 因此,如果您有一些时髦的圆角 PNG 角,也许是漂亮的矢车菊蓝色,然后您将它们放置在指定背景颜色为 #6495ED 的 div
周围,那么 image
和 div
将不匹配。
静态图像的答案是从 PNG 中删除伽马信息。 这可以使用相当多的现成工具来完成。 这段小代码解决的问题是,当图像由您的代码动态生成并使用 GDI+/.NET 保存时,它会删除该信息。
背景
PNG 格式方便地分为多个数据块。 每个数据块提供有关图像的一些信息。 一些数据块(IHDR、PLTE、IDAT 和 IEND)是关键的,必须存在才能显示图像(PLTE 实际上仅对调色板图像至关重要)。 我们感兴趣的是 gAMA。 小写字母表示这是一个辅助数据块,并且是完全可选的。 这就是我们必须摆脱的。
这些数据块在文件中的布局非常简单。 首先是 4 字节部分,指定数据块的数据部分的长度。 然后,有一个标头,指定数据块类型,其长度也是 4 字节。 接下来是数据部分,它是第一部分中指定的长度,最后,我们有一个 CRC 部分,用于检测数据中的损坏。
我们需要知道的另一件事是 PNG 文件以八字节签名开头,并且我们的 gAMA 数据块必须出现在 IDAT 数据块之前,并且如果存在,则出现在 PLTE 数据块之前。
Using the Code
那么,我们要做的第一件事是将我们的 PNG 图像作为字节获取,以便我们可以对其进行处理。 我已经使用位图创建了我的动态图像,因此我必须将其保存为 PNG,然后从中检索字节。 最简单的方法是使用 MemoryStream
。 此外,我直接输出到 Response.OutputStream
,因此我也返回一个 MemoryStream
。
public MemoryStream StripGAMA(Bitmap input)
{
MemoryStream ms = new MemoryStream();
input.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
byte[] data = ms.ToArray();
ms = new MemoryStream();
ms.Write(data, 0, 8);
int offset = 8;
byte[] chkLenBytes = new byte[4];
int chkLength = 0;
string chkType = string.Empty;
在上面,您可以看到我已将位图保存到 MemoryStream
,然后将其转换为字节数组。 然后,我创建了一个新流并将前 8 个字节写入其中。 这是我们可以安全忽略的 PNG 签名。 然后,我声明了一些我们需要的变量。 此时唯一需要注意的是,offset
已经为 8,因为我们已经处理了签名。
现在,我们进入代码的主要部分。 这里有很多东西要处理数据块的长度。 这是因为 PNG 以网络字节顺序或大端存储值。 .NET 通常使用小端,因此我们必须翻转字节以在转换为整数时获得正确的值。 所有数据块名称(和 PNG 格式中的大多数文本)都使用 ASCII 编码,因此我们可以使用一个简单的 static
函数将字节转换为文本。
while (offset < data.Length-12)
{
chkLenBytes[0] = data[offset];
chkLenBytes[1] = data[offset + 1];
chkLenBytes[2] = data[offset + 2];
chkLenBytes[3] = data[offset + 3];
if(System.BitConverter.IsLittleEndian)
System.Array.Reverse(chkLenBytes);
chkLength = System.BitConverter.ToInt32(chkLenBytes, 0);
chkType = System.Text.Encoding.ASCII.GetString(data, offset + 4, 4);
现在,只需检查数据块类型并将我们不感兴趣的数据块写出即可。 或者,如果我们找到一个 gAMA,则跳过它并写出其余字节。 如果我们首先找到 IDAT 或 PLTE 数据块,则没有 gAMA 数据块,因此我们也可以完成。
if (chkType != "gAMA")
{
if (chkType == "IDAT" || chkType == "PLTE")
{
ms.Write(data, offset, data.Length - offset);
break;
}
else
{
ms.Write(data, offset, 12 + chkLength);
offset += 12 + chkLength;
}
}
else
{
offset += 12 + chkLength;
ms.Write(data, offset, data.Length - offset);
break;
}
return ms;
值 12 来自数据块的其他三个部分(长度、名称和 CRC 校验),每个部分 4 个字节。
就是这样。 稍作调整,如果您愿意,您也可以在静态文件上使用它,但是使用 Ken Silverman 的 PNGOUT 压缩器或任何其他可用的实用程序可能会更容易。
历史
- 2009年6月10日:初始发布