使用BitmapSource,PngBitmapEncoder / Decoder时,在PNG中保存8位索引数据会改变原始字节

使用BitmapSource和PngBitmapEncoder / Decoder保存和加载PNG时遇到问题。 基本上,我希望能够保存一个源自字节数组的图像,当PNG加载到我的程序中时,重新加载完全相同的字节。 原始数据保存很重要。

同时,我希望PNG使用自定义调色板(256色的索引数组)。

我正在尝试使用自定义索引调色板保存我的8位数据。 原始数据范围为0-255。 调色板可以是“阈值”调色板(例如,0-20是颜色#1,21-50是颜色#2,等等)。

我发现的是,当我保存数据,重新加载并执行CopyPixels以检索“原始”数据时,数据值是根据调色板设置的,而不是原始字节数组值。

有没有办法在PNG中保留原始字节数组, 而不会丢失自定义调色板? 或者有没有不同的方法从BitmapSource检索字节数组?

以下是我的保存例程:

// This gets me a custom palette that is an array of 256 colors List colors = PaletteToolsWPF.TranslatePalette(this, false, true); BitmapPalette myPalette = new BitmapPalette(colors); // This retrieves my byte data as an array of dimensions _stride * sizeY byte[] ldata = GetData(); BitmapSource image = BitmapSource.Create( sizeX, sizeY, 96, 96, PixelFormats.Indexed8, myPalette, ldata, _stride); PngBitmapEncoder enc = new PngBitmapEncoder(); enc.Interlace = PngInterlaceOption.On; enc.Frames.Add(BitmapFrame.Create(image)); // save the data via FileStream enc.Save(fs); 

这是我的加载程序:

  // Create an array to hold the raw data localData = new byte[_stride * sizeY]; // Load the data via a FileStream PngBitmapDecoder pd = new PngBitmapDecoder(Fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); BitmapSource bitmapSource = pd.Frames[0]; // When I look at the byte data, it is *not* the same as my original data bitmapSource.CopyPixels(localData, _stride, 0); 

任何建议,将不胜感激。 谢谢。

附录#1:我发现问题的一部分是PNG被保存为32位颜色,尽管我将其设置为Indexed8并使用256条目调色板。 这似乎也取决于设置的调色板。 知道为什么吗?

我知道为什么你的PNG文件以高色保存。 实际上,它们不是 “8位图像保存为高色”; 问题是,从它们包含透明度的那一刻起,它们就被.Net框架加载为高色。 图像本身非常好,它只是弄乱它们的框架。

该解决方法发布在此处:

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

但Wil说,如果你想保留原始字节,那么调色板的实际更改应该使用块来完成,而不是通过.Net图形类来完成,因为.Net重新编码将不可避免地改变字节。 而且,有趣的是,我刚刚给出的调色板问题的修复已经包含了你需要的一半代码,即块读取代码。

编写代码是这样的:

 ///  /// Writes a png data chunk. ///  /// Target array to write into. /// Offset in the array to write the data to. /// 4-character chunk name. /// Data to write into the new chunk. /// The new offset after writing the new chunk. Always equal to the offset plus the length of chunk data plus 12. private static Int32 WritePngChunk(Byte[] target, Int32 offset, String chunkName, Byte[] chunkData) { if (offset + chunkData.Length + 12 > target.Length) throw new ArgumentException("Data does not fit in target array!", "chunkData"); 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 bytes!", "chunkName"); Int32 curLength; ArrayUtils.WriteIntToByteArray(target, offset, curLength = 4, false, (UInt32)chunkData.Length); offset += curLength; Int32 nameOffset = offset; Array.Copy(chunkNamebytes, 0, target, offset, curLength = 4); offset += curLength; Array.Copy(chunkData, 0, target, offset, curLength = chunkData.Length); offset += curLength; UInt32 crcval = Crc32.ComputeChecksum(target, nameOffset, chunkData.Length + 4); ArrayUtils.WriteIntToByteArray(target, offset, curLength = 4, false, crcval); offset += curLength; return offset; } 

Crc32.ComputeChecksum函数是Sanity Free Coding CRC实现的in-array自适应。 将它调整到给定数组内的变量start和length应该不难。

字节写入类ArrayUtils是我用来读取和写入具有指定字节序的数组的值的工具集。 它在这个答案的最后发布在SO上。