Files
Das-OS/tools/art2cells.py
T
dasandClaude Opus 5.5 a2bffc7fe9
build / build (push) Successful in 38m26s
Terminal: Das prompt, dprompt and dbanner (0.9.1)
- das-shell 0.1.0: the Das two-line bash prompt by default (replaces the
  PS1 of Void's skel .bashrc once), dprompt (Textual prompt builder with
  presets and live preview) and dbanner (SSH login banner editor: canvas
  tools, fields with values, widgets, das-banner service for cached ones,
  Das OS logo and tryzub pictures). Python tests in make test.
- das-server 0.3.0 depends on das-shell; the server installer mentions
  dprompt and dbanner; the desktop image includes it.
- run-qemu.sh forwards the guest's SSH to localhost:2222.
- Hardware fixes: Ventoy menu icon, boot menu titles "Das OS <version>".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:17:10 +03:00

102 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Turns an SVG into a dbanner picture: half-block cells (▀ ▄ █) in ANSI colors.
tools/art2cells.py SVG WIDTH_PX HEIGHT_PX NAME "UK title" "EN title" \
"#rrggbb=ansi_name" ... [--mirror] > srcpkgs/das-shell/files/share/banner/art/NAME.json
Each output cell holds two square pixels, so HEIGHT_PX must be even. The SVG is
drawn 8 times larger by ImageMagick, and every pixel takes the color that
covers most of its 8x8 block (or stays empty). Colors are matched to the
nearest one given on the command line; --mirror copies the left half to the
right for symmetric pictures. Needs ImageMagick and Pillow; this is a
developer tool, the package ships only the JSON.
"""
import json
import os
import subprocess
import sys
import tempfile
from collections import Counter
from PIL import Image
SCALE = 8
def main():
args = sys.argv[1:]
mirror = "--mirror" in args
if mirror:
args.remove("--mirror")
svg, width, height, name, uk, en, *pairs = args
width, height = int(width), int(height)
palette = []
for pair in pairs:
hexcolor, ansi = pair.split("=")
palette.append((tuple(int(hexcolor[i:i + 2], 16) for i in (1, 3, 5)), ansi))
with tempfile.TemporaryDirectory() as tmp:
png = os.path.join(tmp, "art.png")
subprocess.run(["convert", "-background", "none", "-density", "600", svg,
"-resize", f"{width * SCALE}x{height * SCALE}!", png], check=True)
img = Image.open(png).convert("RGBA")
def nearest(rgb):
return min(palette, key=lambda p: sum((a - b) ** 2 for a, b in zip(p[0], rgb)))[1]
pixels = []
for py in range(height):
row = []
for px in range(width):
votes = Counter()
for y in range(py * SCALE, (py + 1) * SCALE):
for x in range(px * SCALE, (px + 1) * SCALE):
r, g, b, a = img.getpixel((x, y))
votes[nearest((r, g, b)) if a >= 128 else None] += 1
row.append(votes.most_common(1)[0][0])
if mirror:
# The source is symmetric; rounding is not, so copy the left half
for x in range(width // 2):
row[width - 1 - x] = row[x]
pixels.append(row)
styles, keys = {}, "abcdefghijklmnopqrstuvwxyz"
def key(fg, bg):
want = {"fg": fg, "bg": bg, "bold": False}
for k, v in styles.items():
if v == want:
return k
k = keys[len(styles)]
styles[k] = want
return k
lines, smap = [], []
for cy in range(height // 2):
line, keyline = "", ""
for x in range(width):
top, bottom = pixels[2 * cy][x], pixels[2 * cy + 1][x]
if top is None and bottom is None:
ch, k = " ", " "
elif top == bottom:
ch, k = "█", key(top, None)
elif bottom is None:
ch, k = "▀", key(top, None)
elif top is None:
ch, k = "▄", key(bottom, None)
else:
ch, k = "▀", key(top, bottom)
line += ch
keyline += k
lines.append(line.rstrip())
smap.append(keyline[:len(line.rstrip())])
design = {"name": name, "title": {"uk": uk, "en": en}, "styles": styles,
"lines": lines, "style_map": smap}
json.dump(design, sys.stdout, ensure_ascii=False, indent=2)
sys.stdout.write("\n")
if __name__ == "__main__":
main()