Beispiel #1
-1
        private 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 = _bitmap.PixelWidth * _bitmap.PixelHeight * 4 + _bitmap.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.Reset();

            // 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 < _bitmap.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.Add( 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 < _bitmap.PixelWidth; x++ )
                {
                    // Data is in RGBA format but source may not be
                    pixel = BitConverter.GetBytes( _bitmap.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.Add( 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.CurrentValue );
            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 );
        }