/// <summary> /// The make image header chunk. /// </summary> private void MakeImageHeaderChunk() { this.m_ihdrChunk = new PngChunk(PngChunkIhdr); var ihdrData = new byte[13]; NetworkOrderBitConverter.GetBytes((uint)this.m_imageWidth, ihdrData, 0); NetworkOrderBitConverter.GetBytes((uint)this.m_imageHeight, ihdrData, 4); ihdrData[8] = 8; // Bits per channel ihdrData[9] = 2; // Colour model - RGB this.m_ihdrChunk.SetData(ihdrData); }
/// <summary> /// Set the pixel data from which to generate this PNG as Color values. /// </summary> /// <param name="pixels"> /// And array of Color values. This is assumed to contain /// Width*Height pixels, where Width and Height are the values passed in at /// construction. /// </param> public void SetPixelColorData(Color[] pixels) { // ADLER32 counters // This turns out to be relative expensive, which is a PITA, since it's pretty much // pointless in a PNG. This is something PNG inherits as a result of using the ZLIB // spec. In ZLIB it serves a purpose. But in PNG, all chunks have a CRC32, so the // data ends up being protected by both a CRC32 and an ADLER32. Last time I measured, // the CRC32 was taking about 20% of the CPU time, and 50% is spent in the loop // right here. // Tauntingly, if you just crop the pixel data chunk so it's too short to contain the // ADLER32, neither Silverlight nor WPF complain. (Not when I wrote this anyway.) And // it speeds things up a fair bit. But I don't want to depend on that. uint s1 = 1; uint s2 = 0; int adlerCount = 0; for (int row = 0; row < this.m_imageHeight; ++row) { int rowTargetOffset = row * (this.m_imageWidth * 3 + 1); int rowSourceOffset = row * this.m_imageWidth; double rowRatio = row / (double)this.m_imageHeight; // Each row starts with a 0, indicating the non-transforming filter - // this lets us write raw pixel data. // We don't write the value out - the image size is fixed, so these things // never move in the array, and they always keep their initial 0 value. But // We do need to accumulate it for the ADLER32 check s2 = s2 + s1; // No need to add to s1 - this is a zero byte value. adlerCount += 1; int pos = rowTargetOffset + 1; for (int column = 0; column < this.m_imageWidth; ++column) { Color col = pixels[rowSourceOffset + column]; // Nasty duplicated code. But when I had it all nicely factored // out, it was causing serious slowdowns (order of 20%), and we're // inside the critical loop here. // Note, the weird calculation was originally factored out into // MapPixelDataOffset. Its job is to deal with the fact that even // when we disable compression, we still have to insert a 5 byte // deflate block header every 0xffff bytes. byte v = col.R; this.pngPixelData[pos + (pos / 0xffff) * 5 + 7] = v; pos += 1; s1 = s1 + v; s2 = s2 + s1; v = col.G; this.pngPixelData[pos + (pos / 0xffff) * 5 + 7] = v; pos += 1; s1 = s1 + v; s2 = s2 + s1; v = col.B; this.pngPixelData[pos + (pos / 0xffff) * 5 + 7] = v; pos += 1; s1 = s1 + v; s2 = s2 + s1; // Do we need to reduce yet? adlerCount += 3; if (adlerCount > 5546) { s1 %= 65521; s2 %= 65521; adlerCount = 0; } } } s1 %= 65521; s2 %= 65521; uint adler32 = (s2 << 16) + s1; NetworkOrderBitConverter.GetBytes(adler32, this.pngPixelData, this.pngPixelData.Length - 4); this.m_idatChunk.SetData(this.pngPixelData); }