Files
filmsim/poster.py
2025-06-19 15:31:45 -04:00

138 lines
6.5 KiB
Python

import argparse
import sys
from PIL import Image, ImageDraw
def create_full_poster(input_path: str, output_path: str):
"""
Generates a poster from a high-resolution source image.
The poster features a 50% resolution version of the main image at the top,
with a complete 3x3 grid of all nine "rule of thirds" 250% zoom patches below it.
"""
try:
# Open the high-resolution source image
with Image.open(input_path) as original_image:
original_image = original_image.convert("RGB")
orig_width, orig_height = original_image.size
print(f"Loaded input image: {input_path} ({orig_width}x{orig_height})")
# --- 1. Define Sizes ---
# Central image is 50% of original
main_image_width = orig_width // 2
main_image_height = orig_height // 2
# Crop area for patches is 1/6th of original width
patch_crop_width = orig_width // 6
patch_crop_height = int(patch_crop_width * (orig_height / orig_width))
# Patches are zoomed to 250% of their cropped size
zoom_factor = 2.5
zoomed_patch_width = int(patch_crop_width * zoom_factor)
zoomed_patch_height = int(patch_crop_height * zoom_factor)
# Define padding values
patch_padding = 25 # Padding between patches in the grid
section_padding = 50 # Padding between the main image and the grid
canvas_padding = 50 # Padding around the entire content
# --- 2. Calculate Layout & Create Canvas ---
# Calculate the dimensions of the 3x3 patch grid
grid_width = (zoomed_patch_width * 3) + (patch_padding * 2)
grid_height = (zoomed_patch_height * 3) + (patch_padding * 2)
# Calculate total canvas dimensions
canvas_width = max(main_image_width, grid_width) + (canvas_padding * 2)
canvas_height = main_image_height + grid_height + section_padding + (canvas_padding * 2)
# Create the blank white canvas
canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
draw = ImageDraw.Draw(canvas)
print(f"Created poster canvas of size: {canvas_width}x{canvas_height}")
# --- 3. Place Main Image & Draw Highlights ---
# Create the 50% resolution main image
main_image = original_image.resize((main_image_width, main_image_height), Image.Resampling.LANCZOS)
# Position and paste the main image in the top section
main_image_x = (canvas_width - main_image_width) // 2
main_image_y = canvas_padding
canvas.paste(main_image, (main_image_x, main_image_y))
# Define the center points for the Rule of Thirds grid on the ORIGINAL image
thirds_points = [
(orig_width // 6, orig_height // 6), (orig_width // 2, orig_height // 6), (5 * orig_width // 6, orig_height // 6),
(orig_width // 6, orig_height // 2), (orig_width // 2, orig_height // 2), (5 * orig_width // 6, orig_height // 2),
(orig_width // 6, 5 * orig_height // 6), (orig_width // 2, 5 * orig_height // 6), (5 * orig_width // 6, 5 * orig_height // 6),
]
# Draw all 9 highlight boxes on the main image
print("Drawing highlight boxes on main image...")
for center_x, center_y in thirds_points:
crop_left = center_x - (patch_crop_width // 2)
crop_top = center_y - (patch_crop_height // 2)
# Scale coordinates to the 50% main image and add its offset
highlight_x1 = main_image_x + int(crop_left * 0.5)
highlight_y1 = main_image_y + int(crop_top * 0.5)
highlight_x2 = highlight_x1 + int(patch_crop_width * 0.5)
highlight_y2 = highlight_y1 + int(patch_crop_height * 0.5)
draw.rectangle((highlight_x1, highlight_y1, highlight_x2, highlight_y2), outline="red", width=4)
# --- 4. Create and Place all 9 Patches in a Grid ---
print("Generating and placing 9 zoomed patches in a grid...")
grid_origin_x = (canvas_width - grid_width) // 2
grid_origin_y = main_image_y + main_image_height + section_padding
for i, (center_x, center_y) in enumerate(thirds_points):
# Define the crop box on the ORIGINAL image
crop_box = (
center_x - patch_crop_width // 2, center_y - patch_crop_height // 2,
center_x + patch_crop_width // 2, center_y + patch_crop_height // 2
)
# Crop the patch and zoom it
patch = original_image.crop(crop_box)
zoomed_patch = patch.resize((zoomed_patch_width, zoomed_patch_height), Image.Resampling.LANCZOS)
# Determine the patch's position in the 3x3 grid
row, col = divmod(i, 3)
patch_x = grid_origin_x + col * (zoomed_patch_width + patch_padding)
patch_y = grid_origin_y + row * (zoomed_patch_height + patch_padding)
# Paste the patch and draw a border
canvas.paste(zoomed_patch, (patch_x, patch_y))
draw.rectangle(
(patch_x, patch_y, patch_x + zoomed_patch_width, patch_y + zoomed_patch_height),
outline="black",
width=2
)
# --- 5. Save the Final Poster ---
canvas.save(output_path, quality=95)
print(f"\nSuccess! Poster saved to: {output_path}")
except FileNotFoundError:
print(f"Error: The input file was not found at '{input_path}'", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Create a poster with a main image and a full 3x3 grid of zoomed-in 'rule of thirds' patches.",
formatter_class=argparse.RawTextHelpFormatter,
epilog="Example:\n python create_poster_v2.py my_photo.jpg poster_result.png"
)
parser.add_argument("input", help="Path to the high-resolution input image.")
parser.add_argument("output", help="Path for the generated poster image.")
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
create_full_poster(args.input, args.output)