sky: memoise the placeholder, hoist per-blob colour; fix a stale comment (review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+40
−10
sky/placeholder.go+37 −9sky/sky.css+3 −1
CI passed — run details
CI log
=== modcache === all modules verified === vet === CGO_ENABLED=0 go vet ./... === fmt === === build === CGO_ENABLED=0 go build -buildvcs=false ./... === go-test === CGO_ENABLED=0 go test -count=1 ./... ok amadan.net/oficina/estilo.git/base 0.004s ok amadan.net/oficina/estilo.git/brand 0.002s ok amadan.net/oficina/estilo.git/callui 0.005s ok amadan.net/oficina/estilo.git/chrome 0.159s ? amadan.net/oficina/estilo.git/cmd/oficina-notices [no test files] ok amadan.net/oficina/estilo.git/credits 0.005s ok amadan.net/oficina/estilo.git/credits/catalog 0.004s ok amadan.net/oficina/estilo.git/homeconnect 0.005s ok amadan.net/oficina/estilo.git/internal/lint 0.004s ? amadan.net/oficina/estilo.git/internal/tokencheck [no test files] ok amadan.net/oficina/estilo.git/menu 0.007s ok amadan.net/oficina/estilo.git/savestatus 0.075s ok amadan.net/oficina/estilo.git/sky 0.018s ok amadan.net/oficina/estilo.git/theme 0.004s
diff --git a/sky/placeholder.go b/sky/placeholder.go| index 1a8706b..5df0a36 100644 |
| --- a/sky/placeholder.go |
| +++ b/sky/placeholder.go |
| @@ -8,6 +8,7 @@ import ( |
| "image/png" |
| "math" |
| "strconv" |
| + "sync" |
| ) |
| // The placeholder is the sky in a few pixels, painted into the first |
| @@ -36,25 +37,52 @@ const ( |
| phH = 20 // the SVG layers' xMidYMid slice crops them |
| ) |
| +// placeholders memoises Placeholder: it is pure in (id, tone) and costs |
| +// milliseconds, and a page renders it on every request. |
| +var placeholders sync.Map // map[string]string, key id+"/"+tone |
| + |
| // Placeholder is the data: URI of the sky's placeholder for one scheme. |
| func Placeholder(id string, t Tone) string { |
| + key := id + "/" + string(t) |
| + if uri, ok := placeholders.Load(key); ok { |
| + return uri.(string) |
| + } |
| + uri := placeholder(id, t) |
| + placeholders.Store(key, uri) |
| + return uri |
| +} |
| + |
| +func placeholder(id string, t Tone) string { |
| img := image.NewNRGBA(image.Rect(0, 0, phW, phH)) |
| haze, clouds := hazeBlobs(id, t), cloudBlobs(id, t) |
| + type paint struct { |
| + bl blob |
| + r, g, b float64 |
| + } |
| + prep := func(bs []blob) []paint { |
| + out := make([]paint, len(bs)) |
| + for i, bl := range bs { |
| + r, g, b := hexRGB(bl.fill) |
| + if phLinear { |
| + r, g, b = lin(r), lin(g), lin(b) |
| + } |
| + out[i] = paint{bl, r, g, b} |
| + } |
| + return out |
| + } |
| + hazeP, cloudsP := prep(haze), prep(clouds) |
| for y := range phH { |
| for x := range phW { |
| // the pixel's centre on the 1600x640 canvas |
| px, py := (float64(x)+.5)*width/phW, (float64(y)+.5)*height/phH |
| var r, g, b, a float64 // premultiplied, 0..1 |
| for _, layer := range []struct { |
| - blobs []blob |
| - k float64 |
| - }{{haze, 0.7}, {clouds, 1}} { |
| - for _, bl := range layer.blobs { |
| - alpha := math.Min(1, phGain[t]*layer.k*bl.opacity*bl.at(px, py)) |
| - cr, cg, cb := hexRGB(bl.fill) |
| - if phLinear { |
| - cr, cg, cb = lin(cr), lin(cg), lin(cb) |
| - } |
| + paints []paint |
| + k float64 |
| + }{{hazeP, 0.7}, {cloudsP, 1}} { |
| + for _, p := range layer.paints { |
| + alpha := math.Min(1, phGain[t]*layer.k*p.bl.opacity*p.bl.at(px, py)) |
| + cr, cg, cb := p.r, p.g, p.b |
| r = cr*alpha + r*(1-alpha) |
| g = cg*alpha + g*(1-alpha) |
| b = cb*alpha + b*(1-alpha) |
diff --git a/sky/sky.css b/sky/sky.css| index 8e2beb0..9eb7c1e 100644 |
| --- a/sky/sky.css |
| +++ b/sky/sky.css |
| @@ -52,7 +52,9 @@ |
| /* One pair of haze and clouds per scheme; show the one that matches. |
| An explicit Appearance choice ([data-theme]) beats the OS, as it does |
| - for estilo's theme. Hidden images with loading="lazy" are not fetched. */ |
| + for estilo's theme. The other scheme's two layers are fetched too (the |
| + layers are not lazy, so the first frame never waits on layout); they |
| + are a couple of kilobytes and cached for a year. */ |
| [estilo-sky-tone="dark"] { display: none; --estilo-sky-gain: 1.55; } |
| :root[data-theme="dark"] [estilo-sky-tone="light"] { display: none; } |
| :root[data-theme="dark"] [estilo-sky-tone="dark"] { display: block; } |