Add script.py
Signed-off-by: eightball <code@8ball.space>
This commit is contained in:
commit
a313fc5ee1
1 changed files with 120 additions and 0 deletions
120
script.py
Normal file
120
script.py
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue