如何仅将8位PNG图像作为8位PNG图像读取?

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

请帮我。

请参阅下面的代码,我将用于读取文件和更新调色板。

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格式,它没有问题。

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

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

鉴于这种结构,解决方案非常简单:

  • 将文件读入字节数组。
  • 通过分析标题确保它是一个调色板的png文件。
  • 通过从块头跳转到块头来查找“tRNS”块。
  • 从块中读取alpha值。
  • 创建一个包含图像数据的新字节数组,但切掉“tRNS”块。
  • 使用从调整后的字节数据创建的MemoryStream创建Bitmap对象,从而生成正确的8位图像。
  • 使用提取的alpha数据修复调色板。

如果您执行检查和后退,您可以使用此function加载任何图像,如果它恰好标识为具有透明度信息的调色板png,它将执行修复。

我的代码:

 ///  /// Image loading toolset class which corrects the bug that prevents paletted PNG images with transparency from being loaded as paletted. ///  public class BitmapLoader { private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; ///  /// 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 ///  /// Filename to load /// The loaded image public static Bitmap LoadBitmap(String filename) { Byte[] data = File.ReadAllBytes(filename); return LoadBitmap(data); } ///  /// 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 ///  /// File data to load /// The loaded image 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; } ///  /// 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. ///  /// The bytes of the png image /// The name of the chunk to find. /// The index of the start of the png chunk, or -1 if the chunk was not found. 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%安全方式。 在这里能找到它。