A 16-bit RGB 5/6/5 image format for canvases that must fit in 8 MB - #2
Open
mariusandra wants to merge 2 commits into
Open
A 16-bit RGB 5/6/5 image format for canvases that must fit in 8 MB#2mariusandra wants to merge 2 commits into
mariusandra wants to merge 2 commits into
Conversation
Image gains a format field: pfRgbx (everything as before) or pfRgb565, a packed 16-bit RGB surface with no alpha. newImage565 / newImage565Over create one; view, copy, subImage, flips, rotate90 carry the format; the unsafe accessors, fill, the draw family (blendRect, drawSmooth, drawCorrect), the rasterizer's coverage/hits kernels, fillGradient, applyOpacity, invert, ceil, blur and the decoder write seams (RowBoxSampler, JPEG via unsafe[]=, WebP, copyIntoTarget) all handle it. Encoders read through toContiguousSeq, which expands. shadow/spread raise; minify/magnify convert through an RGBX copy; the opaque/transparent predicates answer truthfully. The SIMD variants guard on the format too: on amd64/arm64 hasSimd replaces the scalar body outright, so a check only in the scalar body would be compiled out exactly where a NEON kernel would read a 565 buffer as RGBX. RGBX output is unchanged — every xray score is identical to before. tests/test_rgb565.nim draws every scenario onto an RGBX and a 565 canvas and requires the quantised RGBX result to match: bit-exact for single layers over representable backdrops, one 5-bit step for compound cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A FrameOS cache asks "does this value alias the live canvas?" by comparing buffer pointers; with a 565 canvas the RGBX pointer of both sides is nil and the answer was wrong. bufferPointer answers by whichever buffer the format uses; byteSize is width * height * bytesPerPixel for the memory limits that used to write the 4 out by hand. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
`Image` gains a `format` field: `pfRgbx` (everything as before) or `pfRgb565`, a packed 16-bit RGB surface with no alpha — half the memory, for a final canvas that opaque geometry and decoded pictures are composited onto and a display driver then reads.
What works on a 565 image: `newImage565` / `newImage565Over` (caller-owned buffer); `view`, `copy`, `subImage`, flips, `rotate90` carry the format; `unsafe[]`/`[]`, `fill`, the whole `draw` family (blendRect, drawSmooth, drawCorrect, any blend mode, either side 565), the rasterizer's coverage and hits kernels (AA paths and text), `fillGradient`, `applyOpacity`, `invert`, `ceil`, `blur`, the decoder write seams (RowBoxSampler, JPEG via `unsafe[]=`, WebP, `copyIntoTarget`), encoders through `toContiguousSeq`. `shadow`/`spread` raise; minify/magnify convert through an RGBX copy; the opaque/transparent predicates answer truthfully. `bufferPointer`/`byteSize` for identity and size that do not assume 4 bytes.
RGBX is untouched: every xray score in the suite is identical to before this branch.
The SIMD variants guard on the format too — on amd64/arm64 `hasSimd` replaces the scalar body outright, so a check only in the scalar body would be compiled out exactly where a NEON kernel would read a 565 buffer as RGBX. The 565 implementations live in `rgb565.nim`, shared by both.
`tests/test_rgb565.nim` is an oracle: each scenario is drawn onto an RGBX and a 565 canvas and the quantised RGBX result must match — bit-exact for a single layer over a representable backdrop, one 5-bit step for compound cases. 31 scenarios: fills, AA paths/strokes, text, image draws on every path, mask/multiply blends, gradients, opacity, views, the Context API, SVG, scaled/streamed decodes.
Why: a FrameOS scene renders on an ESP32-S3 with 8 MB of PSRAM; a 1200×1600 RGBA canvas is 7.3 MiB and does not fit, a 565 one is 3.7 MiB and does. The consumer is FrameOS/frameos#375.
🤖 Generated with Claude Code