Cinco bocetos de monigotes de un trazo en zigzag, en piña de colores
docs/bocetos/boceto-1..5.svg y su generador tools/bocetos.py: contenedor, la piña sale a la calle, el portal, calle en diagonal y ausencias.
This commit is contained in:
parent
987893d33c
commit
efedefe535
7 changed files with 190 additions and 0 deletions
24
README.md
24
README.md
|
|
@ -145,6 +145,30 @@ Una calle corta vista en diagonal, con el punto de fuga a la derecha. Solo se ve
|
||||||
python3 tools/calle.py docs/calle.svg
|
python3 tools/calle.py docs/calle.svg
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bocetos nuevos (a la manera de Suso33)
|
||||||
|
|
||||||
|
Cinco bocetos para sustituir el dibujo de la portada. Cada monigote es un solo trazo de spray en zigzag: una espiral para la cabeza y un cuerpo alto que se afila hacia los pies, como en las *Ausencias* y en el contenedor de Suso33. Van en piña: el centro en colores de fuego (blanco, amarillo, naranja, rojo) y alrededor fríos (morados, azules, blanco y negro). Se generan con `python3 tools/bocetos.py docs/bocetos`.
|
||||||
|
|
||||||
|
1. **Contenedor**: la piña de fuego sobre una pared gris.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. **La piña sale a la calle**: la masa avanza hacia la derecha y se va soltando.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. **El portal**: del hueco sale una fila de gente y el fuego va delante.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. **Calle en diagonal**: la piña baja por la calle hacia quien mira.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. **Ausencias**: todos en negro y gris, y solo el corazón de la piña en fuego.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 7. Maqueta
|
## 7. Maqueta
|
||||||
|
|
||||||
`mockup/index.html` es HTML y CSS, sin JavaScript. Se abre directamente en el navegador. Sigue el estilo de acampada26s.net (blanco y negro, bordes de 3 px, sombra de color): firma en la cabecera, portada con el lema y el dibujo debajo, y debajo el deck.Capturas al principio de este README.
|
`mockup/index.html` es HTML y CSS, sin JavaScript. Se abre directamente en el navegador. Sigue el estilo de acampada26s.net (blanco y negro, bordes de 3 px, sombra de color): firma en la cabecera, portada con el lema y el dibujo debajo, y debajo el deck.Capturas al principio de este README.
|
||||||
|
|
|
||||||
6
docs/bocetos/boceto-1.svg
Normal file
6
docs/bocetos/boceto-1.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 58 KiB |
6
docs/bocetos/boceto-2.svg
Normal file
6
docs/bocetos/boceto-2.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 44 KiB |
6
docs/bocetos/boceto-3.svg
Normal file
6
docs/bocetos/boceto-3.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 27 KiB |
6
docs/bocetos/boceto-4.svg
Normal file
6
docs/bocetos/boceto-4.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 141 KiB |
6
docs/bocetos/boceto-5.svg
Normal file
6
docs/bocetos/boceto-5.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 35 KiB |
136
tools/bocetos.py
Normal file
136
tools/bocetos.py
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
# 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 = '''<defs>
|
||||||
|
<filter id="spray" x="-30%" y="-10%" width="160%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" seed="3" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="2" result="d"/><feGaussianBlur in="d" stdDeviation=".7" result="b"/><feGaussianBlur in="SourceGraphic" stdDeviation="4" result="n"/><feComponentTransfer in="n" result="n2"><feFuncA type="linear" slope=".35"/></feComponentTransfer><feMerge><feMergeNode in="n2"/><feMergeNode in="b"/></feMerge></filter>
|
||||||
|
<filter id="brocha" x="-10%" y="-10%" width="120%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.02 0.15" numOctaves="3" seed="4" result="t"/><feDisplacementMap in="SourceGraphic" in2="t" scale="6"/></filter>
|
||||||
|
<filter id="halo" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="40"/></filter>
|
||||||
|
<filter id="sombra"><feGaussianBlur stdDeviation="3"/></filter>
|
||||||
|
</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'<path d="M{x-h*.05:.1f},{y:.1f} L{x+h*.6:.1f},{y+h*.14:.1f} L{x+h*.64:.1f},{y+h*.11:.1f} L{x+h*.05:.1f},{y-2:.1f}Z" fill="#111" opacity=".14" filter="url(#sombra)"/>')
|
||||||
|
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'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linejoin="round" stroke-linecap="round" filter="url(#spray)"/>')
|
||||||
|
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'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linecap="round" filter="url(#spray)"/>')
|
||||||
|
|
||||||
|
def portal(o, x, y, w, h, fondo="#111"):
|
||||||
|
o.append(f'<g filter="url(#brocha)"><rect x="{x}" y="{y-h}" width="{w}" height="{h}" fill="{fondo}" opacity=".9"/>'
|
||||||
|
f'<path d="M{x-8},{y} L{x-8},{y-h-8} L{x+w+8},{y-h-8} L{x+w+8},{y}" fill="none" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
|
||||||
|
|
||||||
|
def svg(cuerpo, fondo="#fff"):
|
||||||
|
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">{DEFS}'
|
||||||
|
f'<rect width="{W}" height="{H}" fill="{fondo}"/>' + "".join(cuerpo) + "</svg>\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('<circle cx="800" cy="400" r="190" fill="#ffb627" opacity=".35" filter="url(#halo)"/>')
|
||||||
|
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('<g filter="url(#brocha)"><path d="M40,640 L1560,632" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
|
||||||
|
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('<g filter="url(#brocha)"><path d="M40,610 L1560,602" stroke="#111" stroke-width="7" stroke-linecap="round"/></g>')
|
||||||
|
portal(o, 150, 610, 190, 380)
|
||||||
|
o.append('<circle cx="330" cy="430" r="130" fill="#ffb627" opacity=".35" filter="url(#halo)"/>')
|
||||||
|
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('<g filter="url(#brocha)"><path d="M40,690 L1180,300" stroke="#111" stroke-width="9" stroke-linecap="round"/>'
|
||||||
|
'<path d="M330,700 L1260,305" stroke="#f04e23" stroke-width="3" stroke-linecap="round" opacity=".9"/></g>')
|
||||||
|
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))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue