private static void WriteDataChunksUncompressed() { // First setup some variables we're going to use later on so we can calculate how big of byte[] we need // to store the entire PNG file in so we only keep a single copy of the data in memory. // Figure out how much image data we're going to have: // H * W * (number of bytes in an ARGB value) + H to account for the filter byte in PNG files int dataLength = _image.PixelWidth * _image.PixelHeight * 4 + _image.PixelHeight; // Variables for the number of PNG blocks and how big the last block is going to be. int blockCount; int lastBlockSize; // We could have an exactly even count of blocks (ie MaxBlockSize * x), but that seems unlikely. // If we don't, then add one for the remainder of the data and figure out how much data will be // left. if ((dataLength % MaxBlockSize) == 0) { blockCount = dataLength / MaxBlockSize; lastBlockSize = MaxBlockSize; } else { blockCount = (dataLength / MaxBlockSize) + 1; lastBlockSize = dataLength - (MaxBlockSize * (blockCount - 1)); } // The size of the PNG file will be: // 2 header bytes + // ( blockCount - 1 ) * // ( // 1 last block byte + // 2 block size bytes + // 2 block size one's complement bytes + // maxBlockSize ) + // ( // 1 last block byte + // 2 block size bytes + // 2 block size one's complement bytes + // lastBlockSize ) + // 4 Adler32 bytes + // // = 2 + ((blockCount-1)*(5+MaxBlockSize)) + (5+lastBlockSize) + 4 // = 11 + ((blockCount-1)*(5+MaxBlockSize)) + lastBlockSize int pngLength; pngLength = 11 + ((blockCount - 1) * (5 + MaxBlockSize)) + lastBlockSize; // Make a buffer to store the PNG in. byte[] data = new byte[pngLength]; // Write zlib headers. data[0] = 0x78; data[1] = 0xDA; // zlib compression uses Adler32 CRCs instead of CRC32s, so setup on up to calculate. Adler32 crcCode = new Adler32(); crcCode.resetAdler(); // Setup some variables to use in the loop. var blockRemainder = 0; // How much of the current block we have left, 0 to start so we write the block header out on the first block. var currentBlock = 0; // The current block we're working on, start with 0 as we increment in the first pass thorugh. var dataPointer = 2; // A pointer to where we are in the data array, start at 2 as we 'wrote' two bytes a few lines ago. var pixelSource = 0; // The current pixel we're working on from the image. byte[] pixel = new byte[4]; // Temporary storage to store the current pixel in as a byte array. // This is the main logic loop, we're going to be doing a lot of work so stick with me... // The loop has three parts to it: // 1. looping through each row (y) // 2. looping through each pixel in the row (x) // 3. looping through each byte of the pixel (z) // Loop thorough each row in the image. for (int y = 0; y < _image.PixelHeight; y++) { // This code appears twice, once here and once in the pixel byte loop (loop 3). // It checks to see if we're at the boundry for the PNG block and if so writes // out a new block header. It get executed on the first time through to setup // the first block but is unlikly to get executed again as it would mean the // block boundry is at a row boundry, which seems unlikly. if (blockRemainder == 0) { // Setup a temporary byte array to store the block size in. byte[] tempBytes = new byte[2]; // Increment the current block count. currentBlock++; // Figure out the current block size and if we're at the last block, write // out and 1 to let the zlib decompressor know. By default, use the MaxBlockSize. int length = MaxBlockSize; if (currentBlock == blockCount) { length = lastBlockSize; data[dataPointer] = 0x01; } else { data[dataPointer] = 0x00; } // Each and every time we write something to the data array, increment the pointer. dataPointer++; // Write the block length out. tempBytes = BitConverter.GetBytes(length); data[dataPointer + 0] = tempBytes[0]; data[dataPointer + 1] = tempBytes[1]; dataPointer += 2; // Write one's compliment of length for error checking. tempBytes = BitConverter.GetBytes((ushort)~length); data[dataPointer + 0] = tempBytes[0]; data[dataPointer + 1] = tempBytes[1]; dataPointer += 2; // Reset the remaining block size to the next block's length. blockRemainder = length; } // Set the filter byte to 0, not really required as C# initalizes the byte array to 0 by default, but here for clarity. data[dataPointer] = 0; // Add the current byte to the running Adler32 value, note we ONLY add the filter byte and the pixel bytes to the // Adler32 CRC, all other header and block header bytes are execluded from the CRC. crcCode.addToAdler(data, 1, (uint)dataPointer); // Increment the data pointer and decrement the remain block value. dataPointer++; blockRemainder--; // Loop thorough each pixel in the row, you have to do this as the source format and destination format may be different. for (int x = 0; x < _image.PixelWidth; x++) { // Data is in RGBA format but source may not be pixel = BitConverter.GetBytes(_image.Pixels[pixelSource]); // Loop through the 4 bytes of the pixel and 'write' them to the data array. for (int z = 0; z < 4; z++) { // This is the second appearance of this code code. // It checks to see if we're at the boundry for the PNG block and if so writes // out a new block header. if (blockRemainder == 0) { // Setup a temporary byte array to store the block size in. byte[] tempBytes = new byte[2]; // Increment the current block count. currentBlock++; // Figure out the current block size and if we're at the last block, write // out and 1 to let the zlib decompressor know. By default, use the MaxBlockSize. int length = MaxBlockSize; if (currentBlock == blockCount) { length = lastBlockSize; data[dataPointer] = 0x01; } else { data[dataPointer] = 0x00; } // Each and every time we write something to the data array, increment the pointer. dataPointer++; // Write the block length out. tempBytes = BitConverter.GetBytes(length); data[dataPointer + 0] = tempBytes[0]; data[dataPointer + 1] = tempBytes[1]; dataPointer += 2; // Write one's compliment of length for error checking. tempBytes = BitConverter.GetBytes((ushort)~length); data[dataPointer + 0] = tempBytes[0]; data[dataPointer + 1] = tempBytes[1]; dataPointer += 2; // Reset the remaining block size to the next block's length. blockRemainder = length; } // Store the pixel's byte in to the data array. We use the WBByteOrder array to ensure // we have the write order of bytes to store in the PNG file. if (z != 3 && pixel[WBByteOrder[3]] != 0 && pixel[WBByteOrder[3]] != 255) { // Calculate unmultiplied pixel value from premultiplied value (Windows Phone always uses premultiplied ARGB32) data[dataPointer] = (byte)((255 * pixel[WBByteOrder[z]]) / pixel[WBByteOrder[3]]); } else { // Alpha channel or no need to unpremultiply data[dataPointer] = pixel[WBByteOrder[z]]; } // Add the current byte to the running Adler32 value, note we ONLY add the filter byte and the pixel bytes to the // Adler32 CRC, all other header and block header bytes are execluded from the CRC. crcCode.addToAdler(data, 1, (uint)dataPointer); // Increment the data pointer and decrement the remain block value. dataPointer++; blockRemainder--; } // Increment where we start writting the next pixel and where we get the next pixel from. pixelSource++; } } // Whew, wipe that brow, we're done all the complex bits now! // Write the Adler32 CRC out, but reverse the order of the bytes to match the zlib spec. pixel = BitConverter.GetBytes(crcCode.adler()); data[dataPointer + 0] = pixel[3]; data[dataPointer + 1] = pixel[2]; data[dataPointer + 2] = pixel[1]; data[dataPointer + 3] = pixel[0]; // Yes, yes, I know I said "Each and every time we write something to the data array, increment the pointer." // but we're done with it now so I'm not going to bother ;) // Write the entire PNG data chunk out to the file stream. WriteChunk(PngChunkTypes.Data, data, 0, pngLength); }