From f8a9ce1cb8d9fe2ef118d3648540b3359dce08ba Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Fri, 16 Oct 2026 03:35:43 -0400 Subject: [PATCH] The capture also draws the two wordmarks of the splash scripts/screenshots.py draws logo.71.ans and logo.53.ans, through the splash's own loader and the emulator's Terminus raster, in the Campbell colours, into docs/screenshots/splash/logo..png at one image pixel per font pixel. The website takes its splash from there, so it always shows the art that the app shows. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/screenshots/splash/logo.53.png | Bin 0 -> 3445 bytes docs/screenshots/splash/logo.71.png | Bin 0 -> 4007 bytes scripts/README.md | 11 +++-- scripts/screenshots.py | 67 +++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 docs/screenshots/splash/logo.53.png create mode 100644 docs/screenshots/splash/logo.71.png diff --git a/docs/screenshots/splash/logo.53.png b/docs/screenshots/splash/logo.53.png new file mode 100644 index 0000000000000000000000000000000000000000..686f979cfa9b98fa1af45f81aa7ac514e058b7b4 GIT binary patch literal 3445 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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/splash/logo.71.png b/docs/screenshots/splash/logo.71.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb1e07e51a14d8b1040aa48e28aebb2d5cf156b GIT binary patch literal 4007 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/` | +| `screenshots.py` | Makes the screenshots of the website from the demo mesh (`--demo`): the main menu, the chat, the paths of a message, and the map, for the terminal, the Cardputer Zero, and the PicoCalc. They are the same at each run. The result goes to `docs/screenshots//`. It also draws the two wordmarks of the splash into `docs/screenshots/splash/`, which the website uses as its splash | | `picocalc-lyra/` | Setup of the Calculinux device: console font, palette, Wi-Fi. `xiao-radio/` is the kit for the XIAO radio (firmware build, flashing, UART setup) | | `uconsole/` | The uConsole's always-on SPI bridge service. MeshTerm can also control that radio directly, with no script (refer to `docs/devices/uconsole.md`) | @@ -67,5 +67,10 @@ Emoji, which Windows has. On another system, change `TEXT_FONTS` and `EMOJI_FONT top of the script. The first run downloads the tiles of the map. The later runs use the tile cache in the temporary directory. -After a change to a screen, run the script again, then rebuild the website (refer to the -README of the `meshterm.net` repository). +The script also draws each wordmark of the startup splash (`meshterm/assets/splash/*.ans`) +as `docs/screenshots/splash/logo..png`, one image pixel for each pixel of the font, +in the Campbell colours. The website takes its splash from there: the wide mark on a wide +screen, and the narrow mark on a phone. `--only splash` draws only these two images. + +After a change to a screen or to the art, run the script again, then rebuild the website +(refer to the README of the `meshterm.net` repository). diff --git a/scripts/screenshots.py b/scripts/screenshots.py index e63e315..c151c3e 100644 --- a/scripts/screenshots.py +++ b/scripts/screenshots.py @@ -9,6 +9,10 @@ an 80×24 terminal), ``cardputer-zero``, and ``picocalc-lyra``. Each run uses `` the paths of a message, and the map, and it saves each screen as a PNG. The result is ``DIR//.png`` (``DIR`` is ``docs/screenshots`` by default). +The script also draws each wordmark of the startup splash (``meshterm/assets/splash``) as +``DIR/splash/logo..png``, one image pixel for each pixel of the font: the website +shows them as its splash, the wide mark on a wide screen and the narrow mark on a phone. + The screens are the same at each run, for four reasons: * The demo writes the same history at each start. @@ -53,6 +57,18 @@ ROOT = Path(__file__).resolve().parent.parent #: The sets, in the order on the website. The terminal set is the default there. SETS = ("terminal", "cardputer-zero", "picocalc-lyra") +#: The name of the wordmarks in ``--only``. They need no run of MeshTerm. +SPLASH = "splash" + +#: The 16 colours of the wordmarks: the Campbell scheme of Windows Terminal, which the +#: art was drawn in and the website uses. +CAMPBELL = ( + (12, 12, 12), (197, 15, 31), (19, 161, 14), (193, 156, 0), + (0, 55, 218), (136, 23, 152), (58, 150, 221), (204, 204, 204), + (118, 118, 118), (231, 72, 86), (22, 198, 12), (249, 241, 165), + (59, 120, 255), (180, 0, 158), (97, 214, 214), (242, 242, 242), +) # fmt: skip + #: The grid of the terminal set: a standard terminal. TERMINAL_GRID = (80, 24) @@ -74,7 +90,7 @@ def main(argv: list[str]) -> int: """Run one child process for each set, then report what each one wrote.""" parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--out", type=Path, default=ROOT / "docs" / "screenshots") - parser.add_argument("--only", nargs="*", choices=SETS, default=list(SETS)) + parser.add_argument("--only", nargs="*", choices=(*SETS, SPLASH), default=[*SETS, SPLASH]) parser.add_argument("--child", choices=SETS, help=argparse.SUPPRESS) args = parser.parse_args(argv) if args.child: @@ -85,7 +101,10 @@ def main(argv: list[str]) -> int: # directory of its own, so it gets the font directory of this machine by name. fonts = os.environ.get("MESHTERM_EMULATOR_FONTS") or str(font_dir()) status = 0 - for name in args.only: + if SPLASH in args.only: + for path in _draw_splash(args.out / SPLASH): + print(f"{SPLASH}: {path}", flush=True) + for name in (name for name in args.only if name in SETS): home = Path(tempfile.gettempdir()) / "meshterm-screenshots" / name env = { **os.environ, @@ -270,6 +289,50 @@ def _tour(capture: Capture, type_text: Callable[[str], None], save: Callable[[st # --------------------------------------------------------------------------- drawing +def _draw_splash(target: Path) -> list[Path]: + """Draw each wordmark of the splash in the emulator's font, one image pixel per pixel. + + The marks are the ANSI art that the startup splash shows, read through the same loader + (:func:`meshterm.ui.logo.load_logo`, widest first). The emulator's raster draws them + in Terminus 6×12, the font of the handhelds. Its cell has the shape of the VGA cell + that the art was drawn in, so the art keeps its proportions. + + Returns: + The paths of the images, widest mark first. + """ + from PIL import Image + + from meshterm.emulator.font import CELL_H, CELL_W, find_font + from meshterm.emulator.raster import Raster + from meshterm.emulator.vt import Terminal + from meshterm.ui.logo import load_logo, logo_width + + target.mkdir(parents=True, exist_ok=True) + font = find_font() + written: list[Path] = [] + limit: int | None = None + while rows := load_logo(limit): + width = logo_width(rows) + terminal = Terminal(width, len(rows), palette=CAMPBELL) + terminal.feed("\r\n".join(f"{row}\x1b[0m" for row in rows)) + raster = Raster( + terminal, + font, + default_fg=CAMPBELL[7], + default_bg=CAMPBELL[0], + width=width * CELL_W, + height=len(rows) * CELL_H, + top_left=True, + ) + raster.redraw() + size = (raster.width, raster.height) + path = target / f"logo.{width}.png" + Image.frombytes("RGB", size, bytes(raster.pixels)).save(path, optimize=True) + written.append(path) + limit = width - 1 + return written + + def _draw(name: str, grid, palette): # noqa: ANN001, ANN202 - cells, colours, an Image """The image of one screen, in the look of its set.""" from meshterm.emulator.devices import DEVICES