Saving 8-bit indexed data in a PNG alters original bytes when using BitmapSource, PngBitmapEncoder/Decoder

雨燕双飞 提交于 2019-11-29 07:45:16

I know why your PNG files are being saved in high colour. In fact, they are not "8-bit images saved as high colour"; the problem is that, from the moment they contain transparency, they're being loaded as high-colour by the .Net framework. The images themselves are perfectly fine, it's just the framework that's messing them up.

The workaround for that was posted here:

A: Loading an indexed color image file correctly

As wip said, though, if you want to preserve the original bytes, the actual changing of the palette should be done using chunks, rather than through .Net graphics classes, since .Net re-encoding will inevitably change the bytes. And, funnily enough, the fix for the palette problem I just gave already contains half of the code you need for that, namely, the chunk reading code.

The chunk writing code would be this:

/// <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;
}

The Crc32.ComputeChecksum function I used is an in-array adaption of the Sanity Free Coding CRC implementation. It shouldn't be hard to adapt it to a variable start and length inside the given array.

The byte writing class ArrayUtils is a toolset I made to read and write values to/from arrays with specified endianness. It is posted here on SO at the end of this answer.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!