Dibujo «¡A la calle!» y su generador
This commit is contained in:
parent
773d952d87
commit
e3a5d631bf
2 changed files with 286 additions and 0 deletions
119
tools/calle.py
Normal file
119
tools/calle.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Genera docs/calle.svg: ilustración stencil "a la calle"
|
||||
import sys, random
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.pens.svgPathPen import SVGPathPen
|
||||
from fontTools.pens.transformPen import TransformPen
|
||||
random.seed(26)
|
||||
FONT, OUT = sys.argv[1], sys.argv[2]
|
||||
W, H = 1200, 640
|
||||
INK, RED = "#000", "#ff1f1f"
|
||||
out = []
|
||||
def add(s): out.append(s)
|
||||
|
||||
def text_path(txt, x, y, size, fill, anchor="middle", font=FONT):
|
||||
f = TTFont(font); gs = f.getGlyphSet(); cmap = f.getBestCmap()
|
||||
upm = f["head"].unitsPerEm; k = size / upm
|
||||
widths = [f["hmtx"][cmap[ord(c)]][0] for c in txt]
|
||||
total = sum(widths) * k
|
||||
cx = x - total / 2 if anchor == "middle" else x
|
||||
d = []
|
||||
for c, w in zip(txt, widths):
|
||||
pen = SVGPathPen(gs)
|
||||
gs[cmap[ord(c)]].draw(TransformPen(pen, (k, 0, 0, -k, cx, y)))
|
||||
d.append(pen.getCommands()); cx += w * k
|
||||
return f'<path fill="{fill}" d="{" ".join(d)}"/>'
|
||||
|
||||
def blob(cx, cy, r, n, jit, fill, op=1):
|
||||
import math
|
||||
pts = []
|
||||
for i in range(n):
|
||||
a = 2 * math.pi * i / n
|
||||
rr = r * (1 + random.uniform(-jit, jit))
|
||||
if random.random() < .25: rr *= 1.25
|
||||
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
|
||||
d = f"M{pts[0][0]:.0f},{pts[0][1]:.0f} " + " ".join(
|
||||
f"Q{p[0]:.0f},{p[1]:.0f} {(p[0]+q[0])/2:.0f},{(p[1]+q[1])/2:.0f}"
|
||||
for p, q in zip(pts, pts[1:] + pts[:1])) + "Z"
|
||||
add(f'<path d="{d}" fill="{fill}" opacity="{op}"/>')
|
||||
|
||||
def splats(cx, cy, spread, n, rmax, fill):
|
||||
for _ in range(n):
|
||||
x = cx + random.gauss(0, spread); y = cy + random.gauss(0, spread * .6)
|
||||
add(f'<circle cx="{x:.0f}" cy="{y:.0f}" r="{random.uniform(2, rmax):.1f}" fill="{fill}"/>')
|
||||
|
||||
def drips(x0, x1, y, fill, n):
|
||||
for _ in range(n):
|
||||
x = random.uniform(x0, x1); l = random.uniform(15, 70); w = random.uniform(5, 11)
|
||||
add(f'<path d="M{x-w/2:.0f},{y} v{l:.0f} a{w/2:.1f},{w/2:.1f} 0 0 0 {w:.0f},0 v-{l:.0f}z" fill="{fill}"/>')
|
||||
|
||||
def tent(x, y, w, h):
|
||||
add(f'<path d="M{x},{y} L{x+w/2},{y-h} L{x+w},{y} Z" fill="#fff" stroke="{INK}" stroke-width="8" stroke-linejoin="round"/>')
|
||||
add(f'<path d="M{x+w/2},{y-h} L{x+w*.38},{y} L{x+w*.62},{y} Z" fill="{INK}"/>')
|
||||
add(f'<path d="M{x+w/2},{y-h} l0,-22 l26,9 l-26,9" fill="{RED}" stroke="{INK}" stroke-width="4" stroke-linejoin="round"/>')
|
||||
|
||||
def fig(x, y, s=1.0, pose="down", step=1):
|
||||
L = lambda *p: " ".join(f"{a:.0f}" for a in p)
|
||||
sw = 19 * s
|
||||
st = f'stroke="{INK}" stroke-width="{sw:.0f}" stroke-linecap="round" stroke-linejoin="round" fill="none"'
|
||||
hipy = y - 78 * s; shy = y - 150 * s
|
||||
# piernas (andando)
|
||||
add(f'<path d="M{L(x-9*s, hipy)} L{L(x-9*s-24*s*step, y-38*s)} L{L(x-12*s-30*s*step, y)}" {st}/>')
|
||||
add(f'<path d="M{L(x+9*s, hipy)} L{L(x+14*s+16*s*step, y-40*s)} L{L(x+20*s+26*s*step, y)}" {st}/>')
|
||||
# torso (chupa)
|
||||
add(f'<path d="M{L(x-31*s, shy)} L{L(x+31*s, shy)} L{L(x+24*s, hipy+6*s)} L{L(x-24*s, hipy+6*s)} Z" fill="{INK}" stroke="{INK}" stroke-width="{8*s:.0f}" stroke-linejoin="round"/>')
|
||||
# cresta (detrás) + cabeza
|
||||
if pose in ("fist", "cresta"):
|
||||
for i in range(5):
|
||||
dx = (i - 2) * 11 * s
|
||||
add(f'<path d="M{L(x+dx-8*s, shy-40*s)} L{L(x+dx*1.9, shy-112*s)} L{L(x+dx+8*s, shy-40*s)} Z" fill="{RED}" stroke="{INK}" stroke-width="{5*s:.0f}" stroke-linejoin="round"/>')
|
||||
add(f'<circle cx="{x:.0f}" cy="{shy-36*s:.0f}" r="{24*s:.0f}" fill="{INK}"/>')
|
||||
# brazos
|
||||
ls, rs = (x - 27 * s, shy + 8 * s), (x + 27 * s, shy + 8 * s)
|
||||
if pose == "fist":
|
||||
add(f'<path d="M{L(*rs)} L{L(x+52*s, shy-38*s)} L{L(x+50*s, shy-100*s)}" {st}/>')
|
||||
add(f'<rect x="{x+33*s:.0f}" y="{shy-138*s:.0f}" width="{34*s:.0f}" height="{40*s:.0f}" rx="{10*s:.0f}" fill="{INK}"/>')
|
||||
add(f'<path d="M{L(*ls)} L{L(x-46*s, shy+50*s)} L{L(x-40*s, shy+88*s)}" {st}/>')
|
||||
elif pose.startswith("pole"):
|
||||
side = -1 if pose == "poleL" else 1
|
||||
px = x + side * 46 * s
|
||||
add(f'<path d="M{L(*(rs if side>0 else ls))} L{L(px, shy-20*s)}" {st}/>')
|
||||
add(f'<path d="M{L(*(ls if side>0 else rs))} L{L(x, shy+30*s)} L{L(px, shy+20*s)}" {st}/>')
|
||||
else:
|
||||
add(f'<path d="M{L(*ls)} L{L(x-44*s, shy+50*s)} L{L(x-38*s, shy+90*s)}" {st}/>')
|
||||
add(f'<path d="M{L(*rs)} L{L(x+44*s, shy+50*s)} L{L(x+38*s, shy+90*s)}" {st}/>')
|
||||
|
||||
add(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">')
|
||||
add(f'<rect width="{W}" height="{H}" fill="#fff"/>')
|
||||
# mancha roja de spray detrás
|
||||
blob(600, 300, 250, 22, .18, RED)
|
||||
splats(600, 300, 230, 70, 9, RED)
|
||||
drips(420, 780, 470, RED, 9)
|
||||
# calle: asfalto + paso de cebra
|
||||
GY = 540
|
||||
add(f'<rect x="0" y="{GY}" width="{W}" height="{H-GY}" fill="{INK}"/>')
|
||||
for i in range(-2, 14):
|
||||
x = i * 100
|
||||
add(f'<path d="M{x+30},{GY+14} L{x+80},{GY+14} L{x+60},{H-14} L{x-10},{H-14} Z" fill="#fff"/>')
|
||||
# tiendas al fondo
|
||||
tent(40, GY, 190, 140); tent(190, GY, 140, 100)
|
||||
tent(930, GY, 200, 150); tent(1070, GY, 120, 90)
|
||||
# pancarta entre dos palos
|
||||
bx0, bx1, by = 300, 900, 50
|
||||
add(f'<path d="M{bx0},{by} L{bx1},{by} L{bx1},{by+115} Q{(bx0+bx1)/2},{by+140} {bx0},{by+115} Z" fill="#fff" stroke="{INK}" stroke-width="9" stroke-linejoin="round"/>')
|
||||
add(text_path("¡A LA CALLE!", 600, by + 86, 74, INK))
|
||||
drips(bx0 + 40, bx1 - 40, by + 122, INK, 6)
|
||||
add(f'<line x1="{bx0}" y1="{by-20}" x2="{bx0}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>')
|
||||
add(f'<line x1="{bx1}" y1="{by-20}" x2="{bx1}" y2="{GY-150}" stroke="{INK}" stroke-width="12" stroke-linecap="round"/>')
|
||||
# gente
|
||||
fig(bx0 + 46, GY - 4, 1.05, "poleL", 1)
|
||||
fig(470, GY - 2, .95, "down", -1)
|
||||
fig(600, GY, 1.15, "fist", 1)
|
||||
fig(730, GY - 2, .95, "cresta", -1)
|
||||
fig(bx1 - 46, GY - 4, 1.05, "poleR", 1)
|
||||
# estrellas
|
||||
for (sx, sy, r) in [(120, 90, 26), (1080, 110, 32), (990, 300, 16), (200, 300, 14)]:
|
||||
import math
|
||||
pts = " ".join(f"{sx + (r if i%2==0 else r*.42)*math.cos(math.pi/2 + i*math.pi/5):.0f},{sy - (r if i%2==0 else r*.42)*math.sin(math.pi/2 + i*math.pi/5):.0f}" for i in range(10))
|
||||
add(f'<polygon points="{pts}" fill="{INK}"/>')
|
||||
add('</svg>')
|
||||
open(OUT, "w").write("\n".join(out))
|
||||
Loading…
Add table
Add a link
Reference in a new issue