gsichtl
Deterministic pixel-art avatars from any seed. Gsichtl is Bavarian for "little face".


The seed is any byte string, typically a public user or group id. The same
seed always gives the same picture, so every client draws it locally and
nothing has to be stored or sent. The crate does no I/O, is
#![forbid(unsafe_code)] and has one dependency, blake3, plus png behind
an optional feature.
Generators
gsichtl::monster(seed): an 8x8 monster with antennae, horns or ears, big eyes and a mouth. Up to 324,000 monsters: 4 backgrounds, 10 bodies, 6 accents, 6 tops, 3 silhouettes, 3 brows, 5 eyes and 5 mouths.gsichtl::nerd(seed): an 8x8 human face with hair, glasses or goggles and sometimes headphones. Up to 1,327,104 nerds: 4 backgrounds, 6 skins, 8 hair colors, 3 frames, 6 accents, 2 heads, 6 hair styles, 4 eyes, 4 mouths and with or without headphones.gsichtl::face(seed): a monster or a nerd, chosen by the seed (about half each). It is not necessarilymonster(seed)ornerd(seed).gsichtl::crest(seed): a 10x10 shield with a division and an emblem, for groups. 28,800 crests: 4 backgrounds, 6 fields, 5 second fields, 2 metals, 3 shields, 5 divisions and 8 emblems.gsichtl::badge(seed): a 12x12 round badge (disc, octagon or cog) with a division, an emblem and sometimes a metal rim, for groups. 57,600 badges: 4 backgrounds, 6 fields, 5 second fields, 2 metals, 3 shapes, with or without a rim, 5 divisions and 8 emblems.gsichtl::realm(seed): a 20x20 small world for groups: a floating island, a planet or a floating patch of sea, with a landmark, a sky detail and sometimes a waterfall or a ring. Made to stand free, rendered withto_rgba_transparentorto_png_transparent. 34,560 realms: 4 backgrounds, 3 shapes (an island counts twice in the draw), 6 biomes, 5 landmarks per shape, 6 accents, with or without a waterfall or ring, and 4 sky details.gsichtl::retro(seed): a 20x20 retro game sprite for groups: a creature, a weapon, an item or a piece of old tech, palette-swapped in neon colors, sometimes in front of an explosion, a blood splatter or sparkles. Original designs in the style of classic games. Made to stand free like a realm. 21,504 retros: 4 backgrounds, 24 motifs, 8 colors, 7 second colors and 4 effects.gsichtl::icon(seed): a realm or a retro sprite, chosen by the seed (about half each), the group counterpart offace. It is not necessarilyrealm(seed)orretro(seed).
The face counts are upper bounds: where no part uses the accent color, two faces that differ only in their accent look the same.
Installation
[]
= "0.1"
For PNG output, turn on the png feature:
[]
= { = "0.1", = ["png"] }
Usage
Render to RGBA8:
let avatar = face;
// 12 px per cell, 16 px of background around the grid: 128 x 128 RGBA8.
let image = avatar.to_rgba;
assert_eq!;
Draw the cells yourself:
let a = crest;
for y in 0..a.side
Encode as PNG, with the png feature:
let png = face.to_png?;
write?;
An image is side * cell_px + 2 * margin_px pixels wide and high, at most
3060. cell_px == 0 gives a plain square of background.
to_rgba and to_png are opaque. to_rgba_transparent and
to_png_transparent render the same picture, but leave the background and
the margin fully transparent, so the avatar stands free on your own surface:
let image = crest.to_rgba_transparent;
assert_eq!;
How it works
The seed goes through BLAKE3 in derive-key mode, with one context string per
generator: gsichtl 2026-10-02 face v1, gsichtl 2026-10-02 monster v1,
gsichtl 2026-10-02 nerd v1, gsichtl 2026-10-02 crest v1,
gsichtl 2026-10-03 badge v1, gsichtl 2026-10-03 realm v1,
gsichtl 2026-10-03 retro v1 and gsichtl 2026-10-03 icon v1. The
extendable output is the stream of draws:
each draw takes 4 bytes, modulo the number of choices. The parts are small
character grids, painted in a fixed order. Eyes, brows and mouths are clipped
to the painted body, so they never leave the silhouette.
BLAKE3 serves as a deterministic mixer here, not for security. Its output is
fixed by its specification, so updating the blake3 crate does not change a
picture.
Sample sheets
writes face-NN.png from the seed user-N, and crest-NN.png,
badge-NN.png, realm-NN.png and retro-NN.png from the seed community-N
into <dir>. Faces and badges have 16 px cells and an 8 px margin. Crests
have 18 px cells and a 14 px margin; realms and retro sprites have 10 px cells
and a 4 px margin on a transparent background, so all of them are 208 px wide
like a badge. The sheets above show 32 faces, and 16 crests above 16 badges
above 16 realms above 16 retro sprites:
Stability
The same seed gives the same avatar within a 0.x minor version. Changing a
part, a palette, the draw order or a context string is a breaking change and
bumps the minor version, because everyone who draws an avatar for the same id
has to see the same picture. tests/gsichtl.rs pins fingerprints of known
seeds.
Not an identity check
Anyone can upload a picture that looks like another person's generated face. A generated avatar is a decoration; never present it as verification of who someone is.
Minimum Rust version
Rust 1.91, edition 2024.
License
MIT, see LICENSE.