private static int WriteIDATChunk(Span <byte> png, Span <byte> zlib, int width, Span <ushort> blocks, Rgba32 backgroundColor, Span <Rgba32> palette, int scale) { // come back to the length of the chunk later var chunkLength = png.Slice(0, 4); const uint textIdat = 0x49444154; // "IDAT" var couldWriteIdatText = BinaryPrimitives.TryWriteUInt32BigEndian(png.Slice(4, sizeof(uint)), textIdat); Debug.Assert(couldWriteIdatText); // now, write the raw PNG data into the *zlib* section, and from // there, compress from the zlib section to the PNG section. // // it may seem backwards to write the PNG data not to the png // section, but we'd have to end up copying the data in the zlib // section back to the png section. // // it's really a matter of bad naming var offset = 0; using (var _ = DebugTimings.Start("Draw Pixels")) { if (scale == 1) { // typically, scale will be 1. this is a routine specifically // optimized for that for (var i = 0; i < blocks.Length; i++) { // for every scanline, we need to define the filter method if (i % width == 0) { // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IDAT // (Note that with filter method 0, the only one currently // defined, this implies prepending a filter-type byte to // each scanline.) zlib[offset++] = 0; } var block = blocks[i]; // TODO: use uint magic or something var color = GetColor(block, palette); var slice = zlib.Slice(offset, sizeof(uint)); slice[0] = color.R; slice[1] = color.G; slice[2] = color.B; slice[3] = color.A; offset += 4; } } else { // for non one scale things, we apply the following optimizations: // // - read in every color once, and write it twice // - once we write a row, we copy what we just wrote over // // in contrast to just a for loop for each block color & height for // the scale, this is much more efficient // RGBA pixels v v scanline var rowLength = (width * scale) * 4 + 1; var lastScanlinePosition = -1; // we'll always do the action once first // then we'll need to copy it scale minus one (one because we already did it) times var copies = scale - 1; for (var i = 0; i < blocks.Length; i++) { if (i % width == 0) { // if we haven't set the scanline position if (lastScanlinePosition != -1) { // there was data before this row // let's copy out the data, and paste it as many times // as the scale calls for var row = zlib.Slice(lastScanlinePosition, rowLength); // for as many times as we need to scale for (int j = 0; j < copies; j++) { row.CopyTo(zlib.Slice(offset, rowLength)); offset += rowLength; } } lastScanlinePosition = offset; zlib[offset++] = 0; } var block = blocks[i]; // TODO: use uint magic or something var color = GetColor(block, palette); var slice = zlib.Slice(offset, sizeof(uint)); slice[0] = color.R; slice[1] = color.G; slice[2] = color.B; slice[3] = color.A; offset += 4; for (int j = 0; j < copies; j++) { slice.CopyTo(zlib.Slice(offset, sizeof(uint))); offset += 4; } } // copied and pasted the code to paste the rows here // when the above loop finishes going through every block, it still // has to copy over the last scanline `copies` times { // there was data before this row // let's copy out the data, and paste it as many times // as the scale calls for var row = zlib.Slice(lastScanlinePosition, rowLength); // for as many times as we need to scale for (int j = 0; j < copies; j++) { row.CopyTo(zlib.Slice(offset, rowLength)); offset += rowLength; } } } } // we should've written to the entirety of zlib. // if zlib doesn't perfectly match our length, we've miscalculated // the amount of bytes to allocate. Debug.Assert(offset == zlib.Length); zlib = zlib.Slice(0, offset); // DEBUG int written; using (var _ = DebugTimings.Start("Zlib compression")) { written = Zlib.Compress(png.Slice(8), zlib); } // crc32 from the name to the end uint crc32; using (var _ = DebugTimings.Start("Crc32 Computation")) { crc32 = Crc32.Compute(png.Slice(4, 4 + written)); } var couldWriteCrc32 = BinaryPrimitives.TryWriteUInt32BigEndian(png.Slice(8 + written, 4), crc32); Debug.Assert(couldWriteCrc32); var couldWriteChunkLength = BinaryPrimitives.TryWriteUInt32BigEndian(chunkLength, (uint)written); Debug.Assert(couldWriteChunkLength); return(8 + written + 4); Rgba32 GetColor(ushort block, Span <Rgba32> palette) { if (block >= palette.Length) { return(backgroundColor); } var color = palette[block]; // if it's default(Rgba32), that means it doesn't have a color if (Rgba32.ToUInt32(ref color) == 0) { return(backgroundColor); } return(color); } }