"""Textures for the jack-o'-lantern (Tidewater fire pit, carved live on stream 2026-10-10). hear-venv python. skin.png: pumpkin skin for an SL sphere (u wraps around, v pole to pole): 10 ribs, darker grooves, a little mottling, darker near the poles. 512x256. face.png: the carved face as an alpha mask for a second, slightly larger sphere: triangle eyes, a triangle nose, a jagged grin. Opaque warm yellow where carved, transparent elsewhere; centred at u=0.5, v=0.5 (so the offset decides which way it looks). 512x256, RGBA. Claude Opus 5.5 (Tessel).""" from pathlib import Path import numpy as np from PIL import Image, ImageDraw, ImageFilter HERE = Path(__file__).parent W, H = 512, 256 rng = np.random.default_rng(31) u = np.linspace(0, 1, W, endpoint=False)[None, :] v = np.linspace(0, 1, H)[:, None] ribs = 0.5 + 0.5 * np.cos(u * 2 * np.pi * 10) # 1 on the rib crowns, 0 in the grooves groove = np.clip(1 - ribs, 0, 1) ** 6 # thin dark grooves pole = np.abs(v - 0.5) * 2 # 0 at the equator, 1 at the poles noise = np.asarray(Image.fromarray((rng.random((H // 8, W // 8)) * 255).astype(np.uint8)) .resize((W, H), Image.BICUBIC), np.float32) / 255 shade = 0.78 + 0.22 * ribs ** 0.6 - 0.45 * groove - 0.25 * pole ** 3 + 0.08 * (noise - 0.5) base = np.array([232, 118, 26], np.float32) # pumpkin orange dark = np.array([150, 62, 10], np.float32) col = dark + (base - dark) * np.clip(shade, 0, 1)[..., None] Image.fromarray(np.clip(col, 0, 255).astype(np.uint8)).save(HERE / "skin.png") # Face: drawn in a box around the centre. On a sphere u spans 360 degrees, so the face's width in u is small. S = 4 # supersample for smooth edges img = Image.new("L", (W * S, H * S), 0) d = ImageDraw.Draw(img) def P(pu, pv): return (pu * W * S, pv * H * S) cu, cv = 0.5, 0.5 for side in (-1, 1): # eyes: triangles pointing up ex = cu + side * 0.055 d.polygon([P(ex - 0.035, cv - 0.06), P(ex + 0.035, cv - 0.06), P(ex, cv - 0.20)], fill=255) d.polygon([P(cu - 0.018, cv + 0.02), P(cu + 0.018, cv + 0.02), P(cu, cv - 0.05)], fill=255) # nose grin = [P(cu - 0.11, cv + 0.06)] # jagged grin: top edge with two teeth for x, y in ((-0.07, 0.10), (-0.05, 0.08), (-0.03, 0.10), (0.03, 0.10), (0.05, 0.08), (0.07, 0.10)): grin.append(P(cu + x, cv + y)) grin += [P(cu + 0.11, cv + 0.06), P(cu + 0.08, cv + 0.17), P(cu + 0.02, cv + 0.19), P(cu, cv + 0.16), P(cu - 0.02, cv + 0.19), P(cu - 0.08, cv + 0.17)] d.polygon(grin, fill=255) mask = img.resize((W, H), Image.LANCZOS).filter(ImageFilter.GaussianBlur(0.6)) a = np.asarray(mask, np.float32) / 255 glow = np.zeros((H, W, 4), np.uint8) glow[..., 0], glow[..., 1], glow[..., 2] = 255, 196, 70 # candle yellow glow[..., 3] = (a * 255).astype(np.uint8) Image.fromarray(glow, "RGBA").save(HERE / "face.png") print(HERE / "skin.png", HERE / "face.png")