You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Sprite sheet export writes premultiplied RGB, so semi-transparent glows come out dark #4
Hi — thanks for the library, the effects are genuinely nice. I ran the full check.mjs suite (244/244 pass) and then hit something on the export path that looks like a real bug.
What happens
tools/lib/fxlib.mjs → spriteSheet() creates the sheet on a fully transparent canvas (canvas(cols*w, rows*h, [0,0,0,0])) and pastes each frame with canvas.paste():
That is an alpha-over blend, but the result is stored without dividing by the output alpha. With a transparent destination (dst = 0), the RGB written to the PNG is src.rgb * src.a — i.e. premultiplied — while the alpha channel is correct.
A consumer reading the PNG as straight (non-premultiplied) RGBA — Aseprite, Godot, Unity, Phaser's default — multiplies by alpha a second time when compositing, so soft edges come out darker than the source.
Repro
node tools/render.mjs fire/torch --sheet
Then crop frame 0 out of records/fire/torch/torch.png using the rects in torch.json, and compare it with the same frame rendered standalone (renderFrames(...).frames[0].clear).
Measured (fire/torch, frame 0, 72×120)
check
result
alpha channel identical to source
8,640 / 8,640 pixels
semi-transparent pixels equal to round(rgb * a / 255)
6,740 / 6,740 (≤1 per channel, 0 exceptions)
So it is a clean premultiply, not compression noise. Visually: the torch's glow is gone in the sheet frame and the smoke gets darker. In this effect 6,740 of 8,640 pixels (78%) are semi-transparent, so it affects most of the fire / lightning / holy / shadow categories.
Suggested fix
Normalize by the output alpha inside paste(). It leaves opaque destinations (the contact sheet, fill = [12,11,16,255]) exactly as they are today:
A narrower fix is to copy RGB straight when data[d + 3] === 0, but the version above also stays correct if anything is ever pasted onto a partly transparent canvas.
Environment
Windows 11, node v24.14.0, pixelfx 0.3.0 (main, 2026-11-03).
Happy to attach the side-by-side PNG (source frame vs. cropped sheet frame) if that helps.
Activity