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

C# 使用BitmapSource、PngBitmapEncoder/Decoder时,将8位索引数据保存在PNG中会改变原始字节,c#,png,bytearray,bitmapsource,C#,Png,Bytearray,Bitmapsource,使用BitmapSource和PngBitmapEncoder/Decoder保存和加载PNG时出现问题。基本上,我希望能够保存一个源于字节数组的图像,当PNG加载到我的程序中时,重新加载完全相同的字节。原始数据保存很重要 同时,我希望PNG使用自定义调色板(256色的索引数组) 我正试图用自定义索引调色板保存我的8位数据。原始数据的范围为0-255。调色板可以是“阈值”调色板(例如,0-20表示颜色1,21-50表示颜色2等) 我发现,当我保存数据、重新加载并执行CopyPixels以检索“原

使用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<System.Windows.Media.Color> 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);
如有任何建议,将不胜感激。谢谢


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

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

解决方法发布在此处:

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

区块编写代码如下所示:

/// <summary>
/// Writes a png data chunk.
/// </summary>
/// <param name="target">Target array to write into.</param>
/// <param name="offset">Offset in the array to write the data to.</param>
/// <param name="chunkName">4-character chunk name.</param>
/// <param name="chunkData">Data to write into the new chunk.</param>
/// <returns>The new offset after writing the new chunk. Always equal to the offset plus the length of chunk data plus 12.</returns>
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;
}
//
///写入png数据块。
/// 
///要写入的目标数组。
///要将数据写入的数组中的偏移量。
///4个字符的块名称。
///要写入新块的数据。
///写入新块后的新偏移量。始终等于偏移量加上区块数据长度加上12。
私有静态Int32 WritePngChunk(字节[]目标,Int32偏移量,字符串chunkName,字节[]chunkData)
{
if(偏移量+chunkData.Length+12>目标.Length)
抛出新ArgumentException(“数据不适合目标数组!”,“chunkData”);
if(chunkName.Length!=4)
抛出新ArgumentException(“Chunk必须是4个字符!”,“chunkName”);
Byte[]chunkNamebytes=Encoding.ASCII.GetBytes(chunkName);
if(chunkNamebytes.Length!=4)
抛出新ArgumentException(“Chunk必须是4字节!”,“chunkName”);
Int32卷曲长度;
ArrayUtils.WriteIntToByteArray(目标,偏移量,curLength=4,false,(UInt32)chunkData.Length);
偏移量+=卷曲长度;
Int32名称偏移=偏移量;
Copy(chunkNamebytes,0,target,offset,curLength=4);
偏移量+=卷曲长度;
Copy(chunkData,0,目标,偏移量,curLength=chunkData.Length);
偏移量+=卷曲长度;
UInt32 crcval=Crc32.ComputeChecksum(目标、名称偏移、chunkData.Length+4);
ArrayUtils.WriteIntToByteArray(目标,偏移量,卷曲长度=4,假,crcval);
偏移量+=卷曲长度;
返回偏移量;
}
我使用的
Crc32.ComputeChecksum
函数是的数组内自适应。要使它适应给定数组中可变的开始和长度应该不难


字节写入类ArrayUtils是我制作的一个工具集,用于在具有指定endianness的数组中读取和写入值。这是在这里发布的,所以在结尾。

请用相关语言(C#)标记?我不太熟悉PngBitmapEncoder,但是:提供了对png解码/编码的高度控制,也许您会发现它很有用。您可以尝试逐字节创建png文件。调色板信息必须保存在PLTE块中,透明度值必须保存在tRNS块中。有关更多详细信息,请参阅。
/// <summary>
/// Writes a png data chunk.
/// </summary>
/// <param name="target">Target array to write into.</param>
/// <param name="offset">Offset in the array to write the data to.</param>
/// <param name="chunkName">4-character chunk name.</param>
/// <param name="chunkData">Data to write into the new chunk.</param>
/// <returns>The new offset after writing the new chunk. Always equal to the offset plus the length of chunk data plus 12.</returns>
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;
}