如何仅将 8 位 PNG 图像读取为 8 位 PNG 图像? [英] How to read 8-bit PNG image as 8-bit PNG image only?

查看:43
本文介绍了如何仅将 8 位 PNG 图像读取为 8 位 PNG 图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 8 位 PNG 图像(见附件).但是当我使用 Image.FromFile 方法读取它时,像素格式为 32 位.因此,我无法修改调色板.

I have a 8-bit PNG image (See the attachment). But when I read it using Image.FromFile method, the pixel format comes as 32-bit. Due to this I am unable to modify the palette.

请帮帮我.

有关我用来读取文件和更新调色板的代码,请参见下文.

See below for the code I am using to read the file and update the palette.

    public static Image GetPreviewImage()
    {
        Bitmap updatedImage = null;
        try
        {
            // Reads the colors as a byte array
            byte[] paletteBytes = FetchColorPallette();

            updatedImage = Image.FromFile(@"C:Screen-SaverBouncing.png");

            ColorPalette colorPalette = updatedImage.Palette;

            int j = 0;
            if (colorPalette.Entries.Length > 0)
            {
                for (int i = 0; i < paletteBytes.Length / 4; i++)
                {
                    Byte AValue = Convert.ToByte(paletteBytes[j]);
                    Byte RValue = Convert.ToByte(paletteBytes[j + 1]);
                    Byte GValue = Convert.ToByte(paletteBytes[j + 2]);
                    Byte BValue = Convert.ToByte(paletteBytes[j + 3]);
                    j += 4;

                    colorPalette.Entries[i] = Color.FromArgb(AValue, RValue, GValue, BValue);
                }
                updatedImage.Palette = colorPalette; ;
            }

            return updatedImage;
        }
        catch
        {
            throw;
        }
    }

推荐答案

我也遇到了这个问题,似乎任何包含透明度的调色 png 图像 都无法加载为被调色.Net 框架,尽管事实上 .Net 函数可以完美地编写这样的文件.相比之下,如果文件是gif格式就没有这个问题了.

I had this problem too, and it seems that any paletted png image that contains transparency can't be loaded as being paletted by the .Net framework, despite the fact the .Net functions can perfectly write such a file. In contrast, it has no problems with this if the file is in gif format.

png 透明度的工作原理是在标题中添加一个可选的tRNS"块,以指定每个调色板条目的 alpha..Net 类正确读取和应用它,所以我真的不明白他们为什么坚持将图像转换为 32 位之后.更重要的是,该错误总是发生在透明块存在时,即使它将所有颜色标记为完全不透明.

Transparency in png works by adding an optional "tRNS" chunk in the header, to specify the alpha of each palette entry. The .Net classes read and apply this correctly, so I don't really understand why they insist on converting the image to 32 bit afterwards. What's more, the bug always happens when the transparency chunk is present, even if it marks all colours as fully opaque.

png 格式的结构相当简单;在识别字节之后,每个块是内容大小的 4 个字节,然后是块 ID 的 4 个 ASCII 字符,然后是块内容本身,最后是一个 4 字节的块 CRC 值.

The structure of the png format is fairly simple; after the identifying bytes, each chunk is 4 bytes of the content size, then 4 ASCII characters for the chunk id, then the chunk content itself, and finally a 4-byte chunk CRC value.

鉴于这种结构,解决方案相当简单:

Given this structure, the solution is fairly simple:

  • 将文件读入字节数组.
  • 通过分析标题确保它是调色板 png 文件.
  • 通过从块头跳转到块头来找到tRNS"块.
  • 从块中读取 alpha 值.
  • 创建一个包含图像数据的新字节数组,但切掉tRNS"块.
  • 使用根据调整后的字节数据创建的 MemoryStream 创建 Bitmap 对象,从而生成正确的 8 位图像.
  • 使用提取的 alpha 数据修复调色板.
  • Read the file into a byte array.
  • Ensure it is a paletted png file by analysing the header.
  • Find the "tRNS" chunk by jumping from chunk header to chunk header.
  • Read the alpha values from the chunk.
  • Make a new byte array containing the image data, but with the "tRNS" chunk cut out.
  • Create the Bitmap object using a MemoryStream created from the adjusted byte data, resulting in the correct 8-bit image.
  • Fix the color palette using the extracted alpha data.

如果您正确地进行检查和回退,您可以使用此功能加载任何图像,并且如果它碰巧识别为带有透明度信息的调色板 png,它将执行修复.

If you do the checks and fallbacks right you can just load any image with this function, and if it happens to identify as paletted png with transparency info it'll perform the fix.

我的代码:

/// <summary>
/// Image loading toolset class which corrects the bug that prevents paletted PNG images with transparency from being loaded as paletted.
/// </summary>
public class BitmapLoader
{
    private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};

    /// <summary>
    /// Loads an image, checks if it is a PNG containing palette transparency, and if so, ensures it loads correctly.
    /// The theory can be found at http://www.libpng.org/pub/png/book/chapter08.html
    /// </summary>
    /// <param name="filename">Filename to load</param>
    /// <returns>The loaded image</returns>
    public static Bitmap LoadBitmap(String filename)
    {
        Byte[] data = File.ReadAllBytes(filename);
        return LoadBitmap(data);
    }

    /// <summary>
    /// Loads an image, checks if it is a PNG containing palette transparency, and if so, ensures it loads correctly.
    /// The theory can be found at http://www.libpng.org/pub/png/book/chapter08.html
    /// </summary>
    /// <param name="data">File data to load</param>
    /// <returns>The loaded image</returns>
    public static Bitmap LoadBitmap(Byte[] data)
    {
        Byte[] transparencyData = null;
        if (data.Length > PNG_IDENTIFIER.Length)
        {
            // Check if the image is a PNG.
            Byte[] compareData = new Byte[PNG_IDENTIFIER.Length];
            Array.Copy(data, compareData, PNG_IDENTIFIER.Length);
            if (PNG_IDENTIFIER.SequenceEqual(compareData))
            {
                // Check if it contains a palette.
                // I'm sure it can be looked up in the header somehow, but meh.
                Int32 plteOffset = FindChunk(data, "PLTE");
                if (plteOffset != -1)
                {
                    // Check if it contains a palette transparency chunk.
                    Int32 trnsOffset = FindChunk(data, "tRNS");
                    if (trnsOffset != -1)
                    {
                        // Get chunk
                        Int32 trnsLength = GetChunkDataLength(data, trnsOffset);
                        transparencyData = new Byte[trnsLength];
                        Array.Copy(data, trnsOffset + 8, transparencyData, 0, trnsLength);
                        // filter out the palette alpha chunk, make new data array
                        Byte[] data2 = new Byte[data.Length - (trnsLength + 12)];
                        Array.Copy(data, 0, data2, 0, trnsOffset);
                        Int32 trnsEnd = trnsOffset + trnsLength + 12;
                        Array.Copy(data, trnsEnd, data2, trnsOffset, data.Length - trnsEnd);
                        data = data2;
                    }
                }
            }
        }
        Bitmap loadedImage;
        using (MemoryStream ms = new MemoryStream(data))
        using (Bitmap tmp = new Bitmap(ms))
            loadedImage = ImageUtils.CloneImage(tmp);
        ColorPalette pal = loadedImage.Palette;
        if (pal.Entries.Length == 0 || transparencyData == null)
            return loadedImage;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
        {
            if (i >= transparencyData.Length)
                break;
            Color col = pal.Entries[i];
            pal.Entries[i] = Color.FromArgb(transparencyData[i], col.R, col.G, col.B);
        }
        loadedImage.Palette = pal;
        return loadedImage;
    }

    /// <summary>
    /// Finds the start of a png chunk. This assumes the image is already identified as PNG.
    /// It does not go over the first 8 bytes, but starts at the start of the header chunk.
    /// </summary>
    /// <param name="data">The bytes of the png image</param>
    /// <param name="chunkName">The name of the chunk to find.</param>
    /// <returns>The index of the start of the png chunk, or -1 if the chunk was not found.</returns>
    private static Int32 FindChunk(Byte[] data, String chunkName)
    {
        if (chunkName.Length != 4 )
            throw new ArgumentException("Chunk must be 4 characters!", "chunkName");
        Byte[] chunkNamebytes = Encoding.ASCII.GetBytes(chunkName);
        if (chunkNamebytes.Length != 4)
            throw new ArgumentException("Chunk must be 4 characters!", "chunkName");
        Int32 offset = PNG_IDENTIFIER.Length;
        Int32 end = data.Length;
        Byte[] testBytes = new Byte[4];
        // continue until either the end is reached, or there is not enough space behind it for reading a new chunk
        while (offset + 12 <= end)
        {
            Array.Copy(data, offset + 4, testBytes, 0, 4);
            // Alternative for more visual debugging:
            //String currentChunk = Encoding.ASCII.GetString(testBytes);
            //if (chunkName.Equals(currentChunk))
            //    return offset;
            if (chunkNamebytes.SequenceEqual(testBytes))
                return offset;
            Int32 chunkLength = GetChunkDataLength(data, offset);
            // chunk size + chunk header + chunk checksum = 12 bytes.
            offset += 12 + chunkLength;
        }
        return -1;
    }

    private static Int32 GetChunkDataLength(Byte[] data, Int32 offset)
    {
        if (offset + 4 > data.Length)
            throw new IndexOutOfRangeException("Bad chunk size in png image.");
        // Don't want to use BitConverter; then you have to check platform endianness and all that mess.
        Int32 length = data[offset + 3] + (data[offset + 2] << 8) + (data[offset + 1] << 16) + (data[offset] << 24);
        if (length < 0)
            throw new IndexOutOfRangeException("Bad chunk size in png image.");
        return length;
    }
}

提到的 ImageUtils.CloneImage 是,据我所知,加载位图并将其与任何支持资源(如文件或流)断开链接的唯一 100% 安全方法.可以在这里找到.

The mentioned ImageUtils.CloneImage is, as far as I know, the only 100% safe way of loading a bitmap and unlinking it from any backing resources like a file or stream. It can be found here.

或者,您可以直接从 MemoryStream 创建图像并保持 MemoryStream 打开.显然,对于引用简单数组而不是外部资源的流,尽管 IDisposable 流保持打开状态,这不会给垃圾收集带来任何问题.它有点不那么整洁干净,但要简单得多.创建 loadedImage 的代码就变成了:

Alternatively, you can just create the image from MemoryStream and leave the MemoryStream open. Apparently, for a stream that refers to a simple array rather than to an external resource, this gives no problems for garbage collection despite the IDisposable stream being left open. It's a bit less neat and clean, but a lot simpler. The code for the creation of loadedImage then simply becomes:

MemoryStream ms = new MemoryStream(data)
Bitmap loadedImage = new Bitmap(ms);

这篇关于如何仅将 8 位 PNG 图像读取为 8 位 PNG 图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆