- 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>
This commit is contained in:
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user