Created
July 11, 2025 00:26
-
-
Save johnjosephhorton/573c264a9a0e81925cf7e2828c4535ca to your computer and use it in GitHub Desktop.
Tool for applying a grid to a PNG and labeling.
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
#!/usr/bin/env python3 | |
""" | |
Grid Image Tool | |
Adds red horizontal and vertical lines to an image and labels each cell with letters. | |
Dependencies are declared inline for use with uv run. | |
Usage: uv run grid_image.py input.png --num-vertical 5 --num-horizontal 3 | |
""" | |
# /// script | |
# dependencies = [ | |
# "typer", | |
# "Pillow", | |
# ] | |
# /// | |
import typer | |
from pathlib import Path | |
from PIL import Image, ImageDraw, ImageFont | |
import string | |
from typing import Optional | |
app = typer.Typer() | |
def generate_labels(num_cells: int) -> list[str]: | |
"""Generate labels 1, 2, 3, ... for the given number of cells.""" | |
labels = [] | |
for i in range(num_cells): | |
labels.append(str(i + 1)) # Start from 1 instead of 0 | |
return labels | |
@app.command() | |
def main( | |
image_path: Path = typer.Argument(..., help="Path to the input PNG image"), | |
num_vertical: int = typer.Option(3, "--num-vertical", help="Number of vertical lines"), | |
num_horizontal: int = typer.Option(3, "--num-horizontal", help="Number of horizontal lines"), | |
output_path: Optional[Path] = typer.Option(None, "--output", help="Output path (default: input_grid.png)"), | |
): | |
"""Add red grid lines and labels to an image.""" | |
if not image_path.exists(): | |
typer.echo(f"Error: Image file {image_path} not found", err=True) | |
raise typer.Exit(1) | |
if output_path is None: | |
output_path = image_path.parent / f"{image_path.stem}_grid{image_path.suffix}" | |
try: | |
with Image.open(str(image_path)) as img: | |
# Convert to RGB if necessary | |
if img.mode != 'RGB': | |
img = img.convert('RGB') | |
width, height = img.size | |
draw = ImageDraw.Draw(img) | |
# Draw vertical lines | |
if num_vertical > 0: | |
step_x = width / (num_vertical + 1) | |
for i in range(1, num_vertical + 1): | |
x = int(step_x * i) | |
draw.line([(x, 0), (x, height)], fill='red', width=2) | |
# Draw horizontal lines | |
if num_horizontal > 0: | |
step_y = height / (num_horizontal + 1) | |
for i in range(1, num_horizontal + 1): | |
y = int(step_y * i) | |
draw.line([(0, y), (width, y)], fill='red', width=2) | |
# Add labels to each cell | |
cols = num_vertical + 1 | |
rows = num_horizontal + 1 | |
total_cells = cols * rows | |
labels = generate_labels(total_cells) | |
cell_width = width / cols | |
cell_height = height / rows | |
# Calculate font size based on cell size | |
font_size = max(24, min(int(cell_width * 0.3), int(cell_height * 0.3))) | |
# Try to use a default font or fall back to PIL default | |
font = None | |
try: | |
# Try common system fonts | |
font = ImageFont.truetype("arial.ttf", font_size) | |
except: | |
try: | |
# Try other common fonts on macOS | |
font = ImageFont.truetype("/System/Library/Fonts/Arial.ttf", font_size) | |
except: | |
try: | |
# Try Helvetica on macOS | |
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size) | |
except: | |
# Fall back to PIL default font (bitmap font, fixed size) | |
font = ImageFont.load_default() | |
label_idx = 0 | |
for row in range(rows): | |
for col in range(cols): | |
if label_idx < len(labels): | |
x = int(col * cell_width + 5) # 5px padding from left | |
y = int(row * cell_height + 5) # 5px padding from top | |
draw.text((x, y), labels[label_idx], fill='black', font=font) | |
label_idx += 1 | |
# Save the result | |
img.save(str(output_path)) | |
typer.echo(f"Grid image saved to: {output_path}") | |
except Exception as e: | |
typer.echo(f"Error processing image: {e}", err=True) | |
raise typer.Exit(1) | |
if __name__ == "__main__": | |
app() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment