File size: 9,807 Bytes
80c945e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/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]      -> <out>/*.png|jpg + <out>/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()