Fix image loaders and redesign window shadows
Merged
JS
3d ago+3125+3125+0123456789012345678901234567890123456789-2112-2112-0123456789012345678901234567890123456789
Two commits.
Fix image loader corruption and decode GIF frames on demand
- PNG: on macOS colors were wrong (orange showed as magenta) because an Apple-only pass swizzled pixels a second time. libpng now writes native ARGB, and only images with alpha are premultiplied. 16-bit and sub-byte PNGs now load, and an A8 + tRNS row overflow is fixed.
- JPEG:
pixis volatile acrosssetjmp(), the A8 path copies pixels instead of row pointers, and a dead branch is removed. - TinyVG: color indices are now bounds-checked (out-of-bounds heap reads, reproduced under ASan), allocation sizes are overflow-checked (heap overflow on wasm32, reproduced), and varuints are capped.
- GIF: frames decode on demand into the returned pixmap, so
nyancat.gifdrops from about 880 KB to 64 KB. Transparency is real alpha (it used to be a fake opaque checkerboard), disposal follows browser behavior, interlaced frames 2 to 4 rows tall are fixed, an LZW clear at a code-size bump no longer desyncs, off-canvas frames are clipped, and GIF87a is accepted.
Cast window shadows from the frame and mask them by window alpha
- Shadows are cast by the window's shape (client plus title tab) instead of its bounding box, and masked by the window's own alpha so they only show outside the window. This fixes the stray shadow beside the title tab and see-through windows showing their own shadow.
- New look, lit from above: no horizontal offset, 4 down, blur 14. Inactive windows keep a lighter shadow.
- Geometry Kconfig options now depend on the desktop WM, and the golden fingerprint counts them only when they exist.
- The animation demo window is see-through.
Validation
make checkpasses: core 337, wm 74, lifetime 21, demos 34. Commit 1 was also built and tested on its own.- Sanitized (ASan + UBSan + pointer-overflow) runs of core, wm, lifetime and demos are clean.
- GIF matches Pillow frame for frame on 26 animations, plus 63 LZW clear patterns and 16 clipping cases. PNG matches on 20 color types. Fuzzing is clean.
- Shadow output is bit-identical to a plain per-pixel reference, and the core golden scene is unchanged. All 56 cellphone frames are identical to before.
- Cost per shadow pixel is at parity with the old code. The larger new look adds about 95 µs per full 480x320 repaint (Apple M-series host).
Note
tests/golden/cellphone.txt needs make check-update on the reference platform. Its stored fingerprint no longer matches, so cellphone golden comparisons are currently skipped. On this macOS host, 11 of its frames already differed at aa36aec.
Summary by cubic
Fixes corrupted rendering in the PNG, JPEG, and TinyVG loaders, decodes GIF frames on demand, and reworks window shadows to cast from the window's shape and mask by its alpha. The window manager and every application are now selected individually in Kconfig under top-level menus.
Image loaders
- PNG colors are correct on macOS: libpng writes native ARGB with one transform set, and only images with alpha are premultiplied. 16-bit and sub-byte PNGs now load, and an A8 row overflow is fixed.
- JPEG keeps its pixmap volatile across
setjmp(), and the A8 path copies pixels instead of row pointers. - TinyVG bounds-checks color indices, overflow-checks allocation sizes, and caps varuints, closing heap over-reads and overflows in crafted files. Its paths fill by a new even-odd rule (
twin_path_set_fill_rule()), so nested outlines incomic.tvg,tiger.tvg, andflowchart.tvgno longer paint black wedges or stray discs. - GIF frames decode into the returned pixmap on demand, so
nyancat.gifdrops from about 880 KB to 88 KB. Transparency is real alpha, disposal follows browser behavior, interlaced and off-canvas frames are fixed, and GIF87a is accepted. - The Vectors demo renders each drawing to fit its window and centers it;
twin_widget_set_stretch()keeps the button at its preferred height.
Windows and demos
- Shadows are cast by the client plus title tab instead of the bounding box and masked by the window's alpha, so they only show outside the window. The new look is lit from above, inactive windows keep a lighter shadow, and the animation demo window is see-through.
- One calculator serves both desktop and phone with a shared keypad and arithmetic, and gains an erase key; the multi-window demo is desktop-only.
- Snake, Pong, and Tetris run under any window manager as their own demos and stay themed launcher apps on the phone.
- Each application is now picked on its own in Kconfig (
DEMO_*becameAPP_*), so configs written before the rename fall back to defaults until their apps are chosen again. Golden frames record the launcher list so comparisons are skipped for other selections, and deterministic edge sorting makes the refreshed frames match on Linux and macOS alike. - The headless determinism check runs with
MADO_FROZEN_TIME=1, which stops twin's clock so every animation keeps its first frame, and now requires identical renders.