146 lines
7.3 KiB
Markdown
146 lines
7.3 KiB
Markdown
# Matrix avatar
|
||
|
||
`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
|
||
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.
|