Ejemplo n.º 1
0
        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);
            }
        }