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

查看:190
本文介绍了如何仅将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 创建位图对象,得到正确的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 header
        while (offset < end && offset + 8 < 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.

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

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