using System.Buffers.Binary;
using System.IO;
using System.Collections.Generic;
using StbImageSharp;
namespace codelanguage;
/// Signature-based, platform-independent decoding for supported runtime icon rasters.
internal static class RuntimeRasterDecoder
{
public static bool TryDecodeGifFrames(byte[] data, out int width, out int height,
out List frames, out string error)
{
width = height = 0; frames = new List(); error = "";
if (data.Length < 6 || !(data.AsSpan(0, 6).SequenceEqual("GIF87a"u8)
|| data.AsSpan(0, 6).SequenceEqual("GIF89a"u8)))
{ error = "Invalid GIF signature."; return false; }
try
{
using var stream = new MemoryStream(data, writable: false);
long totalBytes = 0;
foreach (var frame in ImageResult.AnimatedGifFramesFromStream(stream, ColorComponents.RedGreenBlueAlpha))
{
if (frame.Width is < 1 or > 4096 || frame.Height is < 1 or > 4096
|| frame.Data.Length != checked(frame.Width * frame.Height * 4))
{ error = "GIF frame dimensions are outside supported bounds."; return false; }
if (frames.Count == 0) { width = frame.Width; height = frame.Height; }
if (frame.Width != width || frame.Height != height)
{ error = "GIF decoder returned inconsistent frame dimensions."; return false; }
totalBytes += frame.Data.Length;
if (totalBytes > 64L * 1024 * 1024 || frames.Count >= 1024)
{ error = "GIF animation exceeds runtime frame or memory limits."; frames.Clear(); width = height = 0; return false; }
// The decoder may reuse its backing buffer when advancing the GIF enumerator.
// Retain a snapshot per frame so later reads cannot all become the last frame.
frames.Add(frame.Data.ToArray());
}
if (frames.Count == 0) { error = "GIF contains no decoded frames."; return false; }
return true;
}
catch (Exception ex)
{ frames.Clear(); width = height = 0; error = $"GIF decoding failed: {ex.Message}"; return false; }
}
public static bool TryDecode(byte[] data, out int width, out int height, out byte[] rgba, out string error)
{
if (data.Length >= 8 && data.AsSpan(0, 8).SequenceEqual(new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }))
return PngPixelDecoder.TryDecode(data, out width, out height, out rgba, out error);
if (data.Length >= 2 && data[0] == (byte)'B' && data[1] == (byte)'M')
return TryDecodeBmp(data, out width, out height, out rgba, out error)
|| TryDecodeWithStb(data, out width, out height, out rgba, out error);
bool jpeg = data.Length >= 2 && data[0] == 0xFF && data[1] == 0xD8;
bool gif = data.Length >= 6 && (data.AsSpan(0, 6).SequenceEqual("GIF87a"u8) || data.AsSpan(0, 6).SequenceEqual("GIF89a"u8));
if (!jpeg && !gif)
{ width = height = 0; rgba = Array.Empty(); error = "Unsupported raster format; expected PNG, BMP, JPEG, or GIF."; return false; }
return TryDecodeWithStb(data, out width, out height, out rgba, out error);
}
private static bool TryDecodeWithStb(byte[] data, out int width, out int height, out byte[] rgba, out string error)
{
try
{
var image = ImageResult.FromMemory(data, ColorComponents.RedGreenBlueAlpha);
if (image.Width is < 1 or > 4096 || image.Height is < 1 or > 4096
|| image.Data.Length != checked(image.Width * image.Height * 4))
{ width = height = 0; rgba = Array.Empty(); error = "Decoded raster dimensions are outside supported bounds."; return false; }
width = image.Width; height = image.Height; rgba = image.Data; error = "";
return true;
}
catch (Exception ex)
{ width = height = 0; rgba = Array.Empty(); error = $"Raster decoding failed: {ex.Message}"; return false; }
}
private static bool TryDecodeBmp(byte[] data, out int width, out int height, out byte[] rgba, out string error)
{
width = height = 0; rgba = Array.Empty(); error = "";
if (data.Length < 54) { error = "BMP header is truncated."; return false; }
uint pixelOffset = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(10, 4));
uint dibSize = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(14, 4));
if (dibSize < 40 || 14L + dibSize > data.Length) { error = "Unsupported or truncated BMP DIB header."; return false; }
int signedWidth = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(18, 4));
int signedHeight = BinaryPrimitives.ReadInt32LittleEndian(data.AsSpan(22, 4));
ushort planes = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(26, 2));
ushort bits = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(28, 2));
uint compression = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(30, 4));
if (signedWidth is < 1 or > 4096 || signedHeight is 0 or < -4096 or > 4096 || signedHeight == int.MinValue)
{ error = "BMP dimensions are outside supported bounds."; return false; }
if (planes != 1 || compression != 0 || bits is not (8 or 24 or 32))
{ error = $"Unsupported BMP layout: planes={planes}, depth={bits}, compression={compression}."; return false; }
width = signedWidth; height = Math.Abs(signedHeight);
int bytesPerPixel = bits / 8;
int stride;
try { stride = checked((width * bytesPerPixel + 3) & ~3); }
catch (OverflowException) { error = "BMP row size exceeds supported bounds."; return false; }
if ((long)pixelOffset + (long)stride * height > data.Length)
{ error = "BMP pixel data is truncated."; width = height = 0; return false; }
int paletteStart = checked(14 + (int)dibSize);
int paletteCount = bits == 8 ? (int)BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(46, 4)) : 0;
if (bits == 8 && paletteCount == 0) paletteCount = 256;
if (paletteCount > 256 || bits == 8 && (paletteStart + (long)paletteCount * 4 > pixelOffset || pixelOffset > data.Length))
{ error = "BMP color table is invalid or truncated."; width = height = 0; return false; }
rgba = new byte[checked(width * height * 4)];
for (int y = 0; y < height; y++)
{
int sourceY = signedHeight > 0 ? height - 1 - y : y;
int row = checked((int)pixelOffset + sourceY * stride);
for (int x = 0; x < width; x++)
{
int dst = (y * width + x) * 4;
if (bits == 8)
{
int entry = paletteStart + data[row + x] * 4;
if (entry + 4 > pixelOffset) { error = "BMP pixel references an invalid palette entry."; rgba = Array.Empty(); return false; }
rgba[dst] = data[entry + 2]; rgba[dst + 1] = data[entry + 1]; rgba[dst + 2] = data[entry]; rgba[dst + 3] = 255;
}
else
{
int src = row + x * bytesPerPixel;
rgba[dst] = data[src + 2]; rgba[dst + 1] = data[src + 1]; rgba[dst + 2] = data[src];
rgba[dst + 3] = bits == 24 || compression == 0 ? (byte)255 : data[src + 3];
}
}
}
return true;
}
}