| 1 | """Original stylised adult figure builders (flat-illustration look). |
| 2 | |
| 3 | Every figure is drawn procedurally from bezier primitives — no reference |
| 4 | imagery, no likeness of real persons. All characters are fictional adults. |
| 5 | Coordinates use a local frame: face centre (cx, cy), face half-width fw. |
| 6 | """ |
| 7 | from __future__ import annotations |
| 8 | |
| 9 | import math |
| 10 | |
| 11 | from PIL import ImageDraw, ImageFilter |
| 12 | |
| 13 | from artkit import ( |
| 14 | comp, |
| 15 | cubic, |
| 16 | draw_poly, |
| 17 | ellipse_poly, |
| 18 | mix, |
| 19 | new_layer, |
| 20 | rgba, |
| 21 | smooth_path, |
| 22 | soft_fill, |
| 23 | superellipse_poly, |
| 24 | ) |
| 25 | |
| 26 | |
| 27 | def _face_outline(cx, cy, fw, fh, turn=0.0): |
| 28 | """Near-frontal face with a soft jaw; turn>0 shifts chin slightly left.""" |
| 29 | chin = (cx - turn * 0.10 * fw, cy + fh) |
| 30 | segs = [ |
| 31 | # left temple -> crown -> right temple |
| 32 | ((cx - 0.94 * fw, cy - 0.38 * fh), (cx - 1.02 * fw, cy - 1.02 * fh), (cx + 0.55 * fw, cy - 1.12 * fh), (cx + 0.92 * fw, cy - 0.42 * fh)), |
| 33 | # right cheek -> right jaw |
| 34 | ((cx + 0.92 * fw, cy - 0.42 * fh), (cx + 0.98 * fw, cy + 0.18 * fh), (cx + 0.62 * fw, cy + 0.62 * fh), (cx + 0.30 * fw, cy + 0.86 * fh)), |
| 35 | # right jaw -> chin -> left jaw |
| 36 | ((cx + 0.30 * fw, cy + 0.86 * fh), (cx + 0.14 * fw, cy + 1.00 * fh), (chin[0] + 0.16 * fw, chin[1] + 0.02 * fh), chin), |
| 37 | (chin, (chin[0] - 0.34 * fw, chin[1] - 0.02 * fh), (cx - 0.72 * fw, cy + 0.66 * fh), (cx - 0.88 * fw, cy + 0.30 * fh)), |
| 38 | # left cheek back to temple |
| 39 | ((cx - 0.88 * fw, cy + 0.30 * fh), (cx - 0.96 * fw, cy + 0.02 * fh), (cx - 0.97 * fw, cy - 0.16 * fh), (cx - 0.94 * fw, cy - 0.38 * fh)), |
| 40 | ] |
| 41 | return smooth_path(segs) |
| 42 | |
| 43 | |
| 44 | def _neck_pts(cx, cy, fw, fh): |
| 45 | top_y = cy + 0.62 * fh |
| 46 | bot_y = cy + 1.55 * fh |
| 47 | return smooth_path([ |
| 48 | ((cx - 0.34 * fw, top_y), (cx - 0.30 * fw, bot_y - 0.2 * fh), (cx - 0.38 * fw, bot_y), (cx - 0.42 * fw, bot_y)), |
| 49 | ((cx - 0.42 * fw, bot_y), (cx - 0.1 * fw, bot_y + 0.06 * fh), (cx + 0.2 * fw, bot_y + 0.06 * fh), (cx + 0.34 * fw, bot_y)), |
| 50 | ((cx + 0.34 * fw, bot_y), (cx + 0.28 * fw, bot_y - 0.2 * fh), (cx + 0.30 * fw, top_y), (cx + 0.30 * fw, top_y)), |
| 51 | ]) |
| 52 | |
| 53 | |
| 54 | def _shoulders_pts(cx, y0, width, height, slope=0.12): |
| 55 | hw = width / 2 |
| 56 | return smooth_path([ |
| 57 | ((cx - hw, y0 + height), (cx - hw * 1.02, y0 + height * 0.35), (cx - hw * 0.72, y0 + slope * height), (cx - hw * 0.30, y0 + 0.02 * height)), |
| 58 | ((cx - hw * 0.30, y0 + 0.02 * height), (cx - hw * 0.1, y0 - 0.06 * height), (cx + hw * 0.1, y0 - 0.06 * height), (cx + hw * 0.30, y0 + 0.02 * height)), |
| 59 | ((cx + hw * 0.30, y0 + 0.02 * height), (cx + hw * 0.72, y0 + slope * height), (cx + hw * 1.02, y0 + height * 0.35), (cx + hw, y0 + height)), |
| 60 | ((cx + hw, y0 + height), (cx + hw * 0.4, y0 + height * 1.04), (cx - hw * 0.4, y0 + height * 1.04), (cx - hw, y0 + height)), |
| 61 | ]) |
| 62 | |
| 63 | |
| 64 | def _closed_eye(d, ex, ey, s, color, alpha=235, lashes=0, w=0.16): |
| 65 | pts = cubic((ex - 0.16 * s, ey), (ex - 0.05 * s, ey + 0.10 * s), (ex + 0.06 * s, ey + 0.10 * s), (ex + 0.17 * s, ey - 0.01 * s), n=24) |
| 66 | d.line(pts, fill=rgba(color, alpha), width=max(2, int(w * s * 0.28)), joint="curve") |
| 67 | for i in range(lashes): |
| 68 | t = 0.55 + 0.22 * i |
| 69 | bx = ex - 0.16 * s + (0.33 * s) * t |
| 70 | by = ey + 0.06 * s - 0.05 * s * abs(t - 0.5) |
| 71 | d.line([(bx, by), (bx + 0.05 * s, by - 0.07 * s)], fill=rgba(color, alpha), width=max(2, int(w * s * 0.2))) |
| 72 | |
| 73 | |
| 74 | def _open_eye(lay, ex, ey, s, iris, alpha=255, sparkle=True): |
| 75 | """Simple bright anime-style eye.""" |
| 76 | d = ImageDraw.Draw(lay) |
| 77 | white = (255, 255, 255) |
| 78 | d.ellipse([ex - 0.13 * s, ey - 0.17 * s, ex + 0.13 * s, ey + 0.17 * s], fill=rgba(white, alpha)) |
| 79 | d.ellipse([ex - 0.10 * s, ey - 0.13 * s, ex + 0.10 * s, ey + 0.15 * s], fill=rgba(iris, alpha)) |
| 80 | d.ellipse([ex - 0.055 * s, ey - 0.06 * s, ex + 0.055 * s, ey + 0.09 * s], fill=rgba(mix(iris, "#000000", 0.55), alpha)) |
| 81 | if sparkle: |
| 82 | d.ellipse([ex - 0.055 * s, ey - 0.10 * s, ex + 0.005 * s, ey - 0.04 * s], fill=rgba(white, alpha)) |
| 83 | d.ellipse([ex + 0.03 * s, ey + 0.02 * s, ex + 0.07 * s, ey + 0.06 * s], fill=rgba(white, int(alpha * 0.8))) |
| 84 | |
| 85 | |
| 86 | def _brow(d, ex, ey, s, color, alpha=200, tilt=0.0): |
| 87 | pts = cubic((ex - 0.15 * s, ey + tilt), (ex - 0.05 * s, ey - 0.06 * s + tilt), (ex + 0.08 * s, ey - 0.06 * s - tilt), (ex + 0.16 * s, ey - 0.02 * s - tilt), n=20) |
| 88 | d.line(pts, fill=rgba(color, alpha), width=max(2, int(0.035 * s)), joint="curve") |
| 89 | |
| 90 | |
| 91 | def _smile(d, cx, y, s, color, alpha=220, width=0.16): |
| 92 | pts = cubic((cx - width * s, y), (cx - 0.05 * s, y + 0.07 * s), (cx + 0.05 * s, y + 0.07 * s), (cx + width * s, y - 0.01 * s), n=24) |
| 93 | d.line(pts, fill=rgba(color, alpha), width=max(2, int(0.032 * s)), joint="curve") |
| 94 | |
| 95 | |
| 96 | def _lips(lay, cx, y, s, color, alpha=230): |
| 97 | d = ImageDraw.Draw(lay) |
| 98 | pts = smooth_path([ |
| 99 | ((cx - 0.15 * s, y), (cx - 0.05 * s, y - 0.045 * s), (cx - 0.02 * s, y - 0.03 * s), (cx, y - 0.012 * s)), |
| 100 | ((cx, y - 0.012 * s), (cx + 0.02 * s, y - 0.03 * s), (cx + 0.05 * s, y - 0.045 * s), (cx + 0.15 * s, y)), |
| 101 | ((cx + 0.15 * s, y), (cx + 0.07 * s, y + 0.075 * s), (cx - 0.07 * s, y + 0.075 * s), (cx - 0.15 * s, y)), |
| 102 | ]) |
| 103 | d.polygon(pts, fill=rgba(color, alpha)) |
| 104 | |
| 105 | |
| 106 | def _blush(lay, ex, ey, s, color, alpha=70): |
| 107 | d = ImageDraw.Draw(lay) |
| 108 | d.ellipse([ex - 0.16 * s, ey - 0.08 * s, ex + 0.16 * s, ey + 0.08 * s], fill=rgba(color, alpha)) |
| 109 | |
| 110 | |
| 111 | def _figure_shadow(base, cx, cy, fw, fh, scale_w=3.9, scale_h=4.1, alpha=26, blur=52, dx=22, dy=34): |
| 112 | lay = new_layer() |
| 113 | d = ImageDraw.Draw(lay) |
| 114 | d.ellipse( |
| 115 | [cx - fw * scale_w / 2 + dx, cy - fh * 0.9 + dy, cx + fw * scale_w / 2 + dx, cy + fh * scale_h / 2 + dy], |
| 116 | fill=(30, 24, 28, alpha), |
| 117 | ) |
| 118 | comp(base, lay, blur) |
| 119 | |
| 120 | |
| 121 | # --------------------------------------------------------------------------- |
| 122 | # bust variants |
| 123 | |
| 124 | |
| 125 | def woman_serene(base, cx, cy, s, skin, hair_c, cloth_c, lip_c, blush_c, style="long", accent=None): |
| 126 | """Adult woman, gentle closed-eye expression. s = face half-width.""" |
| 127 | fw, fh = s, s * 1.22 |
| 128 | accent = accent or cloth_c |
| 129 | _figure_shadow(base, cx, cy, fw, fh) |
| 130 | |
| 131 | # back hair mass |
| 132 | lay = new_layer() |
| 133 | d = ImageDraw.Draw(lay) |
| 134 | if style == "long": |
| 135 | back = smooth_path([ |
| 136 | ((cx - 1.05 * fw, cy - 0.5 * fh), (cx - 1.25 * fw, cy - 1.5 * fh), (cx + 0.9 * fw, cy - 1.65 * fh), (cx + 1.15 * fw, cy - 0.45 * fh)), |
| 137 | ((cx + 1.15 * fw, cy - 0.45 * fh), (cx + 1.5 * fw, cy + 0.9 * fh), (cx + 1.35 * fw, cy + 2.6 * fh), (cx + 0.75 * fw, cy + 3.1 * fh)), |
| 138 | ((cx + 0.75 * fw, cy + 3.1 * fh), (cx + 0.45 * fw, cy + 2.2 * fh), (cx + 0.15 * fw, cy + 3.4 * fh), (cx - 0.35 * fw, cy + 3.5 * fh)), |
| 139 | ((cx - 0.35 * fw, cy + 3.5 * fh), (cx - 1.05 * fw, cy + 2.4 * fh), (cx - 1.35 * fw, cy + 1.0 * fh), (cx - 1.05 * fw, cy - 0.5 * fh)), |
| 140 | ]) |
| 141 | else: # bun |
| 142 | back = smooth_path([ |
| 143 | ((cx - 1.02 * fw, cy - 0.45 * fh), (cx - 1.1 * fw, cy - 1.4 * fh), (cx + 0.8 * fw, cy - 1.5 * fh), (cx + 1.05 * fw, cy - 0.4 * fh)), |
| 144 | ((cx + 1.05 * fw, cy - 0.4 * fh), (cx + 1.15 * fw, cy + 0.5 * fh), (cx + 0.6 * fw, cy + 1.0 * fh), (cx, cy + 1.05 * fh)), |
| 145 | ((cx, cy + 1.05 * fh), (cx - 0.7 * fw, cy + 1.0 * fh), (cx - 1.1 * fw, cy + 0.4 * fh), (cx - 1.02 * fw, cy - 0.45 * fh)), |
| 146 | ]) |
| 147 | d.ellipse([cx + 0.35 * fw, cy - 1.95 * fh, cx + 1.05 * fw, cy - 1.15 * fh], fill=rgba(hair_c, 255)) |
| 148 | d.polygon(back, fill=rgba(hair_c, 255)) |
| 149 | base.alpha_composite(lay) |
| 150 | |
| 151 | # neck + shoulders |
| 152 | lay = new_layer() |
| 153 | d = ImageDraw.Draw(lay) |
| 154 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(mix(skin, "#000000", 0.07), 255)) |
| 155 | sh = _shoulders_pts(cx, cy + 1.45 * fh, 3.4 * fw, 2.2 * fh) |
| 156 | d.polygon(sh, fill=rgba(cloth_c, 255)) |
| 157 | # collar V |
| 158 | d.polygon( |
| 159 | [(cx - 0.30 * fw, cy + 1.50 * fh), (cx, cy + 1.95 * fh), (cx + 0.30 * fw, cy + 1.50 * fh), (cx + 0.22 * fw, cy + 1.42 * fh), (cx - 0.22 * fw, cy + 1.42 * fh)], |
| 160 | fill=rgba(mix(cloth_c, "#ffffff", 0.35), 255), |
| 161 | ) |
| 162 | base.alpha_composite(lay) |
| 163 | |
| 164 | # face |
| 165 | lay = new_layer() |
| 166 | d = ImageDraw.Draw(lay) |
| 167 | d.polygon(_face_outline(cx, cy, fw, fh), fill=rgba(skin, 255)) |
| 168 | base.alpha_composite(lay) |
| 169 | |
| 170 | # fringe / front hair |
| 171 | lay = new_layer() |
| 172 | d = ImageDraw.Draw(lay) |
| 173 | fringe = smooth_path([ |
| 174 | ((cx - 1.0 * fw, cy - 0.18 * fh), (cx - 1.10 * fw, cy - 1.25 * fh), (cx + 0.6 * fw, cy - 1.48 * fh), (cx + 0.98 * fw, cy - 0.42 * fh)), |
| 175 | ((cx + 0.98 * fw, cy - 0.42 * fh), (cx + 0.72 * fw, cy - 0.58 * fh), (cx + 0.55 * fw, cy - 0.70 * fh), (cx + 0.30 * fw, cy - 0.62 * fh)), |
| 176 | ((cx + 0.30 * fw, cy - 0.62 * fh), (cx + 0.05 * fw, cy - 0.80 * fh), (cx - 0.35 * fw, cy - 0.78 * fh), (cx - 0.55 * fw, cy - 0.56 * fh)), |
| 177 | ((cx - 0.55 * fw, cy - 0.56 * fh), (cx - 0.75 * fw, cy - 0.70 * fh), (cx - 0.9 * fw, cy - 0.48 * fh), (cx - 1.0 * fw, cy - 0.18 * fh)), |
| 178 | ]) |
| 179 | d.polygon(fringe, fill=rgba(hair_c, 255)) |
| 180 | # side strand |
| 181 | strand = smooth_path([ |
| 182 | ((cx + 0.82 * fw, cy - 0.62 * fh), (cx + 1.10 * fw, cy - 0.05 * fh), (cx + 1.05 * fw, cy + 0.9 * fh), (cx + 0.8 * fw, cy + 1.6 * fh)), |
| 183 | ((cx + 0.8 * fw, cy + 1.6 * fh), (cx + 0.66 * fw, cy + 0.8 * fh), (cx + 0.64 * fw, cy - 0.05 * fh), (cx + 0.68 * fw, cy - 0.58 * fh)), |
| 184 | ]) |
| 185 | d.polygon(strand, fill=rgba(mix(hair_c, "#ffffff", 0.06), 255)) |
| 186 | base.alpha_composite(lay) |
| 187 | |
| 188 | # features |
| 189 | lay = new_layer() |
| 190 | d = ImageDraw.Draw(lay) |
| 191 | ink = mix(skin, "#000000", 0.72) |
| 192 | _closed_eye(d, cx - 0.42 * fw, cy - 0.02 * fh, fw, ink, lashes=2) |
| 193 | _closed_eye(d, cx + 0.42 * fw, cy - 0.02 * fh, fw, ink, lashes=2) |
| 194 | _brow(d, cx - 0.42 * fw, cy - 0.30 * fh, fw, mix(hair_c, "#000000", 0.25)) |
| 195 | _brow(d, cx + 0.42 * fw, cy - 0.30 * fh, fw, mix(hair_c, "#000000", 0.25)) |
| 196 | nose = cubic((cx - 0.02 * fw, cy + 0.16 * fh), (cx + 0.03 * fw, cy + 0.24 * fh), (cx + 0.02 * fw, cy + 0.30 * fh), (cx - 0.04 * fw, cy + 0.32 * fh), n=16) |
| 197 | d.line(nose, fill=rgba(mix(skin, "#000000", 0.30), 200), width=max(2, int(0.03 * fw)), joint="curve") |
| 198 | _lips(lay, cx + 0.02 * fw, cy + 0.52 * fh, fw, lip_c) |
| 199 | _blush(lay, cx - 0.52 * fw, cy + 0.30 * fh, fw, blush_c) |
| 200 | _blush(lay, cx + 0.56 * fw, cy + 0.30 * fh, fw, blush_c) |
| 201 | base.alpha_composite(lay) |
| 202 | |
| 203 | |
| 204 | def man_bust(base, cx, cy, s, skin, hair_c, cloth_c, collar_c=None, style="shirt"): |
| 205 | """Adult man, calm closed-eye expression. style: shirt | suit.""" |
| 206 | fw, fh = s, s * 1.20 |
| 207 | collar_c = collar_c or mix(cloth_c, "#ffffff", 0.5) |
| 208 | _figure_shadow(base, cx, cy, fw, fh, alpha=40) |
| 209 | |
| 210 | lay = new_layer() |
| 211 | d = ImageDraw.Draw(lay) |
| 212 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(mix(skin, "#000000", 0.08), 255)) |
| 213 | sh = _shoulders_pts(cx, cy + 1.45 * fh, 3.6 * fw, 2.25 * fh) |
| 214 | d.polygon(sh, fill=rgba(cloth_c, 255)) |
| 215 | if style == "suit": |
| 216 | # shirt V + lapels + tie |
| 217 | d.polygon([(cx - 0.38 * fw, cy + 1.5 * fh), (cx, cy + 2.35 * fh), (cx + 0.38 * fw, cy + 1.5 * fh)], fill=rgba(collar_c, 255)) |
| 218 | d.polygon([(cx - 0.09 * fw, cy + 1.62 * fh), (cx + 0.09 * fw, cy + 1.62 * fh), (cx + 0.05 * fw, cy + 1.78 * fh), (cx - 0.05 * fw, cy + 1.78 * fh)], fill=rgba(mix(cloth_c, "#000000", 0.4), 255)) |
| 219 | d.polygon([(cx - 0.05 * fw, cy + 1.78 * fh), (cx + 0.05 * fw, cy + 1.78 * fh), (cx + 0.10 * fw, cy + 2.35 * fh), (cx, cy + 2.5 * fh), (cx - 0.10 * fw, cy + 2.35 * fh)], fill=rgba(mix(cloth_c, "#000000", 0.4), 255)) |
| 220 | lap_l = [(cx - 0.38 * fw, cy + 1.5 * fh), (cx - 0.95 * fw, cy + 1.75 * fh), (cx - 0.55 * fw, cy + 2.6 * fh), (cx - 0.12 * fw, cy + 2.1 * fh)] |
| 221 | lap_r = [(cx + 0.38 * fw, cy + 1.5 * fh), (cx + 0.95 * fw, cy + 1.75 * fh), (cx + 0.55 * fw, cy + 2.6 * fh), (cx + 0.12 * fw, cy + 2.1 * fh)] |
| 222 | d.polygon(lap_l, fill=rgba(mix(cloth_c, "#ffffff", 0.08), 255)) |
| 223 | d.polygon(lap_r, fill=rgba(mix(cloth_c, "#ffffff", 0.08), 255)) |
| 224 | else: |
| 225 | # crew / open collar |
| 226 | d.polygon([(cx - 0.40 * fw, cy + 1.48 * fh), (cx, cy + 1.95 * fh), (cx + 0.40 * fw, cy + 1.48 * fh), (cx + 0.28 * fw, cy + 1.38 * fh), (cx - 0.28 * fw, cy + 1.38 * fh)], fill=rgba(collar_c, 255)) |
| 227 | base.alpha_composite(lay) |
| 228 | |
| 229 | lay = new_layer() |
| 230 | d = ImageDraw.Draw(lay) |
| 231 | d.polygon(_face_outline(cx, cy, fw, fh, turn=0.4), fill=rgba(skin, 255)) |
| 232 | base.alpha_composite(lay) |
| 233 | |
| 234 | # short hair |
| 235 | lay = new_layer() |
| 236 | d = ImageDraw.Draw(lay) |
| 237 | hair = smooth_path([ |
| 238 | ((cx - 0.98 * fw, cy - 0.30 * fh), (cx - 1.05 * fw, cy - 1.18 * fh), (cx + 0.6 * fw, cy - 1.38 * fh), (cx + 0.97 * fw, cy - 0.42 * fh)), |
| 239 | ((cx + 0.97 * fw, cy - 0.42 * fh), (cx + 0.86 * fw, cy - 0.60 * fh), (cx + 0.68 * fw, cy - 0.74 * fh), (cx + 0.42 * fw, cy - 0.68 * fh)), |
| 240 | ((cx + 0.42 * fw, cy - 0.68 * fh), (cx + 0.08 * fw, cy - 0.88 * fh), (cx - 0.45 * fw, cy - 0.84 * fh), (cx - 0.68 * fw, cy - 0.58 * fh)), |
| 241 | ((cx - 0.68 * fw, cy - 0.58 * fh), (cx - 0.86 * fw, cy - 0.68 * fh), (cx - 0.96 * fw, cy - 0.50 * fh), (cx - 0.98 * fw, cy - 0.30 * fh)), |
| 242 | ]) |
| 243 | d.polygon(hair, fill=rgba(hair_c, 255)) |
| 244 | base.alpha_composite(lay) |
| 245 | |
| 246 | lay = new_layer() |
| 247 | d = ImageDraw.Draw(lay) |
| 248 | ink = mix(skin, "#000000", 0.75) |
| 249 | _closed_eye(d, cx - 0.42 * fw, cy + 0.0 * fh, fw, ink, lashes=0) |
| 250 | _closed_eye(d, cx + 0.42 * fw, cy + 0.0 * fh, fw, ink, lashes=0) |
| 251 | _brow(d, cx - 0.42 * fw, cy - 0.28 * fh, fw, mix(hair_c, "#000000", 0.2), tilt=0.01 * fw) |
| 252 | _brow(d, cx + 0.42 * fw, cy - 0.28 * fh, fw, mix(hair_c, "#000000", 0.2), tilt=0.01 * fw) |
| 253 | nose = cubic((cx - 0.02 * fw, cy + 0.14 * fh), (cx + 0.035 * fw, cy + 0.24 * fh), (cx + 0.03 * fw, cy + 0.31 * fh), (cx - 0.05 * fw, cy + 0.33 * fh), n=16) |
| 254 | d.line(nose, fill=rgba(mix(skin, "#000000", 0.32), 200), width=max(2, int(0.032 * fw)), joint="curve") |
| 255 | _smile(d, cx, cy + 0.52 * fh, fw, mix(skin, "#7a3b3b", 0.55), width=0.15) |
| 256 | base.alpha_composite(lay) |
| 257 | |
| 258 | |
| 259 | def anime_adult(base, cx, cy, s, skin, hair_c, cloth_c, iris_c, style="bob"): |
| 260 | """Original adult anime-style character, bright open eyes.""" |
| 261 | fw, fh = s, s * 1.16 |
| 262 | _figure_shadow(base, cx, cy, fw, fh, alpha=40) |
| 263 | |
| 264 | # back hair |
| 265 | lay = new_layer() |
| 266 | d = ImageDraw.Draw(lay) |
| 267 | if style == "twin": |
| 268 | for sgn in (-1, 1): |
| 269 | tail = smooth_path([ |
| 270 | ((cx + sgn * 0.85 * fw, cy - 0.7 * fh), (cx + sgn * 1.9 * fw, cy - 0.2 * fh), (cx + sgn * 2.1 * fw, cy + 1.2 * fh), (cx + sgn * 1.5 * fw, cy + 2.4 * fh)), |
| 271 | ((cx + sgn * 1.5 * fw, cy + 2.4 * fh), (cx + sgn * 1.2 * fw, cy + 1.4 * fh), (cx + sgn * 0.9 * fw, cy + 0.6 * fh), (cx + sgn * 0.72 * fw, cy - 0.2 * fh)), |
| 272 | ]) |
| 273 | d.polygon(tail, fill=rgba(mix(hair_c, "#000000", 0.08), 255)) |
| 274 | back = smooth_path([ |
| 275 | ((cx - 1.08 * fw, cy - 0.3 * fh), (cx - 1.2 * fw, cy - 1.5 * fh), (cx + 0.9 * fw, cy - 1.6 * fh), (cx + 1.12 * fw, cy - 0.3 * fh)), |
| 276 | ((cx + 1.12 * fw, cy - 0.3 * fh), (cx + 1.3 * fw, cy + 0.9 * fh), (cx + 0.95 * fw, cy + 2.0 * fh), (cx + 0.55 * fw, cy + 2.5 * fh)), |
| 277 | ((cx + 0.55 * fw, cy + 2.5 * fh), (cx + 0.3 * fw, cy + 1.6 * fh), (cx - 0.1 * fw, cy + 2.6 * fh), (cx - 0.5 * fw, cy + 2.4 * fh)), |
| 278 | ((cx - 0.5 * fw, cy + 2.4 * fh), (cx - 1.15 * fw, cy + 1.2 * fh), (cx - 1.25 * fw, cy + 0.4 * fh), (cx - 1.08 * fw, cy - 0.3 * fh)), |
| 279 | ]) |
| 280 | d.polygon(back, fill=rgba(hair_c, 255)) |
| 281 | base.alpha_composite(lay) |
| 282 | |
| 283 | lay = new_layer() |
| 284 | d = ImageDraw.Draw(lay) |
| 285 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(mix(skin, "#000000", 0.06), 255)) |
| 286 | sh = _shoulders_pts(cx, cy + 1.42 * fh, 3.1 * fw, 2.1 * fh) |
| 287 | d.polygon(sh, fill=rgba(cloth_c, 255)) |
| 288 | # hoodie strings + collar |
| 289 | d.polygon([(cx - 0.44 * fw, cy + 1.5 * fh), (cx, cy + 2.05 * fh), (cx + 0.44 * fw, cy + 1.5 * fh), (cx + 0.30 * fw, cy + 1.38 * fh), (cx - 0.30 * fw, cy + 1.38 * fh)], fill=rgba(mix(cloth_c, "#000000", 0.12), 255)) |
| 290 | d.line([(cx - 0.14 * fw, cy + 1.9 * fh), (cx - 0.16 * fw, cy + 2.3 * fh)], fill=rgba(mix(cloth_c, "#ffffff", 0.6), 255), width=max(2, int(0.045 * fw))) |
| 291 | d.line([(cx + 0.14 * fw, cy + 1.9 * fh), (cx + 0.16 * fw, cy + 2.3 * fh)], fill=rgba(mix(cloth_c, "#ffffff", 0.6), 255), width=max(2, int(0.045 * fw))) |
| 292 | base.alpha_composite(lay) |
| 293 | |
| 294 | lay = new_layer() |
| 295 | d = ImageDraw.Draw(lay) |
| 296 | d.polygon(_face_outline(cx, cy, fw, fh), fill=rgba(skin, 255)) |
| 297 | base.alpha_composite(lay) |
| 298 | |
| 299 | # fringe with anime points |
| 300 | lay = new_layer() |
| 301 | d = ImageDraw.Draw(lay) |
| 302 | pts = [] |
| 303 | x0, x1 = cx - 1.02 * fw, cx + 1.02 * fw |
| 304 | top_y = cy - 1.28 * fh |
| 305 | bot_y = cy - 0.42 * fh |
| 306 | n = 7 |
| 307 | pts.append((x0, cy - 0.2 * fh)) |
| 308 | pts.extend(cubic((x0, cy - 0.2 * fh), (x0 - 0.05 * fw, top_y - 0.2 * fh), (cx, top_y - 0.25 * fh), (x1, cy - 0.35 * fh), n=30)[1:]) |
| 309 | pts.append((x1, cy - 0.1 * fh)) |
| 310 | for i in range(n): |
| 311 | xm = x1 - (x1 - x0) * (i + 0.5) / n |
| 312 | xe = x1 - (x1 - x0) * (i + 1) / n |
| 313 | depth = bot_y + (0.10 * fh if i % 2 == 0 else -0.06 * fh) |
| 314 | pts.append((xm, depth)) |
| 315 | pts.append((xe, bot_y - 0.08 * fh)) |
| 316 | d.polygon(pts, fill=rgba(hair_c, 255)) |
| 317 | base.alpha_composite(lay) |
| 318 | |
| 319 | lay = new_layer() |
| 320 | d = ImageDraw.Draw(lay) |
| 321 | _open_eye(lay, cx - 0.42 * fw, cy + 0.05 * fh, fw, iris_c) |
| 322 | _open_eye(lay, cx + 0.42 * fw, cy + 0.05 * fh, fw, iris_c) |
| 323 | ink = mix(hair_c, "#000000", 0.45) |
| 324 | d.line(cubic((cx - 0.58 * fw, cy - 0.12 * fh), (cx - 0.45 * fw, cy - 0.22 * fh), (cx - 0.28 * fw, cy - 0.22 * fh), (cx - 0.24 * fw, cy - 0.14 * fh), n=16), fill=rgba(ink, 220), width=max(2, int(0.035 * fw))) |
| 325 | d.line(cubic((cx + 0.58 * fw, cy - 0.12 * fh), (cx + 0.45 * fw, cy - 0.22 * fh), (cx + 0.28 * fw, cy - 0.22 * fh), (cx + 0.24 * fw, cy - 0.14 * fh), n=16), fill=rgba(ink, 220), width=max(2, int(0.035 * fw))) |
| 326 | _smile(d, cx, cy + 0.5 * fh, fw, mix(skin, "#8a4040", 0.6), width=0.13) |
| 327 | _blush(lay, cx - 0.58 * fw, cy + 0.32 * fh, fw, (242, 153, 142), alpha=80) |
| 328 | _blush(lay, cx + 0.58 * fw, cy + 0.32 * fh, fw, (242, 153, 142), alpha=80) |
| 329 | base.alpha_composite(lay) |
| 330 | |
| 331 | |
| 332 | def silhouette_muse(base, cx, cy, s, body_c, rim_c=None): |
| 333 | """Elegant adult woman silhouette in a flowing gown (no facial features).""" |
| 334 | fw, fh = s, s * 1.2 |
| 335 | lay = new_layer() |
| 336 | d = ImageDraw.Draw(lay) |
| 337 | # flowing gown |
| 338 | gown = smooth_path([ |
| 339 | ((cx - 0.35 * fw, cy + 1.2 * fh), (cx - 1.6 * fw, cy + 2.2 * fh), (cx - 2.6 * fw, cy + 3.4 * fh), (cx - 2.9 * fw, cy + 4.6 * fh)), |
| 340 | ((cx - 2.9 * fw, cy + 4.6 * fh), (cx - 1.2 * fw, cy + 4.9 * fh), (cx + 1.4 * fw, cy + 4.9 * fh), (cx + 2.8 * fw, cy + 4.4 * fh)), |
| 341 | ((cx + 2.8 * fw, cy + 4.4 * fh), (cx + 2.2 * fw, cy + 3.0 * fh), (cx + 1.3 * fw, cy + 2.0 * fh), (cx + 0.38 * fw, cy + 1.2 * fh)), |
| 342 | ]) |
| 343 | d.polygon(gown, fill=rgba(body_c, 255)) |
| 344 | # torso + neck + head |
| 345 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(body_c, 255)) |
| 346 | torso = smooth_path([ |
| 347 | ((cx - 0.9 * fw, cy + 2.2 * fh), (cx - 0.75 * fw, cy + 1.4 * fh), (cx - 0.4 * fw, cy + 1.25 * fh), (cx, cy + 1.2 * fh)), |
| 348 | ((cx, cy + 1.2 * fh), (cx + 0.4 * fw, cy + 1.25 * fh), (cx + 0.75 * fw, cy + 1.4 * fh), (cx + 0.9 * fw, cy + 2.2 * fh)), |
| 349 | ((cx + 0.9 * fw, cy + 2.2 * fh), (cx + 0.4 * fw, cy + 2.5 * fh), (cx - 0.4 * fw, cy + 2.5 * fh), (cx - 0.9 * fw, cy + 2.2 * fh)), |
| 350 | ]) |
| 351 | d.polygon(torso, fill=rgba(body_c, 255)) |
| 352 | d.polygon(_face_outline(cx, cy, fw * 0.92, fh * 0.92), fill=rgba(body_c, 255)) |
| 353 | # flowing hair |
| 354 | hair = smooth_path([ |
| 355 | ((cx - 0.95 * fw, cy - 0.35 * fh), (cx - 1.1 * fw, cy - 1.5 * fh), (cx + 0.9 * fw, cy - 1.6 * fh), (cx + 1.05 * fw, cy - 0.3 * fh)), |
| 356 | ((cx + 1.05 * fw, cy - 0.3 * fh), (cx + 1.9 * fw, cy + 0.6 * fh), (cx + 2.6 * fw, cy + 1.1 * fh), (cx + 3.2 * fw, cy + 0.9 * fh)), |
| 357 | ((cx + 3.2 * fw, cy + 0.9 * fh), (cx + 2.4 * fw, cy + 1.7 * fh), (cx + 1.6 * fw, cy + 1.7 * fh), (cx + 1.0 * fw, cy + 1.3 * fh)), |
| 358 | ((cx + 1.0 * fw, cy + 1.3 * fh), (cx + 0.5 * fw, cy + 0.9 * fh), (cx - 0.2 * fw, cy + 1.1 * fh), (cx - 0.6 * fw, cy + 0.7 * fh)), |
| 359 | ((cx - 0.6 * fw, cy + 0.7 * fh), (cx - 1.05 * fw, cy + 0.3 * fh), (cx - 1.0 * fw, cy - 0.05 * fh), (cx - 0.95 * fw, cy - 0.35 * fh)), |
| 360 | ]) |
| 361 | d.polygon(hair, fill=rgba(mix(body_c, "#000000", 0.12), 255)) |
| 362 | base.alpha_composite(lay) |
| 363 | if rim_c: |
| 364 | rim = new_layer() |
| 365 | rd = ImageDraw.Draw(rim) |
| 366 | rd.line(cubic((cx + 0.9 * fw, cy - 0.6 * fh), (cx + 1.2 * fw, cy + 0.4 * fh), (cx + 1.1 * fw, cy + 1.6 * fh), (cx + 0.8 * fw, cy + 2.2 * fh), n=40), fill=rgba(rim_c, 150), width=max(3, int(0.07 * fw))) |
| 367 | rd.line(cubic((cx + 1.05 * fw, cy - 0.3 * fh), (cx + 2.0 * fw, cy + 0.7 * fh), (cx + 2.8 * fw, cy + 1.2 * fh), (cx + 3.1 * fw, cy + 1.0 * fh), n=40), fill=rgba(rim_c, 120), width=max(3, int(0.06 * fw))) |
| 368 | comp(base, rim, 6) |
| 369 | |
| 370 | |
| 371 | def mascot_programmer(base, cx, cy, s, skin, hair_c, cloth_c, accent_c, glasses_c=None): |
| 372 | """Original cheerful 'lucky programmer' mascot — fictional adult coder.""" |
| 373 | fw, fh = s, s * 1.14 |
| 374 | glasses_c = glasses_c or mix(cloth_c, "#000000", 0.5) |
| 375 | _figure_shadow(base, cx, cy, fw, fh, alpha=42) |
| 376 | |
| 377 | lay = new_layer() |
| 378 | d = ImageDraw.Draw(lay) |
| 379 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(mix(skin, "#000000", 0.07), 255)) |
| 380 | sh = _shoulders_pts(cx, cy + 1.42 * fh, 3.5 * fw, 2.3 * fh) |
| 381 | d.polygon(sh, fill=rgba(cloth_c, 255)) |
| 382 | # hoodie collar + pocket + strings |
| 383 | d.polygon([(cx - 0.5 * fw, cy + 1.5 * fh), (cx, cy + 2.0 * fh), (cx + 0.5 * fw, cy + 1.5 * fh), (cx + 0.36 * fw, cy + 1.34 * fh), (cx - 0.36 * fw, cy + 1.34 * fh)], fill=rgba(mix(cloth_c, "#000000", 0.14), 255)) |
| 384 | d.line([(cx - 0.16 * fw, cy + 1.85 * fh), (cx - 0.20 * fw, cy + 2.35 * fh)], fill=rgba(accent_c, 255), width=max(3, int(0.05 * fw))) |
| 385 | d.line([(cx + 0.16 * fw, cy + 1.85 * fh), (cx + 0.20 * fw, cy + 2.35 * fh)], fill=rgba(accent_c, 255), width=max(3, int(0.05 * fw))) |
| 386 | pocket = superellipse_poly(cx, cy + 2.9 * fh, 0.7 * fw, 0.5 * fh, power=5) |
| 387 | d.polygon(pocket, fill=rgba(mix(cloth_c, "#ffffff", 0.10), 255)) |
| 388 | base.alpha_composite(lay) |
| 389 | |
| 390 | # headphones around neck |
| 391 | lay = new_layer() |
| 392 | d = ImageDraw.Draw(lay) |
| 393 | band = cubic((cx - 0.75 * fw, cy + 1.35 * fh), (cx - 0.5 * fw, cy + 1.9 * fh), (cx + 0.5 * fw, cy + 1.9 * fh), (cx + 0.75 * fw, cy + 1.35 * fh), n=40) |
| 394 | d.line(band, fill=rgba(glasses_c, 255), width=max(4, int(0.14 * fw))) |
| 395 | for sgn in (-1, 1): |
| 396 | d.ellipse([cx + sgn * 0.82 * fw - 0.17 * fw, cy + 1.18 * fh, cx + sgn * 0.82 * fw + 0.17 * fw, cy + 1.62 * fh], fill=rgba(glasses_c, 255)) |
| 397 | d.ellipse([cx + sgn * 0.82 * fw - 0.09 * fw, cy + 1.28 * fh, cx + sgn * 0.82 * fw + 0.09 * fw, cy + 1.52 * fh], fill=rgba(accent_c, 255)) |
| 398 | base.alpha_composite(lay) |
| 399 | |
| 400 | lay = new_layer() |
| 401 | d = ImageDraw.Draw(lay) |
| 402 | d.polygon(_face_outline(cx, cy, fw, fh), fill=rgba(skin, 255)) |
| 403 | base.alpha_composite(lay) |
| 404 | |
| 405 | # short tidy hair |
| 406 | lay = new_layer() |
| 407 | d = ImageDraw.Draw(lay) |
| 408 | hair = smooth_path([ |
| 409 | ((cx - 0.98 * fw, cy - 0.28 * fh), (cx - 1.07 * fw, cy - 1.22 * fh), (cx + 0.62 * fw, cy - 1.40 * fh), (cx + 0.99 * fw, cy - 0.38 * fh)), |
| 410 | ((cx + 0.99 * fw, cy - 0.38 * fh), (cx + 0.88 * fw, cy - 0.62 * fh), (cx + 0.58 * fw, cy - 0.80 * fh), (cx + 0.28 * fw, cy - 0.72 * fh)), |
| 411 | ((cx + 0.28 * fw, cy - 0.72 * fh), (cx - 0.06 * fw, cy - 0.88 * fh), (cx - 0.5 * fw, cy - 0.82 * fh), (cx - 0.70 * fw, cy - 0.54 * fh)), |
| 412 | ((cx - 0.70 * fw, cy - 0.54 * fh), (cx - 0.88 * fw, cy - 0.64 * fh), (cx - 0.97 * fw, cy - 0.48 * fh), (cx - 0.98 * fw, cy - 0.28 * fh)), |
| 413 | ]) |
| 414 | d.polygon(hair, fill=rgba(hair_c, 255)) |
| 415 | base.alpha_composite(lay) |
| 416 | |
| 417 | # face: happy closed eyes (^^), round glasses |
| 418 | lay = new_layer() |
| 419 | d = ImageDraw.Draw(lay) |
| 420 | ink = mix(skin, "#000000", 0.78) |
| 421 | for sgn in (-1, 1): |
| 422 | ex = cx + sgn * 0.42 * fw |
| 423 | d.ellipse([ex - 0.26 * fw, cy - 0.20 * fh, ex + 0.26 * fw, cy + 0.30 * fh], outline=rgba(glasses_c, 235), width=max(3, int(0.045 * fw))) |
| 424 | pts = cubic((ex - 0.14 * fw, cy + 0.08 * fh), (ex - 0.05 * fw, cy - 0.03 * fh), (ex + 0.05 * fw, cy - 0.03 * fh), (ex + 0.14 * fw, cy + 0.08 * fh), n=20) |
| 425 | d.line(pts, fill=rgba(ink, 240), width=max(3, int(0.045 * fw)), joint="curve") |
| 426 | d.line([(cx - 0.16 * fw, cy + 0.05 * fh), (cx + 0.16 * fw, cy + 0.05 * fh)], fill=rgba(glasses_c, 235), width=max(3, int(0.04 * fw))) |
| 427 | d.line([(cx - 0.68 * fw, cy + 0.02 * fh), (cx - 0.88 * fw, cy - 0.05 * fh)], fill=rgba(glasses_c, 235), width=max(3, int(0.04 * fw))) |
| 428 | d.line([(cx + 0.68 * fw, cy + 0.02 * fh), (cx + 0.88 * fw, cy - 0.05 * fh)], fill=rgba(glasses_c, 235), width=max(3, int(0.04 * fw))) |
| 429 | # open happy mouth |
| 430 | mouth = smooth_path([ |
| 431 | ((cx - 0.16 * fw, cy + 0.5 * fh), (cx - 0.05 * fw, cy + 0.52 * fh), (cx + 0.05 * fw, cy + 0.52 * fh), (cx + 0.16 * fw, cy + 0.5 * fh)), |
| 432 | ((cx + 0.16 * fw, cy + 0.5 * fh), (cx + 0.08 * fw, cy + 0.68 * fh), (cx - 0.08 * fw, cy + 0.68 * fh), (cx - 0.16 * fw, cy + 0.5 * fh)), |
| 433 | ]) |
| 434 | d.polygon(mouth, fill=rgba(mix(skin, "#7a2e2e", 0.6), 235)) |
| 435 | _blush(lay, cx - 0.62 * fw, cy + 0.34 * fh, fw, (238, 140, 110), alpha=85) |
| 436 | _blush(lay, cx + 0.62 * fw, cy + 0.34 * fh, fw, (238, 140, 110), alpha=85) |
| 437 | base.alpha_composite(lay) |
| 438 | |
| 439 | |
| 440 | def performer(base, cx, cy, s, skin, hair_c, cloth_c, glow_c): |
| 441 | """Original adult digital stage performer with headset mic, high ponytail.""" |
| 442 | fw, fh = s, s * 1.18 |
| 443 | _figure_shadow(base, cx, cy, fw, fh, alpha=44) |
| 444 | |
| 445 | # ponytail |
| 446 | lay = new_layer() |
| 447 | d = ImageDraw.Draw(lay) |
| 448 | tail = smooth_path([ |
| 449 | ((cx + 0.55 * fw, cy - 1.35 * fh), (cx + 1.6 * fw, cy - 1.2 * fh), (cx + 2.1 * fw, cy + 0.2 * fh), (cx + 1.85 * fw, cy + 1.6 * fh)), |
| 450 | ((cx + 1.85 * fw, cy + 1.6 * fh), (cx + 1.25 * fw, cy + 0.9 * fh), (cx + 0.85 * fw, cy + 0.2 * fh), (cx + 0.7 * fw, cy - 0.6 * fh)), |
| 451 | ]) |
| 452 | d.polygon(tail, fill=rgba(mix(hair_c, "#000000", 0.10), 255)) |
| 453 | base.alpha_composite(lay) |
| 454 | |
| 455 | lay = new_layer() |
| 456 | d = ImageDraw.Draw(lay) |
| 457 | d.polygon(_neck_pts(cx, cy, fw, fh), fill=rgba(mix(skin, "#000000", 0.07), 255)) |
| 458 | sh = _shoulders_pts(cx, cy + 1.45 * fh, 3.3 * fw, 2.2 * fh) |
| 459 | d.polygon(sh, fill=rgba(cloth_c, 255)) |
| 460 | # jacket collar wings |
| 461 | d.polygon([(cx - 0.46 * fw, cy + 1.5 * fh), (cx - 0.12 * fw, cy + 2.1 * fh), (cx - 0.55 * fw, cy + 2.0 * fh), (cx - 0.75 * fw, cy + 1.65 * fh)], fill=rgba(mix(cloth_c, glow_c, 0.25), 255)) |
| 462 | d.polygon([(cx + 0.46 * fw, cy + 1.5 * fh), (cx + 0.12 * fw, cy + 2.1 * fh), (cx + 0.55 * fw, cy + 2.0 * fh), (cx + 0.75 * fw, cy + 1.65 * fh)], fill=rgba(mix(cloth_c, glow_c, 0.25), 255)) |
| 463 | base.alpha_composite(lay) |
| 464 | |
| 465 | lay = new_layer() |
| 466 | d = ImageDraw.Draw(lay) |
| 467 | d.polygon(_face_outline(cx, cy, fw, fh), fill=rgba(skin, 255)) |
| 468 | base.alpha_composite(lay) |
| 469 | |
| 470 | # swept hair + fringe |
| 471 | lay = new_layer() |
| 472 | d = ImageDraw.Draw(lay) |
| 473 | hair = smooth_path([ |
| 474 | ((cx - 1.0 * fw, cy - 0.3 * fh), (cx - 1.1 * fw, cy - 1.3 * fh), (cx + 0.7 * fw, cy - 1.5 * fh), (cx + 1.05 * fw, cy - 0.4 * fh)), |
| 475 | ((cx + 1.05 * fw, cy - 0.4 * fh), (cx + 0.8 * fw, cy - 0.7 * fh), (cx + 0.5 * fw, cy - 0.9 * fh), (cx + 0.2 * fw, cy - 0.82 * fh)), |
| 476 | ((cx + 0.2 * fw, cy - 0.82 * fh), (cx - 0.2 * fw, cy - 1.0 * fh), (cx - 0.6 * fw, cy - 0.9 * fh), (cx - 0.8 * fw, cy - 0.6 * fh)), |
| 477 | ((cx - 0.8 * fw, cy - 0.6 * fh), (cx - 0.95 * fw, cy - 0.7 * fh), (cx - 1.0 * fw, cy - 0.5 * fh), (cx - 1.0 * fw, cy - 0.3 * fh)), |
| 478 | ]) |
| 479 | d.polygon(hair, fill=rgba(hair_c, 255)) |
| 480 | base.alpha_composite(lay) |
| 481 | |
| 482 | # headset: band over crown + mic boom to mouth |
| 483 | lay = new_layer() |
| 484 | d = ImageDraw.Draw(lay) |
| 485 | band = cubic((cx - 0.85 * fw, cy - 0.45 * fh), (cx - 0.6 * fw, cy - 1.45 * fh), (cx + 0.6 * fw, cy - 1.45 * fh), (cx + 0.85 * fw, cy - 0.45 * fh), n=40) |
| 486 | d.line(band, fill=rgba(mix(cloth_c, "#000000", 0.3), 255), width=max(3, int(0.09 * fw))) |
| 487 | d.ellipse([cx + 0.72 * fw, cy - 0.28 * fh, cx + 1.02 * fw, cy + 0.12 * fh], fill=rgba(mix(cloth_c, "#000000", 0.3), 255)) |
| 488 | boom = cubic((cx + 0.88 * fw, cy + 0.05 * fh), (cx + 0.95 * fw, cy + 0.45 * fh), (cx + 0.5 * fw, cy + 0.62 * fh), (cx + 0.28 * fw, cy + 0.58 * fh), n=30) |
| 489 | d.line(boom, fill=rgba(mix(cloth_c, "#000000", 0.3), 255), width=max(2, int(0.05 * fw))) |
| 490 | d.ellipse([cx + 0.18 * fw, cy + 0.5 * fh, cx + 0.34 * fw, cy + 0.66 * fh], fill=rgba(glow_c, 255)) |
| 491 | |
| 492 | ink = mix(skin, "#000000", 0.72) |
| 493 | _closed_eye(d, cx - 0.42 * fw, cy + 0.0 * fh, fw, ink, lashes=2) |
| 494 | _open_eye(lay, cx + 0.42 * fw, cy + 0.02 * fh, fw, mix(glow_c, "#000000", 0.25)) |
| 495 | _brow(d, cx - 0.42 * fw, cy - 0.28 * fh, fw, mix(hair_c, "#000000", 0.25)) |
| 496 | _brow(d, cx + 0.42 * fw, cy - 0.28 * fh, fw, mix(hair_c, "#000000", 0.25)) |
| 497 | _smile(d, cx, cy + 0.52 * fh, fw, mix(skin, "#8a4040", 0.6), width=0.15) |
| 498 | _blush(lay, cx - 0.56 * fw, cy + 0.3 * fh, fw, glow_c, alpha=55) |
| 499 | _blush(lay, cx + 0.56 * fw, cy + 0.3 * fh, fw, glow_c, alpha=55) |
| 500 | base.alpha_composite(lay) |
| 501 |