# Genera docs/bocetos/boceto-1..5.svg. Monigotes de un solo trazo de spray en zigzag, # altos y juntos, en piña: el centro en colores de fuego y alrededor fríos # (a la manera del contenedor y las «Ausencias» de Suso33). import sys, random, math, os OUT = sys.argv[1] W, H = 1600, 700 FUEGO = ["#fffbe6", "#ffe066", "#ffb627", "#ff7b1c", "#f04e23", "#c8102e"] # del centro hacia fuera FRIO = ["#5b2a86", "#8e44c9", "#3b5bdb", "#6c7ae0", "#e9e6f5", "#111", "#b07cd8"] DEFS = ''' ''' def fuego(d): # d = 0 en el centro de la piña, 1 en el borde i = min(len(FUEGO) - 1, int(max(0, d + random.uniform(-.12, .12)) * len(FUEGO))) return FUEGO[i] def monigote(o, x, y, h, col, lean=0.0, paso=False, brazo=False, sombra=False): """Un trazo: cabeza en zigzag apretado y cuerpo en zigzag alto. (x, y) = pies.""" sw = max(1.1, h * .011) dy = sw * 2.0 # renglones del zigzag if sombra: o.append(f'') top = y - h pts = [] yy = top i = 0 while yy <= y: t = (yy - top) / h cx = x + lean * h * (1 - t) if t < .13: # cabeza w = h * .05 * math.sin(math.pi * t / .13) ** .6 + sw * .3 elif t < .16: # cuello w = h * .008 else: # cuerpo: hombros y se afila hacia los pies u = (t - .16) / .84 w = h * (.062 * (1 - u) ** .8 + .014) if paso and u > .5: # zancada: el zigzag se abre en dos k = (u - .5) / .5 cx += (-1 if (i // 2) % 2 else 1) * h * .035 * k w *= .6 s = -1 if i % 2 else 1 pts.append((cx + s * w + random.uniform(-.3, .3) * sw, yy)) yy += dy * random.uniform(.85, 1.15) * (.55 if t < .13 else 1) i += 1 d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in pts) o.append(f'') if brazo: # brazo en alto bx, by = x + lean * h * .84 + h * .05, top + h * .2 p, n = [], 10 for j in range(n): t = j / (n - 1) p.append((bx + t * h * .06 + (-1 if j % 2 else 1) * h * .012, by - t * h * .30)) d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in p) o.append(f'') def portal(o, x, y, w, h, fondo="#111"): o.append(f'' f'') def svg(cuerpo, fondo="#fff"): return (f'{DEFS}' f'' + "".join(cuerpo) + "\n") def fila(o, xs, y0, h0, centro, radio, frio=FRIO, lean=0.0, jit=12, hvar=.15, paso=.2, brazo=.08): """Una piña: monigotes muy juntos; los cercanos al centro, en fuego.""" for x in xs: d = abs(x - centro) / radio col = fuego(d) if d < 1 else random.choice(frio) h = h0 * random.uniform(1 - hvar, 1 + hvar) * (1.05 if d < 1 else 1) monigote(o, x, y0 + random.uniform(-jit, jit), h, col, lean, random.random() < paso, random.random() < brazo) # 1 · Contenedor: piña de fuego en una pared gris, como el contenedor def b1(): random.seed(1); o = [] o.append('') xs = sorted(random.uniform(90, 1510) for _ in range(70)) fila(o, xs[::2], 610, 470, 800, 260) fila(o, xs[1::2], 650, 500, 800, 230) return o, "#9aa0a8" # 2 · Piña que sale a la calle: en blanco, la masa avanza hacia la derecha def b2(): random.seed(2); o = [] o.append('') xs = sorted(random.gauss(820, 300) for _ in range(55)) xs = [x for x in xs if 80 < x < 1520] fila(o, xs, 630, 400, 1000, 230, lean=.12, paso=.45, brazo=.12) return o, "#fff" # 3 · El portal: del hueco sale la gente y el fuego va delante def b3(): random.seed(3); o = [] o.append('') portal(o, 150, 610, 190, 380) o.append('') for i in range(30): t = i / 29 x = 250 + t * 1250 + random.uniform(-25, 25) d = abs(t - .32) / .25 col = fuego(d) if d < 1 else random.choice(FRIO) monigote(o, x, 610 + t * 70 + random.uniform(-8, 8), 300 + t * 120, col, .1, random.random() < .45, random.random() < .12) return o, "#fff" # 4 · Calle en diagonal: la piña baja por la calle hacia quien mira def b4(): random.seed(4); o = [] o.append('' '') ps = [] for _ in range(1500): t = random.random() x = 1240 - t * 700 + random.gauss(0, 40 + t * 150) y = 300 + t * 390 + random.gauss(0, 10) if 290 < y < 695 and all(math.hypot(x - a, (y - b) * 3) > 22 + t * 50 for a, b, _ in ps): ps.append((x, y, t)) for x, y, t in sorted(ps, key=lambda p: p[1]): d = math.hypot(x - 820, (y - 520) * 1.5) / 230 col = fuego(d) if d < 1 else random.choice(FRIO) monigote(o, x, y, 60 + t * 230, col, -.1, random.random() < .35, random.random() < .1) return o, "#fff" # 5 · Ausencias: todos en negro y gris, y solo el corazón de la piña en fuego def b5(): random.seed(5); o = [] xs = sorted(random.uniform(100, 1500) for _ in range(42)) fila(o, xs, 650, 430, 900, 120, frio=["#111", "#111", "#333", "#555", "#888"], lean=.06, hvar=.12, paso=.3, brazo=.1) return o, "#fff" os.makedirs(OUT, exist_ok=True) for n, f in enumerate([b1, b2, b3, b4, b5], 1): cuerpo, fondo = f() open(f"{OUT}/boceto-{n}.svg", "w").write(svg(cuerpo, fondo))