Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(line): per-gesture alpha works in both preview and bake

- Add setBufferAlpha() to $paintApiUnwrapped — scales all non-transparent
pixels in a buffer to a uniform alpha. Deferred via the layer system so
it runs AFTER wipe/ink/line commands during flush.
- paint() draws stroke opaque, then calls setBufferAlpha for the preview.
- strokeToBake adds a synchronous safety-net scale before paste, since the
previous frame's flush guarantees the buffer has the drawn content by
bake time. Idempotent: setting alpha to the same value is a no-op.

Fixes per-gesture alpha being incorrect or missing in the bake while the
preview looked right (or vice versa).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+28 -7
+18 -7
system/public/aesthetic.computer/disks/line.mjs
··· 140 140 nopaint_generateColoredLabel("line", colorParams, savedParams, modifiers, { hud: savedHud, ...savedApi }); 141 141 } 142 142 143 - function paint({ ink, page, paste, pen, screen, num, system: { nopaint } }) { 143 + function paint({ ink, page, paste, pen, screen, setBufferAlpha, num, system: { nopaint } }) { 144 144 const isPainting = nopaint.is("painting"); 145 145 if (isPainting && !wasPainting) { 146 146 points.length = 0; ··· 180 180 } 181 181 } 182 182 } else { 183 - // Draw fully opaque — alpha is applied per-gesture below. 183 + // Draw fully opaque to the buffer. 184 184 if (thickness === 1) { 185 185 ink(opaqueParams).pppline(stroke); 186 186 } else { ··· 192 192 } 193 193 } 194 194 195 + // Defer buffer alpha scaling so it runs AFTER the deferred drawing 196 + // commands during the layer flush. Modifying buffer pixels directly 197 + // here would happen before the wipe/draw commands actually execute. 198 + if (strokeAlpha < 1) { 199 + setBufferAlpha(nopaint.buffer, strokeAlpha); 200 + } 201 + 195 202 page(screen); 196 203 204 + const bakeAlpha = strokeAlpha; 197 205 strokeToBake = () => { 198 - // Scale buffer alpha once at bake time for per-gesture transparency. 199 - if (strokeAlpha < 1) { 206 + // Sync alpha scaling on the buffer pixels — safe because this runs 207 + // in the bake block AFTER the previous frame's layer was flushed, 208 + // so the buffer has the drawn (opaque) stroke content. 209 + if (bakeAlpha < 1) { 210 + const target = Math.round(bakeAlpha * 255); 200 211 const px = nopaint.buffer.pixels; 201 212 for (let i = 3; i < px.length; i += 4) { 202 - if (px[i] > 0) px[i] = (px[i] * strokeAlpha + 0.5) | 0; 213 + if (px[i] > 0) px[i] = target; 203 214 } 204 215 } 205 216 paste(nopaint.buffer); ··· 210 221 }; 211 222 } 212 223 213 - // Brush preview circle on hover (when not painting and thickness > 2). 214 - if (!isPainting && thickness > 2 && pen) { 224 + // Brush preview circle on hover (when not painting/panning and thickness > 2). 225 + if (!isPainting && !nopaint.is("panning") && thickness > 2 && pen) { 215 226 const r = Math.floor((thickness - 1) / 2); 216 227 const a = Math.round(strokeAlpha * 100); 217 228 if (randomColor) {
+10
system/public/aesthetic.computer/lib/disk.mjs
··· 6316 6316 }, 6317 6317 lineAngle: graph.lineAngle, 6318 6318 pline: graph.pline, 6319 + // Set the alpha of every non-transparent pixel in a buffer to a uniform value. 6320 + // Useful for per-stroke alpha — must be called after drawing on the buffer. 6321 + setBufferAlpha: function (buffer, alpha) { 6322 + if (!buffer || !buffer.pixels) return; 6323 + const target = Math.round(alpha * 255); 6324 + const px = buffer.pixels; 6325 + for (let i = 3; i < px.length; i += 4) { 6326 + if (px[i] > 0) px[i] = target; 6327 + } 6328 + }, 6319 6329 pppline: graph.pixelPerfectPolyline, 6320 6330 oval: graph.oval, 6321 6331 circle: graph.circle,