Last active
May 2, 2025 16:22
-
-
Save simonLeary42/1ba1093b5565a712c8a1f819971f9cd1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
import shutil | |
import argparse | |
from PIL import Image | |
parser = argparse.ArgumentParser() | |
parser.add_argument("path") | |
parser.add_argument("--width", type=int, default=None) | |
parser.add_argument("--height", type=int, default=None) | |
args = parser.parse_args() | |
# for debugging | |
# def display_dots(dots: list[None | int]) -> str: | |
# output = [["" for _ in range(2)] for _ in range(3)] | |
# braille_dot_index_to_x_y = [None, [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]] | |
# for i, (x, y) in enumerate(braille_dot_index_to_x_y[1:], start=1): | |
# if dots[i]: | |
# output[y][x] = "#" | |
# return output | |
img = Image.open(args.path) | |
input_height, input_width = img.size | |
if args.height is None and args.width is not None: | |
w2h = input_width / input_height | |
args.height = args.width * w2h | |
print( | |
f"defaulting height={args.height} from width and aspect ratio...", | |
file=sys.stderr, | |
) | |
if args.width is None and args.height is not None: | |
h2w = input_height / input_width | |
args.width = args.height * h2w | |
print( | |
f"defaulting width={args.height} from height and aspect ratio...", | |
file=sys.stderr, | |
) | |
if args.width is None and args.height is None: | |
# if neither is specified, use half the width of tty | |
# and use original aspect ratio to calculate height | |
tty_cols, tty_rows = shutil.get_terminal_size() | |
width_cols = tty_cols / 2 | |
width_pixels = width_cols * 2 # braille character is 2 dots wide | |
args.width = width_pixels | |
print( | |
f"defaulting width={width_pixels} from tty...", | |
file=sys.stderr, | |
) | |
w2h = input_width / input_height | |
args.height = args.width * w2h | |
print( | |
f"defaulting height={args.height} from width and aspect ratio...", | |
file=sys.stderr, | |
) | |
if remainder := args.height % 4: | |
args.height -= remainder | |
print(f"decreasing height by {remainder} since it must be a multiple of 4...", file=sys.stderr) | |
if remainder := args.width % 2: | |
args.width -= remainder | |
print(f"decreasing width by {remainder} since it must be a multiple of 2...", file=sys.stderr) | |
args.width = int(args.width) | |
args.height = int(args.height) | |
if args.height > input_height: | |
raise RuntimeError( | |
f"image too small! height={input_height}, requested output height={args.height}" | |
) | |
if args.width > input_width: | |
raise RuntimeError(f"image too small! width={input_width}, requested output width={args.width}") | |
# print("squishing image vertically to compensate for gaps in braille...", file=sys.stderr) | |
# img = img.resize((img.size[0], int(img.size[1] * 0.9))) | |
img = img.resize((args.width, args.height)) | |
img = img.convert("1") # black and white, not grayscale | |
# img.save("/tmp/img2braille_intermediate.png") | |
output_lines = [] | |
# starting at top left | |
for y0 in range(0, args.height, 4): | |
output_lines.append("") | |
for x0 in range(0, args.width, 2): | |
# 1 4 | |
# 2 5 | |
# 3 6 | |
# 7 8 | |
# 1 2 3 4 5 6 7 8 | |
# 2^0 2^1 2^2 2^3 2^4 2^5 2^6 2^7 | |
braille_dot_index_and_pow2_to_x_and_y = { | |
(1, 0): (0, 0), | |
(2, 1): (0, 1), | |
(3, 2): (0, 2), | |
(4, 3): (1, 0), | |
(5, 4): (1, 1), | |
(6, 5): (1, 2), | |
(7, 6): (0, 3), | |
(8, 7): (1, 3), | |
} | |
# dots = [None] * 9 # there is no index 0, dots[0] is a placeholder | |
unicode_num = int("2800", 16) | |
for (dot_index, pow2), (x, y) in braille_dot_index_and_pow2_to_x_and_y.items(): | |
pixel = img.getpixel((x0 + x, y0 + y)) | |
# dots[dot_index] = pixel | |
if pixel: | |
unicode_num += 2**pow2 | |
if unicode_num == int("2800", 16): | |
output_lines[-1] += " " | |
else: | |
output_lines[-1] += chr(unicode_num) | |
for line in output_lines: | |
print(line) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment