summaryrefslogtreecommitdiffstats
path: root/tools/make_logo_ans.py
blob: 9736e30c0a02e97e6b33090e12a1151223bd98c1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/usr/bin/env python3
"""Regenerate logo.ans (the block-art splash) from logo.png.

logo.ans is the fallback for terminals that can't draw an image, so it has to be
rendered from the same artwork or the two drift apart -- which is exactly what
happened when logo.png was replaced and the old block art kept a scan line that
no longer existed.

Each cell is a lower half-block: the background colour paints the upper pixel,
the foreground the lower one, so one cell carries two pixel rows. Transparent
pixels emit no colour at all, letting the terminal background show through, and
a cell whose lower pixel is transparent uses an upper half-block instead so the
one opaque pixel still lands on the correct half.

Needs Pillow, so run it with a python that has it (NOT ~/ida-venv):

    /usr/bin/python3 tools/make_logo_ans.py [--cols 60] [-o logo.ans]
"""
from __future__ import annotations

import argparse
import os
import sys

from PIL import Image

REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ALPHA_ON = 128          # at/above this a pixel counts as present

UPPER, LOWER = "\u2580", "\u2584"       # upper half block, lower half block


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--png", default=os.path.join(REPO, "logo.png"))
    ap.add_argument("-o", "--out", default=os.path.join(REPO, "logo.ans"))
    ap.add_argument("--cols", type=int, default=60)
    ap.add_argument("--cell", default="9x22",
                    help="terminal cell size WxH in px, for the aspect ratio")
    args = ap.parse_args()

    cw, ch = (int(v) for v in args.cell.lower().split("x"))
    im = Image.open(args.png).convert("RGBA")
    bbox = im.getchannel("A").getbbox()
    if bbox:
        im = im.crop(bbox)
    w, h = im.size
    cols = args.cols
    # two pixel rows per cell, and cells are taller than they are wide
    rows = max(1, round((h / w) * cols * cw / ch))
    im = im.resize((cols, rows * 2), Image.LANCZOS)
    px = im.load()

    out: list[str] = []
    for r in range(rows):
        line = []
        prev = None
        for c in range(cols):
            top = px[c, r * 2]
            bot = px[c, r * 2 + 1]
            t_on, b_on = top[3] >= ALPHA_ON, bot[3] >= ALPHA_ON
            if not t_on and not b_on:
                sgr, ch_ = "\033[0m", " "
            elif t_on and b_on:
                sgr = (f"\033[38;2;{bot[0]};{bot[1]};{bot[2]}m"
                       f"\033[48;2;{top[0]};{top[1]};{top[2]}m")
                ch_ = LOWER
            elif b_on:                      # only the lower pixel is present
                sgr = f"\033[0m\033[38;2;{bot[0]};{bot[1]};{bot[2]}m"
                ch_ = LOWER
            else:                           # only the upper pixel is present
                sgr = f"\033[0m\033[38;2;{top[0]};{top[1]};{top[2]}m"
                ch_ = UPPER
            if sgr != prev:
                line.append(sgr)
                prev = sgr
            line.append(ch_)
        line.append("\033[0m")
        out.append("".join(line))

    text = "\n".join(out) + "\n"
    with open(args.out, "w", encoding="utf-8") as f:
        f.write(text)
    print(f"{args.png} {w}x{h} -> {args.out}  {cols}x{rows} cells "
          f"({len(text):,} bytes, cell {cw}x{ch})")
    return 0


if __name__ == "__main__":
    sys.exit(main())