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;
}
}