| 1 | #!/usr/bin/env python3 |
| 2 | """ASCII image fallback for brt -image.""" |
| 3 | |
| 4 | import sys |
| 5 | |
| 6 | |
| 7 | ASCII_CHARS = "@%#*+=-:. " |
| 8 | |
| 9 | |
| 10 | def load_ppm(path: str) -> tuple[int, int, bytes]: |
| 11 | with open(path, "rb") as f: |
| 12 | magic = f.readline().strip() |
| 13 | if magic not in (b"P6", b"P5"): |
| 14 | raise ValueError(f"Unsupported PPM magic: {magic!r}") |
| 15 | |
| 16 | def _read_line() -> bytes: |
| 17 | line = f.readline() |
| 18 | while line.startswith(b"#"): |
| 19 | line = f.readline() |
| 20 | return line |
| 21 | |
| 22 | dims = _read_line().decode().strip().split() |
| 23 | width, height = int(dims[0]), int(dims[1]) |
| 24 | maxval = int(_read_line().decode().strip()) |
| 25 | if maxval > 255: |
| 26 | raise ValueError("Only 8-bit PPM is supported") |
| 27 | |
| 28 | if magic == b"P6": |
| 29 | data = f.read(width * height * 3) |
| 30 | else: |
| 31 | gray = f.read(width * height) |
| 32 | data = bytes(ch for px in gray for ch in (px, px, px)) |
| 33 | return width, height, data |
| 34 | |
| 35 | |
| 36 | def render_ascii(path: str, width: int, height: int = 0) -> None: |
| 37 | img_w, img_h, rgb = load_ppm(path) |
| 38 | if width <= 0: |
| 39 | width = min(100, img_w) |
| 40 | |
| 41 | aspect = img_h / img_w |
| 42 | if height <= 0: |
| 43 | height = max(1, int(width * aspect * 0.55)) |
| 44 | out = [] |
| 45 | |
| 46 | for y in range(height): |
| 47 | row = [] |
| 48 | for x in range(width): |
| 49 | src_x = min(img_w - 1, int(x * img_w / width)) |
| 50 | src_y = min(img_h - 1, int(y * img_h / height)) |
| 51 | idx = (src_y * img_w + src_x) * 3 |
| 52 | r, g, b = rgb[idx], rgb[idx + 1], rgb[idx + 2] |
| 53 | lum = 0.2126 * r + 0.7152 * g + 0.0722 * b |
| 54 | char_idx = int(lum / 255 * (len(ASCII_CHARS) - 1)) |
| 55 | row.append(ASCII_CHARS[char_idx]) |
| 56 | out.append("".join(row)) |
| 57 | print("\n".join(out)) |
| 58 | |
| 59 | |
| 60 | def main() -> int: |
| 61 | if len(sys.argv) < 2: |
| 62 | print("usage: media.py ascii <ppm-file> <width> [height]", file=sys.stderr) |
| 63 | return 1 |
| 64 | |
| 65 | mode = sys.argv[1] |
| 66 | if mode != "ascii": |
| 67 | print("usage: media.py ascii <ppm-file> <width> [height]", file=sys.stderr) |
| 68 | return 1 |
| 69 | |
| 70 | if len(sys.argv) < 4: |
| 71 | print("usage: media.py ascii <ppm-file> <width> [height]", file=sys.stderr) |
| 72 | return 1 |
| 73 | |
| 74 | path = sys.argv[2] |
| 75 | width = int(sys.argv[3]) |
| 76 | height = int(sys.argv[4]) if len(sys.argv) > 4 else 0 |
| 77 | render_ascii(path, width, height) |
| 78 | return 0 |
| 79 | |
| 80 | |
| 81 | if __name__ == "__main__": |
| 82 | raise SystemExit(main()) |
| 83 | |