From 07-presentations
Batch-resizes images for web (WebP 1920/1200/400px), social (center-cropped WebP for Instagram/Twitter/LinkedIn), slides (JPEG 1024x768/1920x1080), print (300 DPI ARCH JPEGs). Outputs to subfolders.
npx claudepluginhub alpacalabsllc/skills-for-architects --plugin 07-presentationsThis skill is limited to using the following tools:
Resize project photos and renders for web publishing, social media, and print layouts. Always asks the user for the source folder before doing anything. Outputs resized copies into clearly named subfolders — originals are never modified.
Processes web images: resize, crop, trim whitespace, convert PNG/WebP/JPG, optimize size, generate thumbnails/OG cards via Python Pillow CLI or scripts.
Convert and manipulate images using ImageMagick: format conversion, resizing, batch processing, thumbnails, quality adjustment, rotate, flip, crop.
Generates or edits raster images using AI for bitmap visuals like photos, illustrations, textures, sprites, mockups in projects. Uses built-in image_gen tool; CLI fallback requires OPENAI_API_KEY. Avoid vector/SVG/code assets.
Share bugs, ideas, or general feedback.
Resize project photos and renders for web publishing, social media, and print layouts. Always asks the user for the source folder before doing anything. Outputs resized copies into clearly named subfolders — originals are never modified.
Before doing anything else, ask:
"Which folder contains the images you'd like to resize?"
Wait for the user's response. Accept any valid path — absolute, relative, or with ~. Expand ~ to the user's home directory.
Then ask:
"Which outputs do you need? (choose one or more)
- Web — WebP at 1920px (hero), 1200px (standard), 400px (thumb)
- Social — center-cropped WebP: Instagram square (1080×1080), Instagram portrait (1080×1350), Twitter/X (1200×675), LinkedIn (1200×627)
- Slides — center-cropped JPEG: standard 4:3 (1024×768), widescreen 16:9 (1920×1080)
- Print — 300 DPI JPEG at ARCH A (9×12), ARCH B (12×18), ARCH C (18×24)
- All"
List all image files in the folder (non-recursive). Supported formats: .jpg, .jpeg, .png, .tif, .tiff, .webp.
import os, sys
folder = sys.argv[1]
exts = {'.jpg', '.jpeg', '.png', '.tif', '.tiff', '.webp'}
images = [f for f in os.listdir(folder) if os.path.splitext(f.lower())[1] in exts]
images.sort()
for img in images:
print(img)
Report the count: "Found N image(s) in [folder]. Ready to resize."
If the folder is empty or contains no supported images, tell the user and stop.
Run this to confirm Pillow is available:
python3 -c "import PIL; print('ok')" 2>/dev/null || echo "missing"
If missing, tell the user:
"Pillow isn't installed. Run
pip install Pillowthen try again."
Stop if Pillow is unavailable.
Inside the source folder, create one subfolder per requested mode:
resized-web/ — if web sizes were requestedresized-social/ — if social sizes were requestedresized-slides/ — if slides sizes were requestedresized-print/ — if print sizes were requestedimport os, sys
folder = sys.argv[1]
modes = sys.argv[2:] # 'web', 'social', 'slides', and/or 'print'
for mode in modes:
os.makedirs(os.path.join(folder, f"resized-{mode}"), exist_ok=True)
Run this Python script via Bash, passing the folder path and modes as arguments:
import sys
import os
from PIL import Image
folder = sys.argv[1]
modes = sys.argv[2:] # 'web', 'social', and/or 'print'
exts = {'.jpg', '.jpeg', '.png', '.tif', '.tiff', '.webp'}
images = [f for f in os.listdir(folder) if os.path.splitext(f.lower())[1] in exts]
WEB_SIZES = [
("hero", 1920),
("standard", 1200),
("thumb", 400),
]
# Social: exact crop dimensions (width, height)
SOCIAL_SIZES = [
("social-square", (1080, 1080)),
("social-portrait", (1080, 1350)),
("social-landscape", (1200, 675)),
("social-linkedin", (1200, 627)),
]
# Slides: center-crop to fill slide canvas
SLIDES_SIZES = [
("slides-standard", (1024, 768)), # 4:3
("slides-wide", (1920, 1080)), # 16:9
]
# ARCH sizes in pixels at 300 DPI: inches × 300
PRINT_SIZES = [
("arch-a", (9 * 300, 12 * 300)), # 2700 × 3600
("arch-b", (12 * 300, 18 * 300)), # 3600 × 5400
("arch-c", (18 * 300, 24 * 300)), # 5400 × 7200
]
def center_crop(img, target_w, target_h):
"""Scale to fill target dimensions, then center-crop."""
src_w, src_h = img.size
scale = max(target_w / src_w, target_h / src_h)
scaled_w = int(src_w * scale)
scaled_h = int(src_h * scale)
img = img.resize((scaled_w, scaled_h), Image.LANCZOS)
left = (scaled_w - target_w) // 2
top = (scaled_h - target_h) // 2
return img.crop((left, top, left + target_w, top + target_h))
for filename in images:
stem = os.path.splitext(filename)[0]
src_path = os.path.join(folder, filename)
try:
img = Image.open(src_path)
# Convert to RGB if needed (handles RGBA, palette, etc.)
if img.mode not in ("RGB", "L"):
img = img.convert("RGB")
print(f"Processing: {filename} ({img.width}×{img.height})")
if "web" in modes:
web_dir = os.path.join(folder, "resized-web")
for label, max_width in WEB_SIZES:
out_img = img.copy()
if out_img.width > max_width:
ratio = max_width / out_img.width
new_size = (max_width, int(out_img.height * ratio))
out_img = out_img.resize(new_size, Image.LANCZOS)
out_path = os.path.join(web_dir, f"{stem}-{label}.webp")
out_img.save(out_path, "WEBP", quality=82)
size_kb = os.path.getsize(out_path) // 1024
print(f" {stem}-{label}.webp ({out_img.width}×{out_img.height}) {size_kb} KB")
if "social" in modes:
social_dir = os.path.join(folder, "resized-social")
for label, (tw, th) in SOCIAL_SIZES:
out_img = center_crop(img.copy(), tw, th)
out_path = os.path.join(social_dir, f"{stem}-{label}.webp")
out_img.save(out_path, "WEBP", quality=85)
size_kb = os.path.getsize(out_path) // 1024
print(f" {stem}-{label}.webp ({out_img.width}×{out_img.height}) {size_kb} KB")
if "slides" in modes:
slides_dir = os.path.join(folder, "resized-slides")
for label, (tw, th) in SLIDES_SIZES:
out_img = center_crop(img.copy(), tw, th)
out_path = os.path.join(slides_dir, f"{stem}-{label}.jpg")
out_img.save(out_path, "JPEG", quality=92)
size_kb = os.path.getsize(out_path) // 1024
print(f" {stem}-{label}.jpg ({out_img.width}×{out_img.height}) {size_kb} KB")
if "print" in modes:
print_dir = os.path.join(folder, "resized-print")
for label, (pw, ph) in PRINT_SIZES:
out_img = img.copy()
# Fit within the target box, preserving aspect ratio
out_img.thumbnail((pw, ph), Image.LANCZOS)
out_path = os.path.join(print_dir, f"{stem}-{label}.jpg")
out_img.save(out_path, "JPEG", quality=95, dpi=(300, 300))
size_kb = os.path.getsize(out_path) // 1024
print(f" {stem}-{label}.jpg ({out_img.width}×{out_img.height}) {size_kb} KB")
except Exception as e:
print(f"ERROR: {filename} — {e}")
print("\nDone.")
Give a progress update for each file as it completes.
After all files are processed, show a summary:
Resized N image(s) → [folder]/resized-web/, resized-social/, resized-print/
Web (resized-web/):
project-photo-hero.webp (1920×1385) 138 KB
project-photo-standard.webp (1200×866) 62 KB
project-photo-thumb.webp (400×288) 11 KB
Social (resized-social/):
project-photo-social-square.webp (1080×1080) 74 KB
project-photo-social-portrait.webp (1080×1350) 91 KB
project-photo-social-landscape.webp (1200×675) 65 KB
project-photo-social-linkedin.webp (1200×627) 63 KB
Print (resized-print/):
project-photo-arch-a.jpg (2700×1949) 959 KB
project-photo-arch-b.jpg (3600×2599) 1698 KB
project-photo-arch-c.jpg (5019×3623) 3470 KB
If any files failed, list them with their error messages.