返回 DeepSeek-Reasonix
scenes.py
1 """Per-theme scene generators. Each returns a 2560x1440 RGBA image.
2
3 Layout contract (from the theme plan):
4 - left 0-52% of the width stays a low-information zone
5 - main visual centre sits at x 68-76%
6 - key content stays inside x 62-88%, y 16-72%
7 - no windows/sidebars/cards/buttons/inputs, no readable text, no logos
8 """
9 from __future__ import annotations
10
11 import math
12
13 from PIL import Image, ImageDraw, ImageFilter
14
15 from artkit import (
16 H,
17 W,
18 beam,
19 butterfly_pts,
20 cloud_curl_pts,
21 coin_pts,
22 comp,
23 cubic,
24 draw_poly,
25 ellipse_poly,
26 glow,
27 gradient,
28 hex2rgb,
29 leaf_pts,
30 mix,
31 new_layer,
32 petal_pts,
33 rgba,
34 ring_pts,
35 rng,
36 smooth_path,
37 star4,
38 superellipse_poly,
39 )
40 from figures import (
41 anime_adult,
42 man_bust,
43 mascot_programmer,
44 performer,
45 silhouette_muse,
46 woman_serene,
47 )
48
49
50 def _rose(d, cx, cy, r, deep, mid, light, seed=0):
51 """Layered flat rose head."""
52 rr = rng(seed)
53 for ring_i, (frac, col) in enumerate([(1.0, mid), (0.72, light), (0.46, mid), (0.26, deep)]):
54 n = 7 if ring_i < 2 else 5
55 for i in range(n):
56 ang = (2 * math.pi * i / n) + ring_i * 0.45 + rr.uniform(-0.06, 0.06)
57 px = cx + math.cos(ang) * r * frac * 0.34
58 py = cy + math.sin(ang) * r * frac * 0.34
59 d.polygon(petal_pts(px, py, r * frac * 0.72, ang + math.pi / 2), fill=rgba(col, 255))
60 # centre spiral
61 pts = []
62 for i in range(40):
63 t = i / 39
64 a = 4.6 * math.pi * t
65 pts.append((cx + math.cos(a) * r * 0.20 * (1 - t * 0.75), cy + math.sin(a) * r * 0.20 * (1 - t * 0.75)))
66 d.line(pts, fill=rgba(deep, 220), width=max(2, int(r * 0.05)), joint="curve")
67
68
69 def scene_rose_dawn() -> "Image":
70 R = rng(20260117)
71 img = gradient(W, H, [(0.0, "#FFF9F5"), (0.45, "#FDF0F1"), (0.8, "#F9E0E4"), (1.0, "#F5D3DA")], "d1")
72 glow(img, 0.78 * W, 0.30 * H, 0.55 * W, hex2rgb("#FFFFFF"), 70)
73 glow(img, 0.86 * W, 0.72 * H, 0.38 * W, hex2rgb("#F2B7C4"), 60)
74
75 # faint drifting petals on the low-info left
76 lay = new_layer()
77 d = ImageDraw.Draw(lay)
78 for _ in range(9):
79 x = R.uniform(0.04, 0.48) * W
80 y = R.uniform(0.10, 0.92) * H
81 s = R.uniform(26, 52)
82 d.polygon(petal_pts(x, y, s, R.uniform(0, 6.28)), fill=rgba(hex2rgb("#EFC4CD"), R.randint(28, 55)))
83 comp(img, lay, 2)
84
85 # woman
86 woman_serene(
87 img, 0.722 * W, 0.40 * H, 152,
88 skin=hex2rgb("#F6D8C6"), hair_c=hex2rgb("#5E3A36"),
89 cloth_c=hex2rgb("#FCEFEA"), lip_c=hex2rgb("#C45B73"), blush_c=hex2rgb("#EBA9B5"),
90 style="long",
91 )
92
93 # rose cluster bottom-right + one at shoulder height
94 lay = new_layer()
95 d = ImageDraw.Draw(lay)
96 roses = [
97 (0.865 * W, 0.82 * H, 150, 1),
98 (0.940 * W, 0.62 * H, 108, 2),
99 (0.780 * W, 0.93 * H, 124, 3),
100 (0.925 * W, 0.95 * H, 84, 4),
101 (0.620 * W, 0.90 * H, 56, 5),
102 ]
103 deep, mid, light = hex2rgb("#B43F65"), hex2rgb("#E28CA1"), hex2rgb("#F5C3CE")
104 leaf_c = hex2rgb("#8A9A7B")
105 for cx, cy, r, seed in roses:
106 for i in range(3):
107 ang = R.uniform(0, 6.28)
108 d.polygon(leaf_pts(cx + math.cos(ang) * r * 0.9, cy + math.sin(ang) * r * 0.9, r * 0.85, r * 0.30, ang), fill=rgba(leaf_c, 235))
109 _rose(d, cx, cy, r, deep, mid, light, seed=seed)
110 # stems curling from cluster
111 stem = cubic((0.86 * W, 0.98 * H), (0.83 * W, 0.90 * H), (0.84 * W, 0.84 * H), (0.855 * W, 0.80 * H), n=30)
112 d.line(stem, fill=rgba(mix(leaf_c, "#000000", 0.15), 200), width=10, joint="curve")
113 comp(img, lay, 0.8)
114
115 # loose petals around the figure (right half only)
116 lay = new_layer()
117 d = ImageDraw.Draw(lay)
118 for _ in range(14):
119 x = R.uniform(0.58, 0.97) * W
120 y = R.uniform(0.08, 0.85) * H
121 s = R.uniform(20, 46)
122 col = mid if R.random() < 0.5 else light
123 d.polygon(petal_pts(x, y, s, R.uniform(0, 6.28)), fill=rgba(col, R.randint(120, 200)))
124 for _ in range(10):
125 x, y = R.uniform(0.60, 0.98) * W, R.uniform(0.10, 0.80) * H
126 d.ellipse([x - 5, y - 5, x + 5, y + 5], fill=rgba(hex2rgb("#FFFFFF"), R.randint(90, 160)))
127 comp(img, lay, 1.2)
128
129 # soft light rays upper-right
130 lay = new_layer()
131 d = ImageDraw.Draw(lay)
132 for i, ang in enumerate([-0.5, -0.28, -0.06]):
133 x0, y0 = 0.99 * W, -0.05 * H
134 x1 = 0.60 * W + i * 0.09 * W
135 d.polygon([(x0, y0), (x0 - 0.10 * W, y0), (x1 - 0.06 * W, 0.75 * H), (x1 + 0.10 * W, 0.75 * H)], fill=rgba(hex2rgb("#FFFFFF"), 26))
136 comp(img, lay, 30)
137 return img
138
139
140 def _ingot(d, cx, cy, w, gold, deep):
141 """Abstract gold ingot (no characters)."""
142 body = smooth_path([
143 ((cx - w * 0.5, cy + w * 0.10), (cx - w * 0.62, cy - w * 0.28), (cx - w * 0.30, cy - w * 0.30), (cx - w * 0.16, cy - w * 0.12)),
144 ((cx - w * 0.16, cy - w * 0.12), (cx - w * 0.08, cy - w * 0.02), (cx + w * 0.08, cy - w * 0.02), (cx + w * 0.16, cy - w * 0.12)),
145 ((cx + w * 0.16, cy - w * 0.12), (cx + w * 0.30, cy - w * 0.30), (cx + w * 0.62, cy - w * 0.28), (cx + w * 0.5, cy + w * 0.10)),
146 ((cx + w * 0.5, cy + w * 0.10), (cx + w * 0.3, cy + w * 0.30), (cx - w * 0.3, cy + w * 0.30), (cx - w * 0.5, cy + w * 0.10)),
147 ])
148 d.polygon(body, fill=rgba(gold, 255))
149 d.ellipse([cx - w * 0.16, cy - w * 0.16, cx + w * 0.16, cy + w * 0.06], fill=rgba(mix(gold, "#ffffff", 0.35), 255))
150 d.line([(cx - w * 0.30, cy + w * 0.16), (cx + w * 0.30, cy + w * 0.16)], fill=rgba(deep, 130), width=max(2, int(w * 0.03)))
151
152
153 def scene_fortune_forge() -> "Image":
154 R = rng(20260118)
155 img = gradient(W, H, [(0.0, "#FFFBEF"), (0.5, "#FDF2D8"), (0.85, "#F8E3BC"), (1.0, "#F3D6A2")], "d1")
156 glow(img, 0.76 * W, 0.34 * H, 0.5 * W, hex2rgb("#FFF6DC"), 80)
157 glow(img, 0.88 * W, 0.80 * H, 0.36 * W, hex2rgb("#F0B64E"), 70)
158 glow(img, 0.70 * W, 0.18 * H, 0.30 * W, hex2rgb("#E8604C"), 26)
159
160 # faint auspicious clouds far left (low info)
161 lay = new_layer()
162 d = ImageDraw.Draw(lay)
163 for cx, cy, s in [(0.10 * W, 0.78 * H, 60), (0.30 * W, 0.22 * H, 46), (0.44 * W, 0.60 * H, 40)]:
164 pts, tail = cloud_curl_pts(cx, cy, s)
165 d.line(pts, fill=rgba(hex2rgb("#E4C493"), 70), width=9, joint="curve")
166 d.line(tail, fill=rgba(hex2rgb("#E4C493"), 60), width=9, joint="curve")
167 comp(img, lay, 3)
168
169 # lucky programmer mascot
170 mascot_programmer(
171 img, 0.718 * W, 0.41 * H, 152,
172 skin=hex2rgb("#F3CFAE"), hair_c=hex2rgb("#3A2A22"),
173 cloth_c=hex2rgb("#B23A2C"), accent_c=hex2rgb("#E8AD38"), glasses_c=hex2rgb("#4A3226"),
174 )
175
176 # coins, ingots, clouds, jade beads (right side)
177 lay = new_layer()
178 d = ImageDraw.Draw(lay)
179 gold, gold_deep = hex2rgb("#E8AD38"), hex2rgb("#B97F1E")
180 verm = hex2rgb("#C8402F")
181 jade = hex2rgb("#3E7C5F")
182 coins = [
183 (0.620 * W, 0.30 * H, 54, 0.3), (0.650 * W, 0.62 * H, 40, -0.4), (0.860 * W, 0.24 * H, 62, 0.2),
184 (0.920 * W, 0.46 * H, 44, 0.5), (0.600 * W, 0.82 * H, 48, -0.2), (0.880 * W, 0.70 * H, 38, 0.1),
185 ]
186 for cx, cy, r, rot in coins:
187 outer, hole = coin_pts(cx, cy, r, rot)
188 d.polygon(outer, fill=rgba(gold, 255))
189 d.polygon(hole, fill=rgba(mix(gold, "#7a5310", 0.45), 255))
190 inner, inner_hole = coin_pts(cx, cy, r * 0.74, rot)
191 d.line(inner, fill=rgba(gold_deep, 160), width=max(3, int(r * 0.06)))
192 d.polygon(inner_hole, fill=rgba(gold, 255))
193 _ingot(d, 0.845 * W, 0.90 * H, 300, gold, gold_deep)
194 _ingot(d, 0.640 * W, 0.94 * H, 210, mix(gold, "#ffffff", 0.12), gold_deep)
195 # auspicious clouds around mascot
196 for cx, cy, s, col, alp in [
197 (0.600 * W, 0.52 * H, 42, verm, 200), (0.905 * W, 0.58 * H, 60, verm, 190),
198 (0.855 * W, 0.16 * H, 44, mix(verm, gold, 0.4), 170),
199 ]:
200 pts, tail = cloud_curl_pts(cx, cy, s)
201 d.line(pts, fill=rgba(col, alp), width=11, joint="curve")
202 d.line(tail, fill=rgba(col, int(alp * 0.85)), width=11, joint="curve")
203 # jade beads strand
204 for i in range(5):
205 bx, by = 0.925 * W + math.sin(i * 0.9) * 26, 0.80 * H + i * 46
206 d.ellipse([bx - 17, by - 17, bx + 17, by + 17], fill=rgba(mix(jade, "#ffffff", 0.08 * (i % 2)), 240))
207 d.ellipse([bx - 6, by - 8, bx + 4, by + 2], fill=rgba(mix(jade, "#ffffff", 0.6), 160))
208 # sparks
209 for _ in range(16):
210 x, y = R.uniform(0.58, 0.97) * W, R.uniform(0.10, 0.92) * H
211 star4(d, x, y, R.uniform(8, 22), gold if R.random() < 0.6 else verm, R.randint(120, 210))
212 comp(img, lay, 0.6)
213
214 # warm confetti dots
215 lay = new_layer()
216 d = ImageDraw.Draw(lay)
217 for _ in range(26):
218 x, y = R.uniform(0.56, 0.98) * W, R.uniform(0.08, 0.95) * H
219 r = R.uniform(3, 8)
220 col = [gold, verm, jade][R.randint(0, 2)]
221 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(col, R.randint(60, 140)))
222 comp(img, lay, 1.5)
223 return img
224
225
226 def scene_crimson_horizon() -> "Image":
227 """Pearl-white sky + coral-red future city. No people."""
228 R = rng(20260119)
229 img = gradient(W, H, [(0.0, "#FFFAF8"), (0.5, "#FBECE9"), (0.85, "#F5D8D3"), (1.0, "#F0C9C3")], "v")
230 glow(img, 0.74 * W, 0.30 * H, 0.42 * W, hex2rgb("#FFFFFF"), 80)
231
232 # sun disc with soft rings
233 lay = new_layer()
234 d = ImageDraw.Draw(lay)
235 sun_c = hex2rgb("#F2A3A0")
236 d.ellipse([0.74 * W - 150, 0.28 * H - 150, 0.74 * W + 150, 0.28 * H + 150], fill=rgba(mix(sun_c, "#ffffff", 0.25), 255))
237 for rr, aa in [(210, 70), (280, 45)]:
238 d.ellipse([0.74 * W - rr, 0.28 * H - rr, 0.74 * W + rr, 0.28 * H + rr], outline=rgba(sun_c, aa), width=6)
239 comp(img, lay, 1.5)
240 glow(img, 0.74 * W, 0.28 * H, 320, sun_c, 46)
241
242 # distant faint hills across the width (kept very light)
243 lay = new_layer()
244 d = ImageDraw.Draw(lay)
245 hills = smooth_path([
246 ((0, 0.74 * H), (0.18 * W, 0.68 * H), (0.36 * W, 0.72 * H), (0.52 * W, 0.66 * H)),
247 ((0.52 * W, 0.66 * H), (0.72 * W, 0.60 * H), (0.88 * W, 0.66 * H), (W, 0.62 * H)),
248 ((W, 0.62 * H), (W, 0.80 * H), (0.5 * W, 0.82 * H), (0, 0.80 * H)),
249 ])
250 d.polygon(hills, fill=rgba(hex2rgb("#EFCFC9"), 110))
251 comp(img, lay, 6)
252
253 # future city skyline, right half only
254 deep = hex2rgb("#B92B38")
255 mid = hex2rgb("#D3606A")
256 light = hex2rgb("#E89B9B")
257 pale = hex2rgb("#F3C2BC")
258 base_y = 0.985 * H
259
260 lay = new_layer()
261 d = ImageDraw.Draw(lay)
262
263 def tower(x, w, h, col, style="flat"):
264 pts = [(x, base_y), (x, base_y - h)]
265 if style == "step":
266 pts += [(x + w * 0.18, base_y - h), (x + w * 0.18, base_y - h * 1.12), (x + w * 0.82, base_y - h * 1.12), (x + w * 0.82, base_y - h)]
267 elif style == "spire":
268 pts += [(x + w * 0.30, base_y - h), (x + w * 0.5, base_y - h * 1.38), (x + w * 0.70, base_y - h)]
269 elif style == "dome":
270 arc = ellipse_poly(x + w / 2, base_y - h, w / 2, w * 0.62, a0=math.pi, a1=2 * math.pi)
271 pts += arc
272 elif style == "slant":
273 pts += [(x + w * 0.85, base_y - h * 1.06)]
274 pts += [(x + w, base_y - h if style != "slant" else base_y - h * 0.9), (x + w, base_y)]
275 d.polygon(pts, fill=rgba(col, 255))
276 return x + w
277
278 # back row (pale)
279 x = 0.535 * W
280 for w, h, st in [(120, 300, "flat"), (90, 420, "slant"), (150, 260, "dome"), (80, 520, "spire"), (130, 340, "step"), (100, 240, "flat"), (140, 400, "slant"), (95, 300, "dome"), (120, 360, "step")]:
281 x = tower(x, w * 1.5, h * 1.05, pale, st) + 18
282 comp(img, lay, 1.2)
283
284 # mid row (light/mid)
285 lay = new_layer()
286 d = ImageDraw.Draw(lay)
287 x = 0.56 * W
288 for w, h, st, col in [(110, 430, "step", light), (85, 600, "spire", mid), (140, 380, "dome", light), (95, 700, "slant", mid), (120, 470, "step", light), (80, 560, "spire", mid), (150, 350, "flat", light), (105, 640, "slant", mid)]:
289 x = tower(x, w * 1.5, h * 1.05, col, st) + 26
290 comp(img, lay, 0.8)
291
292 # front row (deep) with light band windows
293 lay = new_layer()
294 d = ImageDraw.Draw(lay)
295 x = 0.585 * W
296 fronts = [(130, 640, "step"), (95, 820, "spire"), (160, 520, "dome"), (110, 900, "slant"), (135, 600, "step"), (100, 740, "spire"), (150, 480, "dome")]
297 for w, h, st in fronts:
298 x0 = x
299 x = tower(x, w * 1.5, h * 1.05, deep, st) + 34
300 for fy in range(int(base_y - h * 1.05 + 60), int(base_y - 60), 92):
301 d.rectangle([x0 + 14, fy, x0 + w * 1.5 - 14, fy + 12], fill=rgba(hex2rgb("#F6D9D4"), 150))
302 # monorail arc sweeping to the right edge
303 rail = cubic((0.60 * W, 0.86 * H), (0.75 * W, 0.78 * H), (0.90 * W, 0.80 * H), (1.02 * W, 0.72 * H), n=60)
304 d.line(rail, fill=rgba(mix(deep, "#000000", 0.1), 200), width=12, joint="curve")
305 comp(img, lay, 0.6)
306
307 # birds + floating particles
308 lay = new_layer()
309 d = ImageDraw.Draw(lay)
310 for bx, by, s in [(0.60 * W, 0.20 * H, 22), (0.64 * W, 0.16 * H, 16), (0.90 * W, 0.14 * H, 20), (0.86 * W, 0.22 * H, 14)]:
311 d.line(cubic((bx - s, by), (bx - s * 0.4, by - s * 0.7), (bx + s * 0.4, by - s * 0.7), (bx + s, by), n=16), fill=rgba(deep, 170), width=5, joint="curve")
312 for _ in range(14):
313 x, y = R.uniform(0.56, 0.98) * W, R.uniform(0.08, 0.55) * H
314 r = R.uniform(3, 7)
315 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(mid, R.randint(50, 110)))
316 comp(img, lay, 1.0)
317 return img
318
319
320 def _sprig(d, x, y, length, angle, n, col, col2, leaf_w=0.30):
321 """Sage sprig: curved stem with opposite oval leaves."""
322 ca, sa = math.cos(angle), math.sin(angle)
323 tip = (x + ca * length, y + sa * length)
324 bend = (x + ca * length * 0.5 - sa * length * 0.12, y + sa * length * 0.5 + ca * length * 0.12)
325 stem = cubic((x, y), bend, (bend[0] + ca * 10, bend[1] + sa * 10), tip, n=40)
326 d.line(stem, fill=rgba(mix(col, "#000000", 0.12), 230), width=7, joint="curve")
327 for i in range(n):
328 t = 0.18 + 0.78 * i / max(1, n - 1)
329 px = x + ca * length * t - sa * length * 0.10 * math.sin(t * math.pi)
330 py = y + sa * length * t + ca * length * 0.10 * math.sin(t * math.pi)
331 ll = length * (0.20 - 0.10 * t)
332 for sgn in (-1, 1):
333 la = angle + sgn * (1.15 - 0.4 * t)
334 d.polygon(leaf_pts(px, py, ll, ll * leaf_w * 2.2, la, curl=0.1), fill=rgba(col if (i + sgn) % 2 == 0 else col2, 245))
335
336
337 def scene_sage_breeze() -> "Image":
338 R = rng(20260120)
339 img = gradient(W, H, [(0.0, "#FAFAF3"), (0.55, "#F2F2E6"), (1.0, "#E7E9D8")], "d1")
340 from artkit import paper_texture
341
342 paper_texture(img, "#8a946f", alpha=7, seed=11)
343 glow(img, 0.76 * W, 0.30 * H, 0.5 * W, hex2rgb("#FFFFFF"), 60)
344 glow(img, 0.88 * W, 0.85 * H, 0.35 * W, hex2rgb("#B9CBA8"), 55)
345
346 # faint sprig silhouettes far left
347 lay = new_layer()
348 d = ImageDraw.Draw(lay)
349 sage, sage2 = hex2rgb("#47735F"), hex2rgb("#7A9B84")
350 _sprig(d, 0.10 * W, 0.95 * H, 330, -1.25, 6, sage2, sage2)
351 _sprig(d, 0.30 * W, 0.98 * H, 260, -1.05, 5, sage2, sage2)
352 alpha = lay.split()[3].point(lambda v: int(v * 0.45))
353 lay.putalpha(alpha)
354 comp(img, lay, 1.5)
355
356 # adult man
357 man_bust(
358 img, 0.722 * W, 0.40 * H, 150,
359 skin=hex2rgb("#EDCBAA"), hair_c=hex2rgb("#3B352E"),
360 cloth_c=hex2rgb("#57765F"), collar_c=hex2rgb("#EFF0E2"), style="shirt",
361 )
362
363 # open book in his hands (foreground)
364 lay = new_layer()
365 d = ImageDraw.Draw(lay)
366 bx, by, bw = 0.722 * W, 0.90 * H, 520
367 cover_l = [(bx - bw / 2 - 12, by - 66), (bx, by - 56), (bx, by + 34), (bx - bw / 2 - 12, by + 24)]
368 cover_r = [(bx + bw / 2 + 12, by - 66), (bx, by - 56), (bx, by + 34), (bx + bw / 2 + 12, by + 24)]
369 d.polygon(cover_l, fill=rgba(mix(sage, "#000000", 0.18), 255))
370 d.polygon(cover_r, fill=rgba(mix(sage, "#000000", 0.18), 255))
371 page_l = [(bx - bw / 2, by - 78), (bx, by - 68), (bx, by + 22), (bx - bw / 2, by + 12)]
372 page_r = [(bx + bw / 2, by - 78), (bx, by - 68), (bx, by + 22), (bx + bw / 2, by + 12)]
373 d.polygon(page_l, fill=rgba(hex2rgb("#FCFBF2"), 255))
374 d.polygon(page_r, fill=rgba(hex2rgb("#F2F0E0"), 255))
375 d.line([(bx, by - 68), (bx, by + 22)], fill=rgba(mix(sage, "#000000", 0.05), 200), width=5)
376 for i in range(3):
377 t = i / 2
378 yy0 = by - 60 + i * 22
379 d.line([(bx - bw * 0.42, yy0 - 6 * (1 - t)), (bx - bw * 0.07, yy0 + 2)], fill=rgba(sage2, 110), width=6)
380 d.line([(bx + bw * 0.07, yy0 + 2), (bx + bw * 0.42, yy0 - 6 * (1 - t))], fill=rgba(sage2, 110), width=6)
381 comp(img, lay, 0.5)
382
383 # sage sprigs foreground right + tall behind
384 lay = new_layer()
385 d = ImageDraw.Draw(lay)
386 _sprig(d, 0.60 * W, 0.99 * H, 380, -1.35, 7, sage, sage2)
387 _sprig(d, 0.86 * W, 1.0 * H, 430, -1.15, 8, sage, sage2)
388 _sprig(d, 0.94 * W, 0.99 * H, 340, -1.45, 6, sage2, sage)
389 _sprig(d, 0.665 * W, 0.995 * H, 300, -0.9, 5, sage2, sage)
390 comp(img, lay, 0.8)
391
392 # drifting seeds / pollen
393 lay = new_layer()
394 d = ImageDraw.Draw(lay)
395 for _ in range(18):
396 x, y = R.uniform(0.55, 0.98) * W, R.uniform(0.10, 0.85) * H
397 r = R.uniform(2.5, 6)
398 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(sage2, R.randint(60, 130)))
399 for _ in range(6):
400 x, y = R.uniform(0.05, 0.45) * W, R.uniform(0.15, 0.9) * H
401 r = R.uniform(2, 5)
402 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(sage2, R.randint(25, 50)))
403 comp(img, lay, 1.5)
404 return img
405
406
407 def _pencil(d, cx, cy, length, angle, body_c, wood_c, tip_c, eraser_c):
408 ca, sa = math.cos(angle), math.sin(angle)
409
410 def tr(p):
411 x, y = p
412 return (cx + x * ca - y * sa, cy + x * sa + y * ca)
413
414 wdt = length * 0.075
415 body = [tr(p) for p in [(-length / 2, -wdt / 2), (length * 0.32, -wdt / 2), (length * 0.32, wdt / 2), (-length / 2, wdt / 2)]]
416 d.polygon(body, fill=rgba(body_c, 255))
417 wood = [tr(p) for p in [(length * 0.32, -wdt / 2), (length * 0.44, 0), (length * 0.32, wdt / 2)]]
418 d.polygon(wood, fill=rgba(wood_c, 255))
419 tip = [tr(p) for p in [(length * 0.40, -wdt * 0.22), (length * 0.44, 0), (length * 0.40, wdt * 0.22)]]
420 d.polygon(tip, fill=rgba(tip_c, 255))
421 era = [tr(p) for p in [(-length / 2, -wdt / 2), (-length * 0.44, -wdt / 2), (-length * 0.44, wdt / 2), (-length / 2, wdt / 2)]]
422 d.polygon(era, fill=rgba(eraser_c, 255))
423 band = [tr(p) for p in [(-length * 0.44, -wdt / 2), (-length * 0.40, -wdt / 2), (-length * 0.40, wdt / 2), (-length * 0.44, wdt / 2)]]
424 d.polygon(band, fill=rgba(mix(body_c, "#000000", 0.35), 255))
425
426
427 def _washi(cx, cy, w, h, angle, col, pattern_c, style="dots"):
428 pad = 12
429 sw, sh = int(w + pad * 2), int(h + pad * 2)
430 lay = Image.new("RGBA", (sw, sh), (0, 0, 0, 0))
431 dd = ImageDraw.Draw(lay)
432 dd.polygon(superellipse_poly(sw / 2, sh / 2, w / 2, h / 2, power=6), fill=rgba(col, 215))
433 if style == "dots":
434 for i in range(-2, 3):
435 for j in (-1, 0, 1):
436 dd.ellipse([sw / 2 + i * w / 6 - 7, sh / 2 + j * h / 3 - 7, sw / 2 + i * w / 6 + 7, sh / 2 + j * h / 3 + 7], fill=rgba(pattern_c, 130))
437 else:
438 for i in range(-3, 4):
439 dd.line([(sw / 2 + i * w / 7 - h, sh / 2 - h / 2), (sw / 2 + i * w / 7 + h, sh / 2 + h / 2)], fill=rgba(pattern_c, 120), width=8)
440 lay = lay.rotate(math.degrees(angle), resample=Image.BICUBIC, expand=True)
441 out = new_layer()
442 out.alpha_composite(lay, (int(cx - lay.width / 2), int(cy - lay.height / 2)))
443 return out
444
445
446 def _paper_plane(d, cx, cy, s, angle, col):
447 ca, sa = math.cos(angle), math.sin(angle)
448
449 def tr(p):
450 x, y = p
451 return (cx + x * ca - y * sa, cy + x * sa + y * ca)
452
453 d.polygon([tr((s, 0)), tr((-s * 0.8, -s * 0.55)), tr((-s * 0.35, 0))], fill=rgba(col, 255))
454 d.polygon([tr((s, 0)), tr((-s * 0.35, 0)), tr((-s * 0.8, s * 0.55))], fill=rgba(mix(col, "#000000", 0.12), 255))
455 d.polygon([tr((-s * 0.35, 0)), tr((-s * 0.62, s * 0.12)), tr((-s * 0.5, -s * 0.10))], fill=rgba(mix(col, "#000000", 0.22), 255))
456
457
458 def _paper_clip(cx, cy, s, angle, col):
459 span = int(s * 1.6)
460 lay = Image.new("RGBA", (span, span), (0, 0, 0, 0))
461 dd = ImageDraw.Draw(lay)
462 ox, oy = span / 2, span / 2
463 w, h = s * 0.42, s
464 lw = max(3, int(s * 0.09))
465
466 def T(pts):
467 return [(px + ox, py + oy) for px, py in pts]
468
469 dd.line(T(ellipse_poly(0, -h * 0.22, w, h * 0.30, a0=math.pi, a1=2 * math.pi)), fill=rgba(col, 230), width=lw)
470 dd.line(T([(-w, -h * 0.22), (-w, h * 0.35)]), fill=rgba(col, 230), width=lw)
471 dd.line(T([(w, -h * 0.22), (w, h * 0.15)]), fill=rgba(col, 230), width=lw)
472 dd.line(T(ellipse_poly(0, h * 0.35, w, h * 0.28, a0=0, a1=math.pi)), fill=rgba(col, 230), width=lw)
473 dd.line(T([(0, -h * 0.52), (0, h * 0.10)]), fill=rgba(col, 230), width=lw)
474 lay = lay.rotate(math.degrees(angle), resample=Image.BICUBIC, expand=True)
475 out = new_layer()
476 out.alpha_composite(lay, (int(cx - lay.width / 2), int(cy - lay.height / 2)))
477 return out
478
479
480 def scene_spark_notebook() -> "Image":
481 R = rng(20260121)
482 img = gradient(W, H, [(0.0, "#FFFBF0"), (0.55, "#FCF5E2"), (1.0, "#F7ECD2")], "v")
483
484 # notebook grid (very faint) + margin line
485 lay = new_layer()
486 d = ImageDraw.Draw(lay)
487 teal = hex2rgb("#007B78")
488 for gx in range(0, W, 92):
489 d.line([(gx, 0), (gx, H)], fill=rgba(teal, 14), width=2)
490 for gy in range(0, H, 92):
491 d.line([(0, gy), (W, gy)], fill=rgba(teal, 14), width=2)
492 d.line([(0.055 * W, 0), (0.055 * W, H)], fill=rgba(hex2rgb("#EB5757"), 40), width=4)
493 comp(img, lay, 0.5)
494 glow(img, 0.76 * W, 0.32 * H, 0.48 * W, hex2rgb("#FFFFFF"), 55)
495
496 # anime adult character
497 anime_adult(
498 img, 0.722 * W, 0.41 * H, 150,
499 skin=hex2rgb("#F7DAC8"), hair_c=hex2rgb("#35ADA4"),
500 cloth_c=hex2rgb("#F2C94C"), iris_c=hex2rgb("#007B78"), style="bob",
501 )
502
503 # stationery around (right side)
504 yellow = hex2rgb("#F2C94C")
505 coral = hex2rgb("#F2994A")
506 red = hex2rgb("#EB5757")
507 teal2 = hex2rgb("#42D1C6")
508 img.alpha_composite(_washi(0.640 * W, 0.165 * H, 300, 84, -0.35, teal2, hex2rgb("#FFFFFF"), "stripes"))
509 img.alpha_composite(_washi(0.905 * W, 0.80 * H, 320, 88, 0.28, yellow, coral, "dots"))
510 img.alpha_composite(_washi(0.615 * W, 0.70 * H, 240, 76, 0.42, coral, hex2rgb("#FFFFFF"), "dots"))
511
512 lay = new_layer()
513 d = ImageDraw.Draw(lay)
514 _pencil(d, 0.880 * W, 0.205 * H, 340, -0.5, yellow, hex2rgb("#EBCB9B"), hex2rgb("#4A4A4A"), red)
515 _pencil(d, 0.640 * W, 0.885 * H, 300, 0.35, teal2, hex2rgb("#EBCB9B"), hex2rgb("#4A4A4A"), coral)
516 _paper_plane(d, 0.885 * W, 0.62 * H, 90, -0.35, red)
517 _paper_plane(d, 0.635 * W, 0.30 * H, 66, 0.25, coral)
518 for _ in range(14):
519 x, y = R.uniform(0.56, 0.97) * W, R.uniform(0.08, 0.92) * H
520 star4(d, x, y, R.uniform(8, 20), [teal, yellow, coral][R.randint(0, 2)], R.randint(110, 200))
521 comp(img, lay, 0.6)
522 img.alpha_composite(_paper_clip(0.93 * W, 0.42 * H, 64, 0.5, teal))
523 img.alpha_composite(_paper_clip(0.60 * W, 0.52 * H, 52, -0.4, coral))
524 return img
525
526
527 def scene_violet_starlight() -> "Image":
528 R = rng(20260122)
529 img = gradient(W, H, [(0.0, "#F4F1FF"), (0.38, "#DDD5F8"), (0.72, "#A794E3"), (1.0, "#7A63C4")], "d1")
530 glow(img, 0.80 * W, 0.24 * H, 0.34 * W, hex2rgb("#EDE7FF"), 90)
531
532 # moon with halo
533 lay = new_layer()
534 d = ImageDraw.Draw(lay)
535 mx, my, mr = 0.845 * W, 0.205 * H, 120
536 d.ellipse([mx - mr, my - mr, mx + mr, my + mr], fill=rgba(hex2rgb("#F2EDFF"), 235))
537 d.ellipse([mx - mr * 0.68, my - mr * 0.5, mx - mr * 0.1, my + mr * 0.1], fill=rgba(hex2rgb("#DDD2F5"), 120))
538 d.ellipse([mx + mr * 0.15, my + mr * 0.25, mx + mr * 0.6, my + mr * 0.62], fill=rgba(hex2rgb("#DDD2F5"), 100))
539 comp(img, lay, 2)
540 glow(img, mx, my, 300, hex2rgb("#E6DCFF"), 60)
541
542 # star field, denser to the right
543 lay = new_layer()
544 d = ImageDraw.Draw(lay)
545 for _ in range(170):
546 x = R.betavariate(2.2, 1.4) * W
547 y = R.betavariate(1.6, 1.2) * H * 0.9
548 r = R.uniform(1.2, 4.2)
549 a = R.randint(60, 220)
550 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(hex2rgb("#FFFFFF"), a))
551 for _ in range(16):
552 x = R.uniform(0.5, 0.99) * W
553 y = R.uniform(0.05, 0.75) * H
554 star4(d, x, y, R.uniform(10, 26), hex2rgb("#FFFFFF"), R.randint(140, 230), thin=0.14)
555 comp(img, lay, 0.8)
556
557 # silhouette muse
558 silhouette_muse(
559 img, 0.715 * W, 0.38 * H, 138,
560 body_c=hex2rgb("#4C3E8F"), rim_c=hex2rgb("#D9CCFF"),
561 )
562
563 # glowing butterflies
564 lay = new_layer()
565 d = ImageDraw.Draw(lay)
566 flies = [
567 (0.615 * W, 0.30 * H, 46, -0.4), (0.80 * W, 0.52 * H, 58, 0.3), (0.885 * W, 0.38 * H, 40, -0.2),
568 (0.67 * W, 0.66 * H, 34, 0.5), (0.93 * W, 0.68 * H, 48, -0.5), (0.565 * W, 0.55 * H, 30, 0.2),
569 ]
570 v1, v2 = hex2rgb("#8F79E0"), hex2rgb("#C9B8FF")
571 for cx, cy, s, ang in flies:
572 polys, body = butterfly_pts(cx, cy, s, ang, flap=R.uniform(0.8, 1.1))
573 for p in polys:
574 d.polygon(p, fill=rgba(v1, 235))
575 d.polygon(body, fill=rgba(mix(v1, "#000000", 0.3), 255))
576 comp(img, lay, 1.0)
577 for cx, cy, s, ang in flies:
578 glow(img, cx, cy, s * 2.6, v2, 34)
579
580 # mist along the bottom
581 lay = new_layer()
582 d = ImageDraw.Draw(lay)
583 for _ in range(9):
584 x, y = R.uniform(0.45, 1.0) * W, R.uniform(0.9, 1.02) * H
585 d.ellipse([x - 320, y - 60, x + 320, y + 60], fill=rgba(hex2rgb("#B7A9E8"), 42))
586 comp(img, lay, 26)
587 return img
588
589
590 def scene_cyan_stage() -> "Image":
591 R = rng(20260123)
592 img = gradient(W, H, [(0.0, "#F3FDFE"), (0.5, "#DCF4F8"), (1.0, "#BDE7EF")], "d1")
593 glow(img, 0.74 * W, 0.30 * H, 0.5 * W, hex2rgb("#FFFFFF"), 70)
594
595 # stage floor + light rings
596 lay = new_layer()
597 d = ImageDraw.Draw(lay)
598 stage_c = hex2rgb("#8FD3DF")
599 d.ellipse([0.48 * W, 0.86 * H, 1.05 * W, 1.10 * H], fill=rgba(mix(stage_c, "#ffffff", 0.45), 160))
600 for rr, aa in [(300, 120), (420, 80), (540, 50)]:
601 d.ellipse([0.74 * W - rr, 0.965 * H - rr * 0.22, 0.74 * W + rr, 0.965 * H + rr * 0.22], outline=rgba(hex2rgb("#37D7E4"), aa), width=6)
602 comp(img, lay, 1.5)
603
604 # spotlights from the top
605 beam(img, (0.58 * W, -0.06 * H), (0.70 * W, 0.92 * H), 90, 420, hex2rgb("#FFFFFF"), 60, blur=30)
606 beam(img, (0.74 * W, -0.06 * H), (0.75 * W, 0.95 * H), 110, 520, hex2rgb("#BDF3F8"), 70, blur=34)
607 beam(img, (0.90 * W, -0.06 * H), (0.80 * W, 0.92 * H), 90, 420, hex2rgb("#FFFFFF"), 55, blur=30)
608
609 # holographic ring behind the performer (flat ellipse around the torso)
610 lay = new_layer()
611 d = ImageDraw.Draw(lay)
612 ring = ring_pts(0.725 * W, 0.56 * H, 430, 9, squash=0.42)
613 d.polygon(ring, fill=rgba(hex2rgb("#37D7E4"), 60))
614 comp(img, lay, 4)
615
616 # performer
617 performer(
618 img, 0.722 * W, 0.42 * H, 148,
619 skin=hex2rgb("#F3D2BA"), hair_c=hex2rgb("#1E3A44"),
620 cloth_c=hex2rgb("#EAFBFD"), glow_c=hex2rgb("#18B7C6"),
621 )
622
623 # equalizer bars on the stage (clearly audio, low alpha)
624 lay = new_layer()
625 d = ImageDraw.Draw(lay)
626 for i in range(12):
627 bx = 0.520 * W + i * 32
628 bh = 40 + 110 * abs(math.sin(i * 1.7 + 0.6))
629 d.polygon(superellipse_poly(bx, 0.945 * H - bh / 2, 10, bh / 2, power=5), fill=rgba(hex2rgb("#18B7C6"), 70))
630 comp(img, lay, 2)
631
632 # floating notes-free particles: dots + sparkles
633 lay = new_layer()
634 d = ImageDraw.Draw(lay)
635 for _ in range(22):
636 x, y = R.uniform(0.55, 0.99) * W, R.uniform(0.08, 0.8) * H
637 r = R.uniform(2.5, 7)
638 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(hex2rgb("#18B7C6"), R.randint(50, 130)))
639 for _ in range(8):
640 x, y = R.uniform(0.58, 0.97) * W, R.uniform(0.10, 0.7) * H
641 star4(d, x, y, R.uniform(9, 20), hex2rgb("#37D7E4"), R.randint(110, 190))
642 comp(img, lay, 1.2)
643 return img
644
645
646 def scene_noir_gold() -> "Image":
647 R = rng(20260124)
648 img = gradient(W, H, [(0.0, "#FDFAF1"), (0.5, "#F6ECD4"), (1.0, "#ECDAB4")], "d1")
649 glow(img, 0.74 * W, 0.30 * H, 0.5 * W, hex2rgb("#FFF8E2"), 80)
650
651 gold = hex2rgb("#D9B45B")
652 gold_deep = hex2rgb("#A9822F")
653 noir = hex2rgb("#211C15")
654
655 # black curtain swag along the top-right with a scalloped hem
656 lay = new_layer()
657 d = ImageDraw.Draw(lay)
658 hem = []
659 hem.extend(cubic((0.62 * W, -0.02 * H), (0.68 * W, 0.10 * H), (0.72 * W, 0.13 * H), (0.77 * W, 0.135 * H), n=24))
660 hem.extend(cubic((0.77 * W, 0.135 * H), (0.80 * W, 0.14 * H), (0.83 * W, 0.13 * H), (0.86 * W, 0.10 * H), n=18)[1:])
661 hem.extend(cubic((0.86 * W, 0.10 * H), (0.90 * W, 0.06 * H), (0.95 * W, 0.03 * H), (1.03 * W, 0.02 * H), n=24)[1:])
662 swag = hem + [(1.03 * W, -0.06 * H), (0.62 * W, -0.06 * H)]
663 d.polygon(swag, fill=rgba(noir, 245))
664 # soft fold shading under the hem
665 for i in range(3):
666 fx = 0.70 * W + i * 0.075 * W
667 fold = cubic((fx, 0.02 * H), (fx - 0.012 * W, 0.07 * H), (fx - 0.005 * W, 0.10 * H), (fx + 0.01 * W, 0.125 * H), n=20)
668 d.line(fold, fill=rgba(mix(noir, "#ffffff", 0.10), 160), width=10, joint="curve")
669 # right edge drape
670 drape = smooth_path([
671 ((0.972 * W, 0.0), (0.995 * W, 0.22 * H), (0.975 * W, 0.48 * H), (1.0 * W, 0.70 * H)),
672 ((1.0 * W, 0.70 * H), (1.035 * W, 0.45 * H), (1.035 * W, 0.2 * H), (1.015 * W, 0.0)),
673 ])
674 d.polygon(drape, fill=rgba(noir, 240))
675 comp(img, lay, 1.2)
676 # gold fringe following the hem
677 lay = new_layer()
678 d = ImageDraw.Draw(lay)
679 d.line(hem, fill=rgba(gold, 210), width=7, joint="curve")
680 comp(img, lay, 1.0)
681
682 # art-deco fan arcs behind the man (kept below the chin)
683 lay = new_layer()
684 d = ImageDraw.Draw(lay)
685 fcx, fcy = 0.725 * W, 0.92 * H
686 for rr in range(200, 470, 66):
687 arc = ellipse_poly(fcx, fcy, rr, rr, a0=math.pi * 1.18, a1=math.pi * 1.82)
688 d.line(arc, fill=rgba(gold_deep, 90), width=5, joint="curve")
689 comp(img, lay, 1.5)
690
691 # golden spotlights
692 beam(img, (0.60 * W, -0.05 * H), (0.70 * W, 0.95 * H), 80, 380, hex2rgb("#FFE9AE"), 80, blur=30)
693 beam(img, (0.88 * W, -0.02 * H), (0.77 * W, 0.95 * H), 90, 430, hex2rgb("#FFDF9E"), 70, blur=32)
694
695 # man in black suit
696 man_bust(
697 img, 0.722 * W, 0.41 * H, 150,
698 skin=hex2rgb("#E8C3A0"), hair_c=hex2rgb("#241F19"),
699 cloth_c=noir, collar_c=hex2rgb("#FBF4E4"), style="suit",
700 )
701 # gold pocket square
702 lay = new_layer()
703 d = ImageDraw.Draw(lay)
704 d.polygon([(0.722 * W + 0.62 * 150, 0.41 * H + 1.9 * 180), (0.722 * W + 0.82 * 150, 0.41 * H + 1.86 * 180), (0.722 * W + 0.72 * 150, 0.41 * H + 2.06 * 180)], fill=rgba(gold, 255))
705 comp(img, lay, 1.5)
706
707 # stage floor curve bottom-right
708 lay = new_layer()
709 d = ImageDraw.Draw(lay)
710 floor = smooth_path([
711 ((0.52 * W, 1.01 * H), (0.66 * W, 0.92 * H), (0.86 * W, 0.90 * H), (1.02 * W, 0.96 * H)),
712 ((1.02 * W, 0.96 * H), (1.02 * W, 1.03 * H), (0.7 * W, 1.04 * H), (0.52 * W, 1.01 * H)),
713 ])
714 d.polygon(floor, fill=rgba(noir, 235))
715 edge = cubic((0.53 * W, 1.005 * H), (0.66 * W, 0.925 * H), (0.86 * W, 0.905 * H), (1.01 * W, 0.955 * H), n=50)
716 d.line(edge, fill=rgba(gold, 200), width=7, joint="curve")
717 comp(img, lay, 1.0)
718
719 # gold dust
720 lay = new_layer()
721 d = ImageDraw.Draw(lay)
722 for _ in range(30):
723 x, y = R.uniform(0.55, 0.99) * W, R.uniform(0.08, 0.9) * H
724 r = R.uniform(2, 6.5)
725 d.ellipse([x - r, y - r, x + r, y + r], fill=rgba(gold if R.random() < 0.7 else gold_deep, R.randint(70, 160)))
726 for _ in range(10):
727 x, y = R.uniform(0.58, 0.97) * W, R.uniform(0.10, 0.75) * H
728 star4(d, x, y, R.uniform(8, 18), gold, R.randint(120, 200))
729 comp(img, lay, 1.2)
730 return img
731
732
733 SCENES = {
734 "official-rose-dawn": scene_rose_dawn,
735 "official-fortune-forge": scene_fortune_forge,
736 "official-crimson-horizon": scene_crimson_horizon,
737 "official-sage-breeze": scene_sage_breeze,
738 "official-spark-notebook": scene_spark_notebook,
739 "official-violet-starlight": scene_violet_starlight,
740 "official-cyan-stage": scene_cyan_stage,
741 "official-noir-gold": scene_noir_gold,
742 }
743
743 lines PYTHON