using System.IO.Compression; using System.Security.Cryptography; namespace AvParser.ImgTestService; /// /// Renders a deterministic identicon PNG from an id, with a hand-written encoder. /// /// /// No image library and no native dependency on purpose: the container stays tiny and runs the same /// on any architecture. The same id always yields the same bytes, so a collector that fetches an id /// twice gets one blob, and a duplicate-detection test has something stable to assert against. /// public static class Identicon { private const int Cells = 5; private const int CellSize = 48; private const int Margin = 20; private const int Size = (Cells * CellSize) + (2 * Margin); /// Builds the PNG bytes for an id. public static byte[] Render(string id) { var hash = SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(id)); // A saturated foreground from the first bytes, on a near-white background. var (fr, fg, fb) = Foreground(hash); byte br = 0xF2, bg = 0xF2, bb = 0xF4; var pixels = new byte[Size * Size * 3]; FillBackground(pixels, br, bg, bb); // A 5x5 grid, mirrored left-to-right, so the icon reads as a single symmetric shape. Bit // source is the tail of the hash, one bit per cell in the left three columns. for (var col = 0; col < (Cells + 1) / 2; col++) { for (var row = 0; row < Cells; row++) { var bit = hash[(col * Cells) + row] & 1; if (bit == 0) { continue; } PaintCell(pixels, col, row, fr, fg, fb); PaintCell(pixels, Cells - 1 - col, row, fr, fg, fb); } } return Encode(pixels, Size, Size); } private static (byte R, byte G, byte B) Foreground(byte[] hash) { // Pick a hue-ish colour that is never too pale: keep at least one channel low and one high. var r = (byte)(60 + (hash[0] % 160)); var g = (byte)(60 + (hash[1] % 160)); var b = (byte)(60 + (hash[2] % 160)); return (r, g, b); } private static void FillBackground(byte[] pixels, byte r, byte g, byte b) { for (var i = 0; i < pixels.Length; i += 3) { pixels[i] = r; pixels[i + 1] = g; pixels[i + 2] = b; } } private static void PaintCell(byte[] pixels, int col, int row, byte r, byte g, byte b) { var x0 = Margin + (col * CellSize); var y0 = Margin + (row * CellSize); for (var y = y0; y < y0 + CellSize; y++) { var rowStart = y * Size * 3; for (var x = x0; x < x0 + CellSize; x++) { var i = rowStart + (x * 3); pixels[i] = r; pixels[i + 1] = g; pixels[i + 2] = b; } } } /// Encodes RGB pixels as a PNG (colour type 2, 8-bit). private static byte[] Encode(byte[] rgb, int width, int height) { using var output = new MemoryStream(); // Signature. output.Write([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); Span ihdr = stackalloc byte[13]; WriteBigEndian(ihdr[..4], (uint)width); WriteBigEndian(ihdr.Slice(4, 4), (uint)height); ihdr[8] = 8; // bit depth ihdr[9] = 2; // colour type: truecolour RGB ihdr[10] = 0; // compression ihdr[11] = 0; // filter ihdr[12] = 0; // interlace WriteChunk(output, "IHDR"u8, ihdr); // Raw scanlines: a leading filter byte (0 = none) then the row's RGB bytes. var stride = width * 3; var raw = new byte[height * (stride + 1)]; for (var y = 0; y < height; y++) { var src = y * stride; var dst = y * (stride + 1); raw[dst] = 0; Array.Copy(rgb, src, raw, dst + 1, stride); } using (var compressed = new MemoryStream()) { using (var zlib = new ZLibStream(compressed, CompressionLevel.Optimal, leaveOpen: true)) { zlib.Write(raw, 0, raw.Length); } WriteChunk(output, "IDAT"u8, compressed.ToArray()); } WriteChunk(output, "IEND"u8, []); return output.ToArray(); } private static void WriteChunk(Stream stream, ReadOnlySpan type, ReadOnlySpan data) { Span length = stackalloc byte[4]; WriteBigEndian(length, (uint)data.Length); stream.Write(length); stream.Write(type); stream.Write(data); var crc = Crc32.Compute(type, data); Span crcBytes = stackalloc byte[4]; WriteBigEndian(crcBytes, crc); stream.Write(crcBytes); } private static void WriteBigEndian(Span destination, uint value) { destination[0] = (byte)(value >> 24); destination[1] = (byte)(value >> 16); destination[2] = (byte)(value >> 8); destination[3] = (byte)value; } } /// Minimal CRC-32 (PNG polynomial), enough to seal chunks. internal static class Crc32 { private static readonly uint[] Table = BuildTable(); public static uint Compute(ReadOnlySpan type, ReadOnlySpan data) { var crc = 0xFFFFFFFFu; crc = Update(crc, type); crc = Update(crc, data); return crc ^ 0xFFFFFFFFu; } private static uint Update(uint crc, ReadOnlySpan bytes) { foreach (var b in bytes) { crc = Table[(crc ^ b) & 0xFF] ^ (crc >> 8); } return crc; } private static uint[] BuildTable() { var table = new uint[256]; for (var n = 0u; n < 256; n++) { var c = n; for (var k = 0; k < 8; k++) { c = (c & 1) != 0 ? 0xEDB88320u ^ (c >> 1) : c >> 1; } table[n] = c; } return table; } }