XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using ICSharpCode.SharpZipLib.Checksum;
using ICSharpCode.SharpZipLib.Zip.Compression;

namespace XFESystem.Browser;

/// <summary>Bounded PNG scanlines with managed DEFLATE, including Adam7 and palette transparency.
/// No platform zlib import is required. Color-management metadata and animation are not interpreted.</summary>
internal static class BrowserPngDecoder
{
    internal static bool HasSignature(ReadOnlySpan<byte> bytes) => bytes.Length >= 8 && bytes[..8].SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 });
    internal static BrowserCanvasSurface Decode(byte[] bytes, int maximumPixels)
    {
        if (!HasSignature(bytes) || bytes.Length > 16_777_216) throw new IOException("Invalid or oversized PNG input.");
        int width = 0, height = 0, depth = 0, colorType = 0, interlace = 0, chunks = 0, position = 8;
        bool header = false, data = false, dataEnded = false, ended = false;
        byte[] palette = [], transparency = [];
        using var compressed = new MemoryStream();
        while (position < bytes.Length)
        {
            if (++chunks > 4096 || bytes.Length - position < 12) throw new IOException("Invalid PNG chunk framing.");
            uint size = BinaryPrimitives.ReadUInt32BigEndian(bytes.AsSpan(position, 4));
            if (size > bytes.Length - position - 12) throw new IOException("Truncated PNG chunk.");
            int length = (int)size;
            string kind = System.Text.Encoding.ASCII.GetString(bytes, position + 4, 4);
            if (!kind.All(char.IsAsciiLetter)) throw new IOException("Invalid PNG chunk type.");
            var crc = new Crc32(); crc.Update(new ArraySegment<byte>(bytes, position + 4, length + 4));
            if ((uint)crc.Value != BinaryPrimitives.ReadUInt32BigEndian(bytes.AsSpan(position + 8 + length, 4))) throw new IOException("PNG chunk checksum mismatch.");
            var content = bytes.AsSpan(position + 8, length);
            if (!header && kind != "IHDR") throw new IOException("PNG header must be first.");
            if (data && kind != "IDAT") dataEnded = true;
            switch (kind)
            {
                case "IHDR":
                    if (header || length != 13) throw new IOException("Invalid PNG header.");
                    uint w = BinaryPrimitives.ReadUInt32BigEndian(content), h = BinaryPrimitives.ReadUInt32BigEndian(content[4..]);
                    if (w == 0 || h == 0 || w > 32768 || h > 32768 || (long)w * h > maximumPixels)
                        throw new BrowserLimitException("PNG dimensions exceed the decoded pixel budget.");
                    width = (int)w; height = (int)h; depth = content[8]; colorType = content[9]; interlace = content[12];
                    bool validDepth = colorType switch { 0 or 3 => depth is 1 or 2 or 4 or 8 || colorType == 0 && depth == 16,
                        2 or 4 or 6 => depth is 8 or 16, _ => false };
                    if (!validDepth || content[10] != 0 || content[11] != 0 || interlace > 1) throw new NotSupportedException("Unsupported PNG coding method.");
                    header = true; break;
                case "PLTE":
                    if (data || palette.Length != 0 || length == 0 || length > 768 || length % 3 != 0 || colorType is 0 or 4) throw new IOException("Invalid PNG palette.");
                    palette = content.ToArray(); break;
                case "tRNS":
                    if (data || transparency.Length != 0 || colorType is 4 or 6 ||
                        colorType == 0 && length != 2 || colorType == 2 && length != 6 ||
                        colorType == 3 && (palette.Length == 0 || length == 0 || length > palette.Length / 3)) throw new IOException("Invalid PNG transparency.");
                    transparency = content.ToArray(); break;
                case "IDAT":
                    if (dataEnded || colorType == 3 && palette.Length == 0) throw new IOException("Invalid PNG image-data ordering.");
                    compressed.Write(content); data = true; break;
                case "IEND":
                    if (!data || length != 0) throw new IOException("Invalid PNG end marker.");
                    ended = true; break;
                default:
                    if (char.IsAsciiLetterUpper(kind[0])) throw new NotSupportedException("Unsupported critical PNG chunk: " + kind);
                    break;
            }
            position += length + 12;
            if (ended) break;
        }
        if (!ended || position != bytes.Length) throw new IOException("PNG image data is incomplete.");
        if (colorType == 3 && palette.Length / 3 > 1 << depth) throw new IOException("PNG palette exceeds its bit depth.");
        var inflater = new Inflater(); inflater.SetInput(compressed.ToArray());
        var surface = new BrowserCanvasSurface(width, height);
        int channels = colorType switch { 2 => 3, 4 => 2, 6 => 4, _ => 1 };
        int[] startX = interlace == 0 ? [0] : [0, 4, 0, 2, 0, 1, 0];
        int[] startY = interlace == 0 ? [0] : [0, 0, 4, 0, 2, 0, 1];
        int[] stepX = interlace == 0 ? [1] : [8, 8, 4, 4, 2, 2, 1];
        int[] stepY = interlace == 0 ? [1] : [8, 8, 8, 4, 4, 2, 2];
        byte[] filterByte = new byte[1];
        for (int pass = 0; pass < startX.Length; pass++)
        {
            int columns = Math.Max(0, (width - startX[pass] + stepX[pass] - 1) / stepX[pass]);
            int rows = Math.Max(0, (height - startY[pass] + stepY[pass] - 1) / stepY[pass]);
            if (columns == 0 || rows == 0) continue;
            int rowLength = checked((columns * channels * depth + 7) / 8), distance = Math.Max(1, (channels * depth + 7) / 8);
            byte[] row = new byte[rowLength], previous = new byte[rowLength];
            for (int y = 0; y < rows; y++)
            {
                ReadExactly(inflater, filterByte); ReadExactly(inflater, row);
                int filter = filterByte[0]; if (filter > 4) throw new IOException("Invalid PNG row filter.");
                for (int index = 0; index < row.Length; index++)
                {
                    int left = index >= distance ? row[index - distance] : 0, above = previous[index], upperLeft = index >= distance ? previous[index - distance] : 0;
                    int predictor = filter switch { 1 => left, 2 => above, 3 => (left + above) / 2, 4 => Paeth(left, above, upperLeft), _ => 0 };
                    row[index] = unchecked((byte)(row[index] + predictor));
                }
                for (int x = 0; x < columns; x++)
                {
                    int first = Sample(row, x * channels, depth), red, green, blue, alpha = 255;
                    if (colorType == 3)
                    {
                        if (first >= palette.Length / 3) throw new IOException("PNG palette index is outside its palette.");
                        red = palette[first * 3]; green = palette[first * 3 + 1]; blue = palette[first * 3 + 2];
                        if (first < transparency.Length) alpha = transparency[first];
                    }
                    else if (colorType is 0 or 4)
                    {
                        red = green = blue = Expand(first, depth);
                        if (colorType == 4) alpha = Expand(Sample(row, x * channels + 1, depth), depth);
                        else if (transparency.Length == 2 && first == BinaryPrimitives.ReadUInt16BigEndian(transparency)) alpha = 0;
                    }
                    else
                    {
                        int g = Sample(row, x * channels + 1, depth), b = Sample(row, x * channels + 2, depth);
                        red = Expand(first, depth); green = Expand(g, depth); blue = Expand(b, depth);
                        if (colorType == 6) alpha = Expand(Sample(row, x * channels + 3, depth), depth);
                        else if (transparency.Length == 6 && first == BinaryPrimitives.ReadUInt16BigEndian(transparency) &&
                            g == BinaryPrimitives.ReadUInt16BigEndian(transparency.AsSpan(2)) && b == BinaryPrimitives.ReadUInt16BigEndian(transparency.AsSpan(4))) alpha = 0;
                    }
                    surface.PutPixel(startX[pass] + x * stepX[pass], startY[pass] + y * stepY[pass], (uint)(alpha << 24 | red << 16 | green << 8 | blue));
                }
                (row, previous) = (previous, row);
            }
        }
        if (inflater.Inflate(filterByte, 0, 1) != 0) throw new BrowserLimitException("PNG decompression exceeds the declared scanline size.");
        if (!inflater.IsFinished || inflater.RemainingInput != 0) throw new IOException("Invalid PNG compressed stream termination.");
        surface.Commit(); return surface;
    }
    private static void ReadExactly(Inflater inflater, byte[] output)
    {
        for (int offset = 0; offset < output.Length;)
        {
            int count = inflater.Inflate(output, offset, output.Length - offset);
            if (count == 0) throw new IOException("PNG compressed scanlines are truncated or require a dictionary.");
            offset += count;
        }
    }
    private static int Sample(byte[] row, int index, int depth) => depth == 8 ? row[index] : depth == 16
        ? row[index * 2] << 8 | row[index * 2 + 1] : row[index * depth / 8] >> (8 - depth - index * depth % 8) & (1 << depth) - 1;
    private static int Expand(int value, int depth) => depth == 8 ? value : (value * 255 + ((1 << depth) - 1) / 2) / ((1 << depth) - 1);
    private static int Paeth(int left, int above, int upperLeft)
    {
        int target = left + above - upperLeft, a = Math.Abs(target - left), b = Math.Abs(target - above), c = Math.Abs(target - upperLeft);
        return a <= b && a <= c ? left : b <= c ? above : upperLeft;
    }
}