|
#!/usr/bin/env python3 |
|
""" |
|
Image Printer - Split large images into printable 8.5x11 inch pages |
|
|
|
This script takes an image file as input and outputs multiple image files |
|
that fit on 8.5x11 inch sheets of paper for printing. The script automatically: |
|
- Determines optimal page orientation (landscape vs portrait) |
|
- Scales the image to fit within page boundaries |
|
- Splits the image into numbered page sections in collated order |
|
""" |
|
|
|
import argparse |
|
import math |
|
import os |
|
from pathlib import Path |
|
from PIL import Image |
|
import sys |
|
|
|
|
|
# Page dimensions in inches |
|
PAGE_WIDTH_INCHES = 8.5 |
|
PAGE_HEIGHT_INCHES = 11.0 |
|
|
|
# Margin size in inches (1 inch on all sides) |
|
MARGIN_INCHES = 1.0 |
|
|
|
# DPI for output images (300 DPI is good for printing) |
|
OUTPUT_DPI = 300 |
|
|
|
# Convert page dimensions to pixels |
|
PAGE_WIDTH_PX = int(PAGE_WIDTH_INCHES * OUTPUT_DPI) |
|
PAGE_HEIGHT_PX = int(PAGE_HEIGHT_INCHES * OUTPUT_DPI) |
|
|
|
# Calculate printable area (page minus margins) |
|
PRINTABLE_WIDTH_PX = int((PAGE_WIDTH_INCHES - 2 * MARGIN_INCHES) * OUTPUT_DPI) |
|
PRINTABLE_HEIGHT_PX = int((PAGE_HEIGHT_INCHES - 2 * MARGIN_INCHES) * OUTPUT_DPI) |
|
MARGIN_PX = int(MARGIN_INCHES * OUTPUT_DPI) |
|
|
|
|
|
def get_page_dimensions(image_width, image_height): |
|
""" |
|
Determine the optimal page orientation and return printable area dimensions. |
|
|
|
Args: |
|
image_width (int): Width of the image in pixels |
|
image_height (int): Height of the image in pixels |
|
|
|
Returns: |
|
tuple: (printable_width_px, printable_height_px) based on optimal orientation |
|
""" |
|
# If image is wider than tall, use landscape orientation |
|
if image_width > image_height: |
|
return PRINTABLE_HEIGHT_PX, PRINTABLE_WIDTH_PX # landscape: 9" x 6.5" printable |
|
else: |
|
return PRINTABLE_WIDTH_PX, PRINTABLE_HEIGHT_PX # portrait: 6.5" x 9" printable |
|
|
|
|
|
def calculate_scale_factor(image_width, image_height, page_width, page_height): |
|
""" |
|
Calculate the scale factor to fill the printable area optimally. |
|
The image will be scaled to fill either the width or height of the printable area, |
|
whichever allows for larger scaling, then split as needed in the other dimension. |
|
This maximizes paper usage by filling as much of the printable area as possible. |
|
|
|
Args: |
|
image_width (int): Original image width |
|
image_height (int): Original image height |
|
page_width (int): Printable area width in pixels |
|
page_height (int): Printable area height in pixels |
|
|
|
Returns: |
|
float: Scale factor to maximize use of printable area |
|
""" |
|
# Calculate scale factors for both dimensions |
|
width_scale = page_width / image_width |
|
height_scale = page_height / image_height |
|
|
|
# Use the larger scale factor to maximize printable area usage |
|
# The image will be split in the other dimension if needed |
|
scale_factor = max(width_scale, height_scale) |
|
|
|
return scale_factor |
|
|
|
|
|
def load_and_process_image(image_path): |
|
""" |
|
Load an image and determine processing parameters. |
|
|
|
Args: |
|
image_path (str): Path to the input image file |
|
|
|
Returns: |
|
tuple: (image, page_width, page_height, scale_factor) |
|
""" |
|
try: |
|
# Load the image |
|
image = Image.open(image_path) |
|
|
|
# Convert to RGB if necessary (for consistent output) |
|
if image.mode != 'RGB': |
|
image = image.convert('RGB') |
|
|
|
# Get image dimensions |
|
img_width, img_height = image.size |
|
|
|
# Determine page orientation and dimensions |
|
page_width, page_height = get_page_dimensions(img_width, img_height) |
|
|
|
# Calculate scale factor |
|
scale_factor = calculate_scale_factor(img_width, img_height, page_width, page_height) |
|
|
|
print(f"Original image size: {img_width}x{img_height}") |
|
print(f"Page orientation: {'Landscape' if page_width > page_height else 'Portrait'}") |
|
print(f"Printable area: {page_width}x{page_height} pixels (with 1\" margins)") |
|
print(f"Scale factor: {scale_factor:.3f}") |
|
|
|
return image, page_width, page_height, scale_factor |
|
|
|
except Exception as e: |
|
print(f"Error loading image: {e}") |
|
sys.exit(1) |
|
|
|
|
|
def split_image_into_pages(image, page_width, page_height, scale_factor, output_dir, base_name): |
|
""" |
|
Scale and split the image into page-sized pieces. |
|
|
|
Args: |
|
image (PIL.Image): The input image |
|
page_width (int): Page width in pixels |
|
page_height (int): Page height in pixels |
|
scale_factor (float): Scale factor to apply |
|
output_dir (str): Directory to save output files |
|
base_name (str): Base name for output files |
|
|
|
Returns: |
|
int: Number of pages created |
|
""" |
|
# Scale the image if necessary |
|
if scale_factor != 1.0: |
|
orig_width, orig_height = image.size |
|
new_width = int(orig_width * scale_factor) |
|
new_height = int(orig_height * scale_factor) |
|
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS) |
|
print(f"Scaled image to: {new_width}x{new_height}") |
|
|
|
scaled_width, scaled_height = image.size |
|
|
|
# Calculate how many pages we need in each direction |
|
pages_horizontal = math.ceil(scaled_width / page_width) |
|
pages_vertical = math.ceil(scaled_height / page_height) |
|
total_pages = pages_horizontal * pages_vertical |
|
|
|
print(f"Will create {pages_horizontal}x{pages_vertical} = {total_pages} pages") |
|
|
|
# Create output directory if it doesn't exist |
|
Path(output_dir).mkdir(parents=True, exist_ok=True) |
|
|
|
page_number = 1 |
|
|
|
# Split the image in reading order (left to right, top to bottom) |
|
for row in range(pages_vertical): |
|
for col in range(pages_horizontal): |
|
# Calculate the crop box for this page (within printable area) |
|
left = col * page_width |
|
top = row * page_height |
|
right = min(left + page_width, scaled_width) |
|
bottom = min(top + page_height, scaled_height) |
|
|
|
# Crop the image |
|
page_image = image.crop((left, top, right, bottom)) |
|
|
|
# Create a full-page white background with margins |
|
full_page_width = PAGE_WIDTH_PX if page_width == PRINTABLE_WIDTH_PX else PAGE_HEIGHT_PX |
|
full_page_height = PAGE_HEIGHT_PX if page_height == PRINTABLE_HEIGHT_PX else PAGE_WIDTH_PX |
|
background = Image.new('RGB', (full_page_width, full_page_height), 'white') |
|
|
|
# Calculate position to center the content within margins |
|
paste_x = MARGIN_PX |
|
paste_y = MARGIN_PX |
|
|
|
# Paste the cropped image onto the background with margins |
|
background.paste(page_image, (paste_x, paste_y)) |
|
page_image = background |
|
|
|
# Save the page |
|
output_filename = f"{base_name}_page_{page_number:03d}.jpg" |
|
output_path = os.path.join(output_dir, output_filename) |
|
page_image.save(output_path, 'JPEG', quality=95, dpi=(OUTPUT_DPI, OUTPUT_DPI)) |
|
|
|
print(f"Created: {output_filename}") |
|
page_number += 1 |
|
|
|
return total_pages |
|
|
|
|
|
def main(): |
|
"""Main function to handle command-line arguments and process the image.""" |
|
parser = argparse.ArgumentParser( |
|
description="Split large images into printable 8.5x11 inch pages", |
|
formatter_class=argparse.RawDescriptionHelpFormatter, |
|
epilog=""" |
|
Examples: |
|
python image_printer.py large_poster.jpg |
|
python image_printer.py -o ./output_pages/ family_photo.png |
|
python image_printer.py --output-dir /tmp/pages/ blueprint.tiff |
|
""" |
|
) |
|
|
|
parser.add_argument( |
|
'image_path', |
|
help='Path to the input image file' |
|
) |
|
|
|
parser.add_argument( |
|
'-o', '--output-dir', |
|
default='./pages/', |
|
help='Directory to save output page files (default: ./pages/)' |
|
) |
|
|
|
args = parser.parse_args() |
|
|
|
# Validate input file |
|
if not os.path.isfile(args.image_path): |
|
print(f"Error: Input file '{args.image_path}' not found.") |
|
sys.exit(1) |
|
|
|
# Get base name for output files |
|
base_name = Path(args.image_path).stem |
|
|
|
print(f"Processing image: {args.image_path}") |
|
print(f"Output directory: {args.output_dir}") |
|
print(f"Base name: {base_name}") |
|
print("-" * 50) |
|
|
|
# Load and process the image |
|
image, page_width, page_height, scale_factor = load_and_process_image(args.image_path) |
|
|
|
# Split the image into pages |
|
total_pages = split_image_into_pages( |
|
image, page_width, page_height, scale_factor, args.output_dir, base_name |
|
) |
|
|
|
print("-" * 50) |
|
print(f"Success! Created {total_pages} page(s) in '{args.output_dir}'") |
|
print(f"Files are numbered in collated order for easy printing.") |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |