Pixel Encoding
Etherean pixel art uses a compact 2-bit-per-pixel encoding that stores a 40x40 image in exactly 400 bytes. This applies to both avatars (Body) and soul generative art.
Format
- Grid — 40x40 = 1,600 pixels
- Colors — 4 per image (2 bits per pixel)
- Packing — 4 pixels per byte, big-endian (most significant bits first)
- Size — 1,600 pixels x 2 bits = 3,200 bits = 400 bytes
Bit Layout
Byte N contains pixels [N*4, N*4+1, N*4+2, N*4+3]
Bits: [7:6] = pixel N*4
[5:4] = pixel N*4+1
[3:2] = pixel N*4+2
[1:0] = pixel N*4+3
Pixel index 0 = color at bits [7:6] of byte 0
Pixel index 1599 = color at bits [1:0] of byte 399Decoding
const pixelIndex = y * 40 + x;
const byteIndex = Math.floor(pixelIndex / 4);
const bitShift = (3 - (pixelIndex % 4)) * 2;
const colorIndex = (bytes[byteIndex] >> bitShift) & 0x03;Palettes
Color index 0-3 maps to the 4 colors in the chosen palette. Palettes are defined as arrays of 4 hex color strings. Soul generative art inherits the parent Etherean's palette.
Rendering
Client-side rendering uses canvas.putImageData() with an RGBA buffer:
// Decode 400 bytes -> 6400 bytes RGBA
const rgba = new Uint8ClampedArray(1600 * 4);
for (let i = 0; i < 1600; i++) {
const colorIndex = decode2bpp(bytes, i);
const [r, g, b] = palette[colorIndex];
rgba[i * 4] = r;
rgba[i * 4 + 1] = g;
rgba[i * 4 + 2] = b;
rgba[i * 4 + 3] = 255;
}
ctx.putImageData(new ImageData(rgba, 40, 40), 0, 0);Display at larger sizes uses CSS image-rendering: pixelated to preserve the crisp pixel art look. Export resolution is 10x (400x400px).
Onchain SVG
The contract's tokenURI() generates an SVG with one <rect> per pixel. This is 1,600 rect elements in a 40x40 viewBox. The SVG is base64-encoded and embedded in the JSON metadata. No external resources needed.