Created
September 2, 2025 16:52
-
-
Save zvchei/015075e07433e373b568f3f8f7b53dd8 to your computer and use it in GitHub Desktop.
Script to arrange images two by two on A4 landscape pages. Each page will contain up to 2 images side by side with spacing between them.
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 | |
| """ | |
| MIT-0 License | |
| Copyright (c) 2025 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| SOFTWARE. | |
| --- | |
| This script was generated by Claude Sonnet 4 via GitHub Copilot. | |
| ## Original prompt: | |
| "Create a Python script that will process all images in the current directory | |
| by arranging them two by two on A4 pages in a landscape orientation, so that | |
| the images are side by side with a little space between. Preserve the aspect | |
| ratio of the images. If the number of images is odd, one page will contain a | |
| single image. The images may have different sizes, so white borders of various | |
| widths are expected to be around them." | |
| --- | |
| ## Description: | |
| Script to arrange images two by two on A4 landscape pages. | |
| Each page will contain up to 2 images side by side with spacing between them. | |
| ## Usage: | |
| 1. Place this script in the directory containing your images. | |
| 2. Create a virtual environment and install dependencies: | |
| python3 -m venv venv | |
| source venv/bin/activate | |
| python3 -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| 3. Run the script: python arrange_images.py | |
| 4. The output PDF "arranged_images.pdf" will be created in the same directory. | |
| """ | |
| import os | |
| import glob | |
| from PIL import Image | |
| from reportlab.pdfgen import canvas | |
| from reportlab.lib.pagesizes import A4, landscape | |
| from reportlab.lib.utils import ImageReader | |
| import io | |
| def get_image_files(directory): | |
| """Get all image files from the directory.""" | |
| image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.bmp', '*.tiff'] | |
| image_files = [] | |
| for extension in image_extensions: | |
| image_files.extend(glob.glob(os.path.join(directory, extension))) | |
| image_files.extend(glob.glob(os.path.join(directory, extension.upper()))) | |
| return sorted(image_files) | |
| def resize_image_to_fit(image, max_width, max_height): | |
| """Resize image to fit within max dimensions while maintaining aspect ratio.""" | |
| img_width, img_height = image.size | |
| # Calculate scaling factor | |
| width_ratio = max_width / img_width | |
| height_ratio = max_height / img_height | |
| scale_factor = min(width_ratio, height_ratio) | |
| # Calculate new dimensions | |
| new_width = int(img_width * scale_factor) | |
| new_height = int(img_height * scale_factor) | |
| return image.resize((new_width, new_height), Image.Resampling.LANCZOS) | |
| def create_page_with_images(image_paths, page_width, page_height): | |
| """Create a page with 1 or 2 images arranged side by side.""" | |
| # Convert points to pixels (assuming 72 DPI) | |
| page_width_px = int(page_width * 2) # Higher resolution for better quality | |
| page_height_px = int(page_height * 2) | |
| # Create a white background | |
| page_image = Image.new('RGB', (page_width_px, page_height_px), 'white') | |
| # Define margins and spacing | |
| margin = int(page_width_px * 0.05) # 5% margin | |
| spacing = int(page_width_px * 0.02) # 2% spacing between images | |
| if len(image_paths) == 1: | |
| # Single image - center it on the page | |
| img = Image.open(image_paths[0]) | |
| # Available space for the image | |
| available_width = page_width_px - 2 * margin | |
| available_height = page_height_px - 2 * margin | |
| # Resize image to fit | |
| resized_img = resize_image_to_fit(img, available_width, available_height) | |
| # Calculate position to center the image | |
| x = (page_width_px - resized_img.width) // 2 | |
| y = (page_height_px - resized_img.height) // 2 | |
| page_image.paste(resized_img, (x, y)) | |
| elif len(image_paths) == 2: | |
| # Two images side by side | |
| available_width = (page_width_px - 2 * margin - spacing) // 2 | |
| available_height = page_height_px - 2 * margin | |
| for i, img_path in enumerate(image_paths): | |
| img = Image.open(img_path) | |
| resized_img = resize_image_to_fit(img, available_width, available_height) | |
| # Calculate position | |
| if i == 0: # Left image | |
| x = margin + (available_width - resized_img.width) // 2 | |
| else: # Right image | |
| x = margin + available_width + spacing + (available_width - resized_img.width) // 2 | |
| y = margin + (available_height - resized_img.height) // 2 | |
| page_image.paste(resized_img, (x, y)) | |
| return page_image | |
| def create_pdf_with_images(image_files, output_filename): | |
| """Create a PDF with images arranged two per page.""" | |
| # A4 landscape dimensions in points | |
| page_width, page_height = landscape(A4) | |
| # Create PDF canvas | |
| c = canvas.Canvas(output_filename, pagesize=landscape(A4)) | |
| # Process images in pairs | |
| for i in range(0, len(image_files), 2): | |
| # Get 1 or 2 images for this page | |
| page_images = image_files[i:i+2] | |
| print(f"Processing page {i//2 + 1} with images: {[os.path.basename(img) for img in page_images]}") | |
| # Create page image | |
| page_img = create_page_with_images(page_images, page_width, page_height) | |
| # Convert PIL image to bytes for reportlab | |
| img_buffer = io.BytesIO() | |
| page_img.save(img_buffer, format='PNG', dpi=(150, 150)) | |
| img_buffer.seek(0) | |
| # Add image to PDF | |
| c.drawImage(ImageReader(img_buffer), 0, 0, width=page_width, height=page_height) | |
| # Start new page if not the last page | |
| if i + 2 < len(image_files): | |
| c.showPage() | |
| # Save PDF | |
| c.save() | |
| print(f"PDF saved as: {output_filename}") | |
| def main(): | |
| """Main function to arrange images in PDF.""" | |
| current_dir = os.path.dirname(os.path.abspath(__file__)) | |
| # Get all image files in the current directory | |
| image_files = get_image_files(current_dir) | |
| if not image_files: | |
| print("No image files found in the current directory!") | |
| return | |
| print(f"Found {len(image_files)} image files:") | |
| for img in image_files: | |
| print(f" - {os.path.basename(img)}") | |
| # Create output filename | |
| output_filename = os.path.join(current_dir, "arranged_images.pdf") | |
| # Create PDF | |
| create_pdf_with_images(image_files, output_filename) | |
| # Calculate number of pages | |
| num_pages = (len(image_files) + 1) // 2 | |
| print(f"\nCreated {num_pages} page(s) with {len(image_files)} images") | |
| print(f"Output file: {output_filename}") | |
| if __name__ == "__main__": | |
| main() |
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
| Pillow>=10.0.0 | |
| reportlab>=4.0.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment