Add tools
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Linen backgrounds with pressed flowers for Simple Mercies."""
|
||||
import numpy as np, os, math
|
||||
from PIL import Image, ImageDraw, ImageFilter
|
||||
from scipy.ndimage import gaussian_filter, zoom
|
||||
|
||||
OUT = '/home/claude/mercies/app/src/main/res/drawable-nodpi'
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
W, H = 1440, 2560
|
||||
|
||||
def fbm(w, h, octaves, base, pers, seed):
|
||||
r = np.random.default_rng(seed); out = np.zeros((h, w)); amp = 1; tot = 0
|
||||
for o in range(octaves):
|
||||
n = base * 2 ** o
|
||||
g = r.random((max(2, int(n * h / w)) + 1, n + 1))
|
||||
out += amp * zoom(g, (h / (g.shape[0] - 1), w / (g.shape[1] - 1)), order=3)[:h, :w]
|
||||
tot += amp; amp *= pers
|
||||
out /= tot
|
||||
return (out - out.min()) / (out.max() - out.min())
|
||||
|
||||
def linen(c0, c1, seed, dark=False):
|
||||
r = np.random.default_rng(seed)
|
||||
# warp and weft threads
|
||||
rows = gaussian_filter(r.random(H), 0.6); cols = gaussian_filter(r.random(W), 0.6)
|
||||
yy, xx = np.mgrid[0:H, 0:W]
|
||||
weave = (np.sin(xx * 0.9) * 0.5 + 0.5) * 0.5 + (np.sin(yy * 0.9) * 0.5 + 0.5) * 0.5
|
||||
threads = rows[:, None] * 0.5 + cols[None, :] * 0.5
|
||||
slub = gaussian_filter(r.random((H, W)), (0.6, 18)) + gaussian_filter(r.random((H, W)), (18, 0.6))
|
||||
slub = (slub - slub.mean()) / slub.std()
|
||||
cloud = fbm(W, H, 6, 3, 0.55, seed + 1)
|
||||
v = 0.45 * cloud + 0.18 * threads + 0.12 * weave + 0.05 * slub
|
||||
v = (v - v.min()) / (v.max() - v.min())
|
||||
c0 = np.array(c0, float); c1 = np.array(c1, float)
|
||||
img = c0 * (1 - v[..., None]) + c1 * v[..., None]
|
||||
dx = (xx - W / 2) / (W / 2); dy = (yy - H / 2) / (H / 2)
|
||||
vig = 1 - (0.45 if dark else 0.22) * np.clip(np.sqrt(dx ** 2 + dy ** 2 * 0.85), 0, 1.5) ** 2.4
|
||||
img *= vig[..., None]
|
||||
return Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
|
||||
|
||||
def flower(d, cx, cy, size, ang, petals, col, center, rng, alpha):
|
||||
pts = []
|
||||
for i in range(petals):
|
||||
a = ang + i * 2 * math.pi / petals + rng.normal(0, 0.12)
|
||||
L = size * rng.uniform(0.85, 1.1); w = size * 0.42
|
||||
px, py = cx + math.cos(a) * L * 0.55, cy + math.sin(a) * L * 0.55
|
||||
poly = []
|
||||
for t in np.linspace(0, 2 * math.pi, 28):
|
||||
ex, ey = math.cos(t) * L * 0.55, math.sin(t) * w * 0.5
|
||||
poly.append((px + ex * math.cos(a) - ey * math.sin(a), py + ex * math.sin(a) + ey * math.cos(a)))
|
||||
d.polygon(poly, fill=col + (alpha,))
|
||||
rr = size * 0.18
|
||||
d.ellipse([cx - rr, cy - rr, cx + rr, cy + rr], fill=center + (alpha + 30,))
|
||||
|
||||
def leaf(d, x, y, L, a, col, alpha):
|
||||
poly = []
|
||||
for t in np.linspace(0, math.pi, 20):
|
||||
ex = (t / math.pi) * L; ey = math.sin(t) * L * 0.22
|
||||
poly.append((x + ex * math.cos(a) - ey * math.sin(a), y + ex * math.sin(a) + ey * math.cos(a)))
|
||||
for t in np.linspace(math.pi, 0, 20):
|
||||
ex = (t / math.pi) * L; ey = -math.sin(t) * L * 0.22
|
||||
poly.append((x + ex * math.cos(a) - ey * math.sin(a), y + ex * math.sin(a) + ey * math.cos(a)))
|
||||
d.polygon(poly, fill=col + (alpha,))
|
||||
|
||||
def sprig(layer, x0, y0, direction, seed, palette, scale=1.0):
|
||||
rng = np.random.default_rng(seed)
|
||||
d = ImageDraw.Draw(layer, 'RGBA')
|
||||
stem_col = (110, 120, 80)
|
||||
for s in range(3):
|
||||
ang = direction + rng.normal(0, 0.35)
|
||||
L = rng.uniform(380, 620) * scale
|
||||
# curved stem
|
||||
pts = []
|
||||
sign = rng.choice([-1, 1])
|
||||
for t in np.linspace(0, 1, 40):
|
||||
bend = math.sin(t * math.pi) * 60 * sign * scale
|
||||
x = x0 + math.cos(ang) * L * t - math.sin(ang) * bend
|
||||
y = y0 + math.sin(ang) * L * t + math.cos(ang) * bend
|
||||
pts.append((x, y))
|
||||
d.line(pts, fill=stem_col + (150,), width=int(5 * scale))
|
||||
for k in range(4, 36, 7):
|
||||
px, py = pts[k]
|
||||
leaf(d, px, py, rng.uniform(50, 90) * scale, ang + rng.choice([-1, 1]) * rng.uniform(0.6, 1.1), (122, 138, 92), 120)
|
||||
col, cen = palette[rng.integers(len(palette))]
|
||||
fx, fy = pts[-1]
|
||||
flower(d, fx, fy, rng.uniform(60, 90) * scale, rng.uniform(0, 6), int(rng.integers(5, 7)), col, cen, rng, 150)
|
||||
# small buds along stem
|
||||
for k in (18, 28):
|
||||
bx, by = pts[k]
|
||||
c2, cc2 = palette[rng.integers(len(palette))]
|
||||
flower(d, bx + rng.normal(0, 30), by + rng.normal(0, 30), rng.uniform(22, 34) * scale, rng.uniform(0, 6), 5, c2, cc2, rng, 130)
|
||||
|
||||
def compose(base, palette, seed, flowers_alpha=1.0):
|
||||
layer = Image.new('RGBA', (W, H), (0, 0, 0, 0))
|
||||
sprig(layer, -40, -40, math.pi * 0.28, seed, palette, 1.0)
|
||||
sprig(layer, W + 40, H + 40, math.pi * 1.25, seed + 5, palette, 1.05)
|
||||
layer = layer.filter(ImageFilter.GaussianBlur(1.2))
|
||||
if flowers_alpha < 1:
|
||||
a = np.array(layer); a[..., 3] = (a[..., 3] * flowers_alpha).astype(np.uint8); layer = Image.fromarray(a)
|
||||
out = base.convert('RGBA'); out.alpha_composite(layer)
|
||||
return out.convert('RGB')
|
||||
|
||||
day_pal = [((206, 142, 150), (226, 196, 120)), ((176, 150, 196), (232, 210, 140)), ((226, 178, 120), (190, 120, 80)), ((230, 220, 200), (220, 180, 90)), ((150, 170, 200), (230, 210, 150))]
|
||||
dusk_pal = [((150, 96, 104), (190, 160, 90)), ((120, 104, 150), (190, 170, 110)), ((170, 130, 90), (150, 96, 60))]
|
||||
|
||||
imgs = {
|
||||
'bg_linen': compose(linen((214, 202, 182), (240, 233, 219), 3), day_pal, 11),
|
||||
'bg_linen_rose': compose(linen((222, 196, 190), (245, 230, 226), 4), day_pal, 21),
|
||||
'bg_linen_sage': compose(linen((196, 204, 182), (234, 238, 224), 5), day_pal, 31),
|
||||
'bg_dusk': compose(linen((30, 28, 32), (58, 52, 56), 6, dark=True), dusk_pal, 41, 0.55),
|
||||
}
|
||||
for k, im in imgs.items():
|
||||
im.save(os.path.join(OUT, k + '.webp'), quality=82, method=6)
|
||||
im.resize((360, 640)).save('/home/claude/data/prev_' + k + '.png')
|
||||
row = Image.new('RGB', (360 * 4 + 30, 640), 'white')
|
||||
for i, k in enumerate(imgs): row.paste(Image.open('/home/claude/data/prev_' + k + '.png'), (i * 370, 0))
|
||||
row.save('/home/claude/data/prev_linen.png')
|
||||
print({k: os.path.getsize(os.path.join(OUT, k + '.webp')) // 1024 for k in imgs})
|
||||
|
||||
# transparent corner sprigs for the share pictures
|
||||
def corner(palette, seed, flip, alpha=1.0):
|
||||
S = 760
|
||||
layer = Image.new('RGBA', (S, S), (0, 0, 0, 0))
|
||||
sprig(layer, -40, -40, math.pi * 0.28, seed, palette, 0.95)
|
||||
layer = layer.filter(ImageFilter.GaussianBlur(1.0))
|
||||
if alpha < 1:
|
||||
a = np.array(layer); a[..., 3] = (a[..., 3] * alpha).astype(np.uint8); layer = Image.fromarray(a)
|
||||
return layer.rotate(180) if flip else layer
|
||||
for name, pal, a in (('day', day_pal, 1.0), ('dusk', dusk_pal, 0.6)):
|
||||
corner(pal, 11, False, a).save(os.path.join(OUT, f'sprig_{name}_tl.webp'), quality=88)
|
||||
corner(pal, 16, True, a).save(os.path.join(OUT, f'sprig_{name}_br.webp'), quality=88)
|
||||
print('sprigs ok')
|
||||
Reference in New Issue
Block a user