I have a problem when saving and loading a PNG using BitmapSource and PngBitmapEncoder/Decoder. Basically, I\'d like to be able to save an image that originated as an array
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.