"""Compose a collage from three extension screenshots. Layout (with padding and gaps): +--padding--+---gap---+-----padding-----+ | | | | | popup | | disabled (right) | | (left) | +---------gap------+ | | | enabled (right) | | | | | +--padding--+---------+-----padding-----+ Usage: python docs/make_collage.py """ import math from pathlib import Path from PIL import Image, ImageDraw, ImageFilter DOCS = Path(__file__).parent PAD = 20 GAP = 14 GRAD_START = (136, 136, 136) GRAD_END = (87, 87, 87) GRAD_ANGLE_DEG = 45 POPUP_COL_W = 230 BORDER_COLOR = (17, 17, 17) BORDER_W = 1 CORNER_R = 4 SHADOW_OFFSET = (4, 4) SHADOW_BLUR = 8 SHADOW_COLOR = (0, 0, 0, 110) def scale_and_crop(img, target_w, target_h): """Scale image to cover target box, then crop from top-left.""" scale = max(target_w / img.width, target_h / img.height) new_w = round(img.width * scale) new_h = round(img.height * scale) img = img.resize((new_w, new_h), Image.LANCZOS) return img.crop((0, 0, target_w, target_h)) def make_gradient(w, h): """Create a linear gradient background at GRAD_ANGLE_DEG degrees.""" angle = math.radians(GRAD_ANGLE_DEG) dx, dy = math.cos(angle), math.sin(angle) pixels = [] for y in range(h): for x in range(w): t = (x * dx + y * dy) / (w * dx + h * dy) t = max(0.0, min(1.0, t)) r = round(GRAD_START[0] + (GRAD_END[0] - GRAD_START[0]) * t) g = round(GRAD_START[1] + (GRAD_END[1] - GRAD_START[1]) * t) b = round(GRAD_START[2] + (GRAD_END[2] - GRAD_START[2]) * t) pixels.append((r, g, b)) img = Image.new("RGB", (w, h)) img.putdata(pixels) return img def round_corners(img, radius): """Apply rounded corners using an alpha mask.""" img = img.convert("RGBA") mask = Image.new("L", img.size, 255) draw = ImageDraw.Draw(mask) draw.rectangle([0, 0, radius, radius], fill=0) draw.rectangle([img.width - radius, 0, img.width, radius], fill=0) draw.rectangle([0, img.height - radius, radius, img.height], fill=0) draw.rectangle([img.width - radius, img.height - radius, img.width, img.height], fill=0) draw.pieslice([0, 0, radius * 2, radius * 2], 180, 270, fill=255) draw.pieslice([img.width - radius * 2, 0, img.width, radius * 2], 270, 360, fill=255) draw.pieslice([0, img.height - radius * 2, radius * 2, img.height], 90, 180, fill=255) draw.pieslice([img.width - radius * 2, img.height - radius * 2, img.width, img.height], 0, 90, fill=255) img.putalpha(mask) return img def add_border(img, color, width, radius): """Draw a rounded-rect border on an RGBA image.""" draw = ImageDraw.Draw(img) draw.rounded_rectangle( [0, 0, img.width - 1, img.height - 1], radius=radius, outline=color, width=width, ) return img def make_shadow(w, h, radius): """Create a drop shadow image for a panel of size w x h.""" spread = SHADOW_BLUR * 2 sw = w + spread * 2 sh = h + spread * 2 shadow = Image.new("RGBA", (sw, sh), (0, 0, 0, 0)) draw = ImageDraw.Draw(shadow) draw.rounded_rectangle( [spread, spread, spread + w, spread + h], radius=radius, fill=SHADOW_COLOR, ) shadow = shadow.filter(ImageFilter.GaussianBlur(SHADOW_BLUR)) return shadow def paste_panel(canvas, panel_img, x, y): """Paste a panel onto the canvas with rounded corners, border, and drop shadow.""" w, h = panel_img.size panel = round_corners(panel_img, CORNER_R) panel = add_border(panel, BORDER_COLOR, BORDER_W, CORNER_R) shadow = make_shadow(w, h, CORNER_R) spread = SHADOW_BLUR * 2 sx = x + SHADOW_OFFSET[0] - spread sy = y + SHADOW_OFFSET[1] - spread canvas.paste(shadow, (sx, sy), shadow) canvas.paste(panel, (x, y), panel) ASPECT_RATIO = (4, 3) MAX_DIMENSION = 1200 def main(): popup = Image.open(DOCS / "reddit-extension-popup.png") disabled = Image.open(DOCS / "reddit-extension-disabled.png") enabled = Image.open(DOCS / "reddit-extension-enabled.png") popup_scale = POPUP_COL_W / popup.width popup_natural_h = round(popup.height * popup_scale) right_col_w = 700 right_panel_h = (popup_natural_h - GAP) // 2 content_h = popup_natural_h content_w = POPUP_COL_W + GAP + right_col_w shadow_extra = max(SHADOW_OFFSET[0], SHADOW_OFFSET[1]) + SHADOW_BLUR inner_w = content_w + 2 * PAD + shadow_extra inner_h = content_h + 2 * PAD + shadow_extra aspect_w, aspect_h = ASPECT_RATIO target_w = max(inner_w, round(inner_h * aspect_w / aspect_h)) target_h = max(inner_h, round(target_w * aspect_h / aspect_w)) # Clamp to MAX_DIMENSION, scaling down proportionally if max(target_w, target_h) > MAX_DIMENSION: scale_down = MAX_DIMENSION / max(target_w, target_h) target_w = round(target_w * scale_down) target_h = round(target_h * scale_down) inner_w = round(inner_w * scale_down) inner_h = round(inner_h * scale_down) popup_natural_h = round(popup_natural_h * scale_down) right_col_w = round(right_col_w * scale_down) right_panel_h = round(right_panel_h * scale_down) popup_col_w = round(POPUP_COL_W * scale_down) pad = round(PAD * scale_down) gap = round(GAP * scale_down) else: popup_col_w = POPUP_COL_W pad = PAD gap = GAP canvas = make_gradient(target_w, target_h).convert("RGBA") x_off = (target_w - inner_w) // 2 y_off = (target_h - inner_h) // 2 popup_resized = popup.resize((popup_col_w, popup_natural_h), Image.LANCZOS) paste_panel(canvas, popup_resized, x_off + pad, y_off + pad) rx = x_off + pad + popup_col_w + gap paste_panel(canvas, scale_and_crop(disabled, right_col_w, right_panel_h), rx, y_off + pad) paste_panel(canvas, scale_and_crop(enabled, right_col_w, right_panel_h), rx, y_off + pad + right_panel_h + gap) out = DOCS / "reddit-extension-collage.png" canvas.convert("RGB").save(out, optimize=True) print(f"Saved {out} ({target_w}x{target_h})") print(f"Popup: {popup_col_w}x{popup_natural_h} (showing 100%)") print(f"Right panels: {right_col_w}x{right_panel_h} each") if __name__ == "__main__": main()