#!/usr/bin/env python3 # Copyright 2026 Everpaper. Licensed under the Apache License, Version 2.0 (see ../LICENSE). """Render the small fictional image set shipped in tests/images/ (Pillow only, no randomness): a bar chart, a phone login screen, a slide, an invoice, a flow diagram, a sunset scene (JPEG), a red circle, an RGBA icon, a grayscale letter, a palette chart, plus two edge-size images (7x5 and 1x300 px) that exercise the resampler's upscale and extreme-aspect paths. All names, companies and numbers are invented. python3 make_test_images.py [--out images] -> /*.png|jpg + /manifest.json The output is repeatable on one machine but NOT byte-identical across machines: text is drawn with the first system font found in FONT_PATHS (or Pillow's built-in font), and glyph rasterization, PNG compression and JPEG encoding depend on the font files and on the Pillow / FreeType / zlib / libjpeg versions. The shipped files in tests/images/ are the reference: image-smoke-reference.json and preprocess-cases.json were computed from them, so regenerate those two fixtures (make_image_smoke_reference.py, make_preprocess_cases.py) if you regenerate the images. """ from __future__ import annotations import argparse import json from pathlib import Path from PIL import Image, ImageDraw, ImageFont FONT_PATHS = ["/System/Library/Fonts/Helvetica.ttc", "/System/Library/Fonts/Supplemental/Arial.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"] def font(size: int): for p in FONT_PATHS: try: return ImageFont.truetype(p, size) except OSError: continue return ImageFont.load_default() def chart_bar(w=640, h=400): im = Image.new("RGB", (w, h), "white") d = ImageDraw.Draw(im) d.text((20, 12), "Lumen lamps - units sold per city, Q3", fill="black", font=font(20)) cities = ["Port Avelin", "North Corra", "Westmere", "Lake Tamsin", "Oskaro"] vals = [412, 263, 538, 190, 347] cols = ["#4C78A8", "#F58518", "#54A24B", "#E45756", "#72B7B2"] d.line([(60, h - 50), (w - 20, h - 50)], fill="black", width=2) d.line([(60, 50), (60, h - 50)], fill="black", width=2) bw = (w - 100) // len(cities) for i, (c, v, col) in enumerate(zip(cities, vals, cols)): x0 = 70 + i * bw d.rectangle([x0, h - 50 - v * 0.5, x0 + bw - 20, h - 50], fill=col) d.text((x0, h - 44), c, fill="black", font=font(13)) d.text((x0 + 5, h - 50 - v * 0.5 - 18), str(v), fill="black", font=font(13)) return im def ui_login(w=390, h=844): im = Image.new("RGB", (w, h), "#111418") d = ImageDraw.Draw(im) d.rounded_rectangle([40, 160, w - 40, 300], 24, fill="#1d2229") d.text((60, 180), "Welcome back", fill="white", font=font(28)) d.text((60, 230), "Sign in to Tidal Boards", fill="#9aa4b1", font=font(16)) for i, label in enumerate(["Email", "Password"]): y = 340 + i * 90 d.rounded_rectangle([40, y, w - 40, y + 56], 14, fill="#1d2229", outline="#2f3842") d.text((56, y + 18), label, fill="#6b7682", font=font(16)) d.rounded_rectangle([40, 540, w - 40, 596], 14, fill="#2f6fed") d.text((150, 556), "Sign in", fill="white", font=font(18)) d.text((110, 620), "Forgot your password?", fill="#9aa4b1", font=font(14)) return im def slide(w=1280, h=720): im = Image.new("RGB", (w, h), "#f6f3ec") d = ImageDraw.Draw(im) d.rectangle([0, 0, w, 90], fill="#264653") d.text((50, 25), "Nimbus tents: 2027 launch plan", fill="white", font=font(36)) bullets = ["Three sizes, one pole system", "Pre-orders open in March", "Retail partners in six cities", "Target: 18,000 units in the first season"] for i, b in enumerate(bullets): d.ellipse([70, 160 + i * 90, 90, 180 + i * 90], fill="#2a9d8f") d.text((110, 150 + i * 90), b, fill="#222", font=font(30)) d.text((w - 260, h - 50), "slide 4 of 12", fill="#777", font=font(18)) return im def doc_invoice(w=595, h=842): im = Image.new("RGB", (w, h), "white") d = ImageDraw.Draw(im) d.text((40, 40), "INVOICE No. 2027-0418", fill="black", font=font(22)) d.text((40, 80), "Quill Pens Ltd., 12 Harbour Lane, Port Avelin", fill="black", font=font(13)) d.text((40, 100), "Bill to: Westmere Public Library", fill="black", font=font(13)) y = 160 d.line([(40, y), (w - 40, y)], fill="black") rows = [("Fountain pen, steel nib", 24, "18.00"), ("Ink cartridges (box of 50)", 6, "9.50"), ("Gift boxes", 24, "2.20"), ("Shipping", 1, "14.00")] for name, qty, price in rows: y += 30 d.text((40, y), name, fill="black", font=font(13)) d.text((380, y), str(qty), fill="black", font=font(13)) d.text((460, y), price, fill="black", font=font(13)) y += 50 d.line([(40, y), (w - 40, y)], fill="black") d.text((380, y + 15), "Total: 555.80", fill="black", font=font(15)) d.text((40, h - 80), "Payment within 30 days. Thank you for your order.", fill="black", font=font(12)) return im def diagram_flow(w=800, h=500): im = Image.new("RGB", (w, h), "white") d = ImageDraw.Draw(im) boxes = [("Order received", 60, 200), ("Check stock", 300, 200), ("Pack", 540, 120), ("Back-order", 540, 280)] for t, x, y in boxes: d.rounded_rectangle([x, y, x + 180, y + 70], 12, fill="#dbeafe", outline="#1e3a8a", width=2) d.text((x + 15, y + 25), t, fill="#1e3a8a", font=font(17)) for a, b in [((240, 235), (300, 235)), ((480, 220), (540, 155)), ((480, 250), (540, 315))]: d.line([a, b], fill="#1e3a8a", width=3) d.polygon([b, (b[0] - 12, b[1] - 6), (b[0] - 12, b[1] + 6)], fill="#1e3a8a") d.text((320, 60), "Fulfilment flow", fill="black", font=font(24)) return im def scene_sunset(w=800, h=533): im = Image.new("RGB", (w, h)) d = ImageDraw.Draw(im) for y in range(h): t = y / h if t < 0.6: c = (int(40 + 200 * t / 0.6), int(60 + 90 * t / 0.6), int(120 - 80 * t / 0.6)) else: c = (int(20 + 30 * (t - 0.6) / 0.4), int(50 + 40 * (t - 0.6) / 0.4), int(90 + 60 * (t - 0.6) / 0.4)) d.line([(0, y), (w, y)], fill=c) d.ellipse([w // 2 - 60, int(h * 0.6) - 60, w // 2 + 60, int(h * 0.6) + 60], fill="#ffb347") d.rectangle([0, int(h * 0.6), w, h], fill=None) for i in range(0, w, 40): d.line([(i, int(h * 0.62) + (i % 80) // 20), (i + 24, int(h * 0.62) + (i % 80) // 20)], fill="#ffd27f", width=2) return im def shape_red_circle(w=300, h=300): im = Image.new("RGB", (w, h), "white") ImageDraw.Draw(im).ellipse([50, 50, 250, 250], fill="#d62828") return im def icon_rgba(w=256, h=256): im = Image.new("RGBA", (w, h), (0, 0, 0, 0)) d = ImageDraw.Draw(im) d.rounded_rectangle([20, 20, 236, 236], 48, fill=(47, 111, 237, 255)) d.polygon([(100, 75), (100, 181), (185, 128)], fill=(255, 255, 255, 255)) return im def gray_letter(w=600, h=800): im = Image.new("L", (w, h), 245) d = ImageDraw.Draw(im) lines = ["Dear neighbours,", "", "The community garden on Fern Street reopens on the first Saturday of May.", "Plots are assigned in the order of the sign-up sheet at the library.", "Tools stay in the shed; please", "return them before dusk.", "", "With thanks, the garden committee"] for i, l in enumerate(lines): d.text((50, 60 + i * 32), l, fill=20, font=font(16)) return im def palette_chart(w=500, h=320): im = Image.new("RGB", (w, h), "white") d = ImageDraw.Draw(im) d.text((20, 10), "Sales channel share", fill="black", font=font(18)) start = 0 for frac, col in [(0.45, "#1b9e77"), (0.3, "#d95f02"), (0.25, "#7570b3")]: d.pieslice([120, 60, 380, 300], start, start + frac * 360, fill=col) start += frac * 360 return im.quantize(16) def build(out: Path) -> list[dict]: out.mkdir(parents=True, exist_ok=True) items = [] def save(name, im, query, fmt="PNG", **kw): fn = f"{name}.{'jpg' if fmt == 'JPEG' else 'png'}" im.save(out / fn, format=fmt, **kw) with Image.open(out / fn) as chk: items.append(dict(name=name, file=fn, query=query, width=chk.width, height=chk.height, mode=chk.mode, lossy=fmt == "JPEG")) save("chart_bar", chart_bar(), "bar chart of lamp units sold per city") save("ui_login", ui_login(), "dark mobile login screen with email and password fields") save("slide_launch", slide(), "presentation slide about a tent product launch plan") save("doc_invoice", doc_invoice(), "invoice for fountain pens and ink cartridges") save("diagram_flow", diagram_flow(), "flow diagram of order fulfilment steps") save("scene_sunset", scene_sunset(), "sunset over the sea with an orange sun", fmt="JPEG", quality=90) save("shape_red_circle", shape_red_circle(), "a red circle on a white background") save("icon_rgba", icon_rgba(), "blue rounded app icon with a white play symbol") save("gray_letter", gray_letter(), "letter about a community garden reopening") save("palette_chart", palette_chart(), "pie chart of sales channel share") save("edge_tiny_7x5", shape_red_circle().resize((7, 5), Image.BICUBIC), "a red circle on a white background") save("edge_strip_1x300", ui_login().resize((1, 300), Image.BICUBIC), "a thin vertical strip") return items def main() -> None: ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--out", default=str(Path(__file__).resolve().parent / "images")) args = ap.parse_args() items = build(Path(args.out)) (Path(args.out) / "manifest.json").write_text(json.dumps(items, indent=1) + "\n") print(len(items), "images ->", args.out) if __name__ == "__main__": main()