feat(avatar): add SVG, PNG and HDR avatar exports

This commit is contained in:
2026-10-18 18:48:49 +08:00
parent 61d039beba
commit 899df27427
10 changed files with 1331 additions and 324 deletions
+142 -7
View File
@@ -1,10 +1,145 @@
Generate avatar
===
This repo is a collection of codes that generate avatars.
# Matrix avatar
# "The Matrix"-like avatar
`cmatrix_avatar.py` generating avatars imitate "The Matrix".
`cmatrix_avatar.py` generates a square avatar directly, with the original
green character rain, yellow G/W/B highlights, and blue block-letter GWB.
It writes both an outlined SVG and a PNG; terminal screenshots are no longer needed.
## Run
Create the environment in this directory with [uv](https://docs.astral.sh/uv/):
```shell
python cmatrix_avatar.py
```
uv venv
uv sync
uv run python cmatrix_avatar.py
```
The default outputs are `avatar.svg` and `avatar.png` (2048 × 2048).
The default background grid is 43 rows × 77 columns. The original six-line GWB
is rendered as a separate vector layer, uniformly scaled to 72% of the image
width (roughly 2.1 times the background cell size) and centered.
The complete background grid fits inside the square with a safety margin.
Whole background cells intersecting the central logo area are left empty;
all remaining cells are preserved without cropping. The reserved area includes
a quarter-cell gap, and actual glyph bounds are checked to avoid sliced letters.
Colors are fixed, independent of terminal themes.
Rain brightness uses 32 nonlinear levels, from 0.5% to 100% of its peak.
Green and yellow characters share the logo's peak luminance. The SDR logo stays
exactly its original blue (#3b73fa); rain peaks are reduced to its luminance.
HDR scales linear RGB without adding white, preserving the original chromaticity.
The stronger gradient remains; this is not a pixel-identical restoration of the
older rain palette. Shape, size, and placement are unchanged.
```shell
# Reproduce a background and also generate small previews.
uv run python cmatrix_avatar.py --seed 42 --previews
# Also write a true 16-bit HDR PNG with a shared 400-nit luminance peak.
uv run python cmatrix_avatar.py --seed 42 --hdr --peak-nits 400
# Customize the output, resolution, and complete grid dimensions.
uv run python cmatrix_avatar.py --output portraits/gwb.png --size 2048 --rows 43 --columns 77 --logo-width 0.72
# Use the original grid dimensions while retaining the independently sized logo.
uv run python cmatrix_avatar.py --rows 28 --columns 67
# Select a different monospace font.
uv run python cmatrix_avatar.py --font C:/Windows/Fonts/consola.ttf
```
Each run prints its random seed. Use that seed and the same font to reproduce
the image with the same row/column settings. Without `--seed`, each run generates a new background. Existing
outputs at the selected path are replaced. `--output` accepts a basename, a
`.png` path, or a `.svg` path; both formats are always generated.
`--previews` adds `avatar-128.png` and `avatar-64.png` (using the chosen output
basename). Use the full-size PNG for a WeChat avatar and keep the SVG as the
scalable original.
## HDR output
`--hdr` adds `avatar-hdr.png`, a 16-bit RGB PNG encoded with BT.2020 primaries
and the ST 2084 (PQ) transfer function. PNG `cICP` identifies that encoding;
`cLLI` records the measured maximum and average luminance of the encoded image.
Rendering and antialiasing are composited in linear light before PQ encoding.
This is an HDR signal, rather than an SDR image with a brightness filter.
See the [PNG specification](https://www.w3.org/TR/png-3/).
`--peak-nits` accepts 100–2000 (default 400). This is the target luminance Y
of fully covered peak characters and GWB, not the maximum individual RGB
channel value. At the default setting, rain levels span 2–400 nits, with a
0.02-nit background. Quantization can introduce very small differences.
Actual HDR appearance requires an HDR display, enabled HDR output, and a viewer
that understands PQ PNG metadata. An ordinary chat preview cannot demonstrate
the encoded peak brightness. The regular PNG and SVG remain SDR alternatives;
use the regular PNG for a WeChat avatar rather than relying on HDR preservation.
The PQ PNG does not contain an SDR fallback: do not upload it expecting an
unsupported viewer to reproduce the regular PNG. Display tone mapping and color
volume limits can also alter the HDR appearance despite preserved encoded chromaticity.
### WeChat compatibility research (2026-10-18)
No explicit official guarantee for HDR preservation in personal profile pictures
was found. Tencent Cloud's [HDR processing documentation](https://intl.cloud.tencent.com/zh/document/product/1045/74729)
describes a separate cloud service, not a guarantee for WeChat avatars.
[Ultra HDR JPEG](https://developer.android.com/media/platform/hdr-image-format)
can carry an SDR base and an HDR gain map, enabling SDR fallback in legacy
viewers. That would be preferable to PQ-only PNG for a single adaptive file,
but WeChat's upload/crop/re-encode pipeline still needs device testing to verify
gain-map preservation. Use avatar.png for dependable SDR source colors;
no promise is made about pixel-identical output after WeChat processing.
To package the current matching PNG/SVG pair as an adaptive Ultra HDR JPEG:
```shell
uv run python avatar_ultrahdr.py
```
This writes `avatar-ultrahdr.jpg` using pyultrahdr: a quality-100, 4:4:4 SDR
JPEG base plus a full-resolution grayscale gain map and XMP/MPF metadata.
The base comes directly from avatar.png, with no tone mapping or palette change.
JPEG compression introduces small pixel differences. The map targets 400-nit
character/logo peaks while preserving the base chromaticity; shadows never
become darker than the SDR base. This differs intentionally from the PQ PNG's
black level. The script reports ordinary-decoding error and checks both images
are present. Actual HDR display and WeChat gain-map survival require device testing.
## Rendering
OCR A Extended (`C:/Windows/Fonts/OCRAEXT.TTF`) is preferred on Windows:
its distinctive diamond-shaped O, D, S, and W match the reference screenshot.
The screenshot's historical terminal settings are unavailable; this identification
is based on comparing glyph shapes, not the current Windows Terminal setting.
See [Microsoft's font entry](https://learn.microsoft.com/en-us/typography/font-list/ocr-a-extended).
OCR A Extended has no block/box glyphs, so GWB uses Consolas as a fallback,
with the original character proportions. This preserves the logo design; the screenshot alone
does not establish its original fallback font. `--logo-font` can override it.
No proprietary font files are copied into the project.
If OCR A Extended is unavailable, the defaults fall back to Consolas, Cascadia
Mono, DejaVu Sans Mono (Linux), or Menlo (macOS). `--font` selects the background
font explicitly. A font that includes the logo glyphs is also used for GWB
unless `--logo-font` overrides it.
Cells use a height/width ratio of 1.8 to match the reference terminal; each font's
outlines are first fitted to that cell aspect ratio. The assembled GWB is then
scaled uniformly using `--logo-width` (0.4 to 0.85, default 0.72), independently
of the number of background rows and columns.
FontTools converts glyphs to vector paths, and resvg renders that same SVG to
PNG. The SVG embeds no raster images and requires no installed fonts to view.
Pillow creates optional thumbnail previews. Dependencies are recorded in
`pyproject.toml` and pinned in `uv.lock`.
## Verify
```shell
uv run python -m unittest discover -s tests
```
Checks cover reproducible SVG paths, matching PNG rendering, logo placement,
thumbnail dimensions, custom output paths, and invalid command-line options.
HDR checks also cover PQ reference values, equal luminance peaks, gradients,
16-bit PNG data, color metadata, and decoded luminance measurements.