← atelier

landing/art/watercolor.py

Watercolour: alpine lake, broad washes light -> dark, glazed ridges, ink pines, paper tooth.

Render it yourself: atelier render watercolor.py --preview (it imports _common.py for the palette and output folder).

watercolor.py
# Watercolour: alpine lake, broad washes light -> dark, glazed ridges, ink pines, paper tooth.
import numpy as np
from atelier import *
from _common import *

W, H = 1500, 1000
c = Canvas(W, H, bg="#f4efe4")
rng = np.random.default_rng(SEED)
sky = c.layer(blend="multiply")
sky.fill(gradient([(0, "#9dbbd6"), (0.32, "#d9e2e6"), (0.45, "#f1d2b0")]), mask=top(0.5, soft=0.05) * 0.8)
for k in range(5):                                                                   # sky washes, top-heavy
    y = 40 + k * 80
    sky.stroke([(-40, y + rng.uniform(-20, 20)), (W / 2, y + rng.uniform(-30, 30)), (W + 40, y + rng.uniform(-20, 20))],
               "watercolor", size=170 - k * 18, color="#6f9bc2" if k < 3 else "#e9b78f", opacity=0.32 - k * 0.04, seed=SEED + k)
ridges = [(0.42, 0.16, "#8c8fb8", 0.7), (0.5, 0.12, "#5f6f99", 0.8), (0.58, 0.07, "#3f5a6e", 0.85)]
for i, (y, amp, col, op) in enumerate(ridges):                                       # mountain glazes
    L = c.layer(blend="multiply", opacity=op)
    crest = ridged(2.2 + i, 4, seed=SEED + 10 + i).ridge_line(y, amp)
    L.fill_below(crest, mix(col, "#f4efe4", fbm(5, seed=SEED + 20 + i) * 0.35))
    L.apply(displace(fbm(9, 3, seed=SEED + 30 + i), 5), blur(1.2))
    L.erase(bottom(0.645, soft=0.01))                                                # stop at the far shore
snow = c.layer(opacity=0.7)
snow.fill_below(ridged(2.2, 4, seed=SEED + 10).ridge_line(0.42, 0.16), "#f8f5ee",)
snow.erase(band(0.5, 1.0, 0.12) + (1 - smoothstep(0.55, 0.75, fbm(14, 3, seed=SEED + 40))))
lake_y = 640
lake = c.layer(blend="multiply")
lake.fill(gradient([(0, "#c9d6dd"), (0.4, "#8fb0c8"), (1, "#4f7392")]).within(rect(0, lake_y, W, H - lake_y)),
          mask=rect(0, lake_y, W, H - lake_y))
for k in range(6):                                                                   # horizontal lake strokes
    y = lake_y + 40 + k * 60
    lake.stroke([(-30, y), (W * 0.5, y + rng.uniform(-6, 6)), (W + 30, y)], "watercolor", size=40,
                color="#5f86a6", opacity=0.18 + k * 0.03, seed=SEED + 60 + k)
ink = c.layer(blend="multiply")
for x0 in list(range(30, 420, 46)) + list(range(1040, 1480, 52)):                   # pine forest on both shores
    h = rng.uniform(120, 230) * (1.2 if x0 < 300 or x0 > 1300 else 0.85)
    base = lake_y + rng.uniform(-6, 10)
    ink.stroke([(x0, base), (x0 + rng.uniform(-3, 3), base - h * 0.8)], "ink", size=4, color="#1f2e2a", taper=(0, 0.9), seed=int(x0))
    for j in range(9):
        t = j / 9
        yy = base - h * (0.1 + t * 0.85)
        w = (1 - t) * h * 0.24 + 6
        ink.stroke([(x0 - w, yy + w * 0.35), (x0, yy - 4), (x0 + w, yy + w * 0.35)], "watercolor", size=14 + (1 - t) * 10,
                   color="#22413a", opacity=0.75, seed=int(x0) + j)
    ink.stroke([(x0, base + 4), (x0, base + 4 + h * 0.55)], "watercolor", size=h * 0.2, color="#2c4a4a", opacity=0.16, taper=(0, 0.8),
               seed=int(x0) + 99)                                                     # reflection
c.layer(blend="overlay", opacity=0.35).fill(paper())
c.layer()
c.apply(curves([(0.15, 0.12), (0.85, 0.88)]), grain(0.012, seed=SEED))
c.save(out("watercolor.jpg"), quality=90)