#!/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()