65.9K
CodeProject 正在变化。 阅读更多。
Home

从 PNG 中移除 gAMA 数据块,用于 ASP.NET 应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (7投票s)

2009 年 6 月 10 日

CPOL

4分钟阅读

viewsIcon

23789

用托管代码从 PNG 中移除 gAMA 数据块的代码,以解决伽马校正问题。

引言

随着 Internet Explorer 在 7 及以上版本中终于支持 PNG 透明度,这种有用的格式可以在网站上发挥自己的作用。 然而,还有另一个问题,伽马校正。

当信息可用时,IE 会对 PNG 图像进行伽马校正。 这很棒。 即使它的校正不正确,至少它是在尝试。 问题在于 IE 不会费心对其他任何内容进行伽马校正。 因此,如果您有一些时髦的圆角 PNG 角,也许是漂亮的矢车菊蓝色,然后您将它们放置在指定背景颜色为 #6495ED 的 div 周围,那么 imagediv 将不匹配。

静态图像的答案是从 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日:初始发布
© . All rights reserved.