image2text/script.py

121 lines
3.4 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
import argparse
import sys
from pathlib import Path
from typing import Optional
try:
from PIL import Image
except ImportError:
print(
"Error: Pillow is not installed. Install it with 'pip install Pillow' or use the project venv.",
file=sys.stderr,
)
sys.exit(1)
def to_bw_char(value: int, threshold: int, invert: bool) -> str:
is_black = value < threshold
if invert:
is_black = not is_black
return 'B' if is_black else 'W'
def convert_image(
input_path: Path,
output_path: Path,
width: Optional[int],
height: Optional[int],
threshold: int,
invert: bool,
) -> None:
# Load image
img = Image.open(input_path)
# Convert to grayscale (luminance)
img = img.convert('L')
# Resize if requested
if width is not None or height is not None:
# Preserve aspect ratio if only one dimension is provided
if width is not None and height is not None:
target_size = (width, height)
else:
w, h = img.size
if width is not None and height is None:
height = round(h * (width / w))
elif height is not None and width is None:
width = round(w * (height / h))
target_size = (width, height)
img = img.resize(target_size, resample=Image.NEAREST)
pixels = img.load()
w, h = img.size
lines = []
for y in range(h):
row_chars = []
for x in range(w):
row_chars.append(to_bw_char(pixels[x, y], threshold, invert))
lines.append(''.join(row_chars))
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text('\n'.join(lines), encoding='utf-8')
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description="Convert an image to bad_apple_frame.txt format (W=white, B=black)."
)
# Input aliases: --input/-i and --image
p.add_argument(
'--input', '-i', dest='input', type=Path, required=False,
help='Path to input image (PNG, JPG, GIF, etc.)'
)
p.add_argument(
'--image', dest='input', type=Path, required=False,
help='Alias for --input'
)
# Output aliases: --output/-o and --out
p.add_argument(
'--output', '-o', dest='output', type=Path, required=False,
help='Path to output text file (defaults to <image>_frame.txt)'
)
p.add_argument(
'--out', dest='output', type=Path, required=False,
help='Alias for --output'
)
p.add_argument('--width', type=int, help='Optional output width in pixels')
p.add_argument('--height', type=int,
help='Optional output height in pixels')
p.add_argument(
'--threshold', type=int, default=128,
help='Grayscale threshold 0..255 (default: 128)'
)
p.add_argument('--invert', action='store_true',
help='Invert mapping (B/W flipped)')
args = p.parse_args()
if args.input is None:
p.error("--input/--image is required")
if args.output is None:
args.output = args.input.with_name(f"{args.input.stem}_frame.txt")
return args
def main() -> None:
args = parse_args()
convert_image(
input_path=args.input,
output_path=args.output,
width=args.width,
height=args.height,
threshold=max(0, min(255, args.threshold)),
invert=args.invert,
)
if __name__ == '__main__':
main()