A living ASCII face on your desktop.
Facer is a transparent, click-through Linux desktop overlay that renders a human face as animated ASCII. Faces breathe, blink, shimmer, and smoothly change expression while your mouse clicks pass through to the applications underneath.
The animation is stored as brightness maps, not ASCII frames. Every frame reinterprets those maps using your current character set, resolution, and a little evolving static.
- Four distinct faces, including the original short-haired face.
- Five expressions: neutral, happy, sad, angry, and surprised.
- Direct transitions between any two expressions, with smooth interruption mid-transition.
- Smooth face switching that retains the selected emotion and rendering settings.
- Independent blinking, subtle breathing and head movement, and continuous brightness noise.
- Live control of character density, glyph selection, color, opacity, position, and animation speed.
- A detached background process with terminal controls and a foreground mode for debugging.
- Linux with an active X11 or XWayland display.
- A compositing desktop for window transparency.
- Python 3.11–3.14 and
uv. - Bash and GNU
readlinkfor the launcher.
Facer has been tested on GNOME/Wayland using Qt's XCB backend through XWayland. Native Wayland-only sessions are not supported by this launcher. The overlay runs within your graphical session; it does not install a system service or automatic login startup.
Clone or download the repository, then run these commands from its root directory:
uv sync --locked
./facer startThe original face appears near the bottom-right of your primary monitor and automatically cycles through expressions. The included brightness maps are ready to use; no image generation or API key is required to run Facer.
./facer status
./facer stopTo run in the foreground instead:
./facer runPress Ctrl+C to stop foreground mode. start detaches and returns after the overlay is ready. Starting it again reports the existing instance instead of opening another overlay.
From the repository root:
mkdir -p "$HOME/.local/bin"
ln -s "$PWD/facer" "$HOME/.local/bin/facer"Ensure ~/.local/bin is on your PATH. You can then use facer from any directory. The launcher resolves the symlink back to the project, where its code, assets, and isolated .venv remain. If you move the project, update the symlink.
facer faces
facer face bob
facer face silver
facer face copper
facer face original| Name | Face | Reference Sheet |
|---|---|---|
original |
Original short-haired face; default at launch | View |
bob |
Woman with a dark bob | View |
silver |
Older man with a silver beard | View |
copper |
Freckled woman with copper curls | View |
Every face has all five expressions and its own closed-eye reference. Switching faces morphs from the currently visible face into the same expression on the new face, preserving automatic or held mode. The original assets are retained separately.
Choose a face when starting a new instance:
facer start --face silverOriginal Photographic Expression References
These generated photographs supply the brightness maps. The running overlay draws ASCII characters, not these photographs.
facer expression happy
facer expression sad
facer expression angry
facer expression surprised
facer expression neutral
facer autoSelecting an expression transitions to it and holds it. Breathing, blinking, and static continue. auto resumes automatic expression changes, with 4–7 second holds after transitions. Transitions take 2.4 seconds by default.
To start with a held expression:
facer start --face copper --expression happy --holdSettings can be changed while the overlay is running:
facer set --static 0.03
facer set --columns 140 --font-size 9
facer set --x 40 --y 80
facer set --color '#e8f0ee' --opacity 0.95
facer set --ramp ' .:-=+*#%@'
facer set --transition 3 --motion 0.7 --fps 30
facer set --contrast 1.3 --gamma 0.85| Option | Default | Range / Format | Effect |
|---|---|---|---|
--static |
0.018 |
0.001–0.15 |
Amount of live brightness noise |
--motion |
1.0 |
0–4 |
Breathing and head movement amplitude |
--transition |
2.4 |
0.2–12 seconds |
Expression and face transition duration |
--fps |
30 |
5–60, integer |
Target frame rate |
--columns |
112 |
40–240, integer |
Horizontal character count |
--font-size |
9 |
6–24, integer pixels |
Glyph size |
--opacity |
0.95 |
0.1–1 |
Glyph opacity |
--color |
#e8f0ee |
#RRGGBB |
Glyph color |
--ramp |
.,:;irsXA253hMHGS#9B&@ |
Unique printable ASCII, starting with a space | Available characters |
--x, --y |
Near bottom-right | -32768–32768, integer |
Desktop position |
--contrast |
1.2 |
0.2–3 |
Brightness multiplier before gamma |
--gamma |
0.85 |
0.2–3 |
Brightness exponent |
Settings are session-only. All options above except contrast and gamma can also be supplied to start or run. Launch options apply only when creating a new instance; use set or face to change an existing one.
Noise evolves gradually between frames. Its nonzero minimum keeps the ASCII lively even when head motion is disabled. Characters are sorted by their measured density in the actual font, so custom ramps do not need to be manually ordered.
More columns preserve more facial detail and increase the overlay's width at a fixed font size. Row count follows the glyph dimensions to preserve the portrait's proportions. Try dark text such as --color '#182020' against a light desktop.
- Six photographic expression references are converted into 320 × 320 floating-point luminance maps and soft transparency masks.
- Bidirectional optical flow warps the current and target expressions before blending their brightness values.
- A localized closed-eye reference provides blinking, while small spatial warps add breathing and head movement.
- The result is sampled at the current character resolution. Temporally correlated noise perturbs the sampled brightness.
- Brightness selects ASCII glyphs by measured font density. Qt paints the glyphs onto a transparent, input-pass-through window.
Optical flow and rendered frames stay in memory. No ASCII frame sequences are saved. The result is an ASCII interpretation of the photographic shading, with detail limited by the selected character resolution.
| Path | Purpose |
|---|---|
facer |
Bash launcher with symlink support |
facer.py |
Command-line interface and daemon lifecycle |
overlay.py |
Qt overlay, glyph rendering, and local control socket |
animation.py |
Brightness morphs, blinking, motion, and static |
faces.py |
Face catalog and asset paths |
prepare-maps.py |
Reference-sheet conversion |
test-animation.py |
Animation and rendering tests |
assets/ |
Original face references, prompt, maps, and metadata |
assets/faces/ |
Separate bob, silver, and copper assets |
pyproject.toml, uv.lock |
Dependencies and locked versions |
Each face set includes expression-reference.png, generation-prompt.txt, brightness-maps.npz, and brightness-maps.json. The fictional face references were created with the built-in image generation tool; the exact prompts are included alongside each set.
The NPZ files contain brightness and masks arrays, expression names, and a format version. Brightness uses float32 display-referred luminance in the 0–1 range. Load these files with numpy.load(path, allow_pickle=False).
The renderer uses Python, PySide6/Qt, NumPy, and OpenCV. Pillow is used when preparing maps from reference images.
uv sync --locked
uv run python test-animation.py -vTests cover expression endpoints, interruption continuity, held-face static, localized blinking, face switching, live settings, and transparent rendering. Qt tests run offscreen.
To rebuild a face's maps from its included reference sheet:
uv run python prepare-maps.py --face original
uv run python prepare-maps.py --face bob
uv run python prepare-maps.py --face silver
uv run python prepare-maps.py --face copperThe converter expects a three-column, two-row atlas in this order: neutral, happy, sad, angry, surprised, closed eyes. Rebuilding overwrites that face's map and metadata files. Restart the overlay or reselect the face to load rebuilt maps.
- Startup fails: inspect
.local/facer.logor usefacer runfor foreground output. An active X11/XWaylandDISPLAYis required. - Qt reports an XCB plugin error: check the log for the missing library and install the corresponding runtime package for your Linux distribution.
- The face is difficult to see: adjust its color, opacity, contrast, or desktop position.
- The overlay is too large: reduce
--columnsor--font-size. - Brightness maps are missing: run the indicated
prepare-maps.py --face …command using that face's reference sheet. - You cannot click the face: this is intentional. Use the terminal commands to control it or
facer stopto close it.
The daemon uses a per-project, user-only Unix socket in the session runtime directory and a file lock to prevent duplicate instances. Logs and lock files are kept in the ignored .local/ directory.
