| 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 |