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