Build Pixlr
KINDAback to the verdict
A layered editor whose hard problems are keeping pixel data out of the canvas so repeated edits do not degrade it, holding large images in bounded memory, and exporting files that still carry the orientation and colour information they arrived with.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Builds the interface half of the application.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Decoding and encoding happen here, which is the only place orientation and colour profiles are visible.
Get it rustup.rs, then the platform packages listed in the Tauri prerequisites. open ↗
Verify
cargo --version prints a version and a bare Tauri app runs - have readyfree
Why Orientation handling is the bug that ships, and it is only caught by a fixture per value rather than by the one photograph on your desk.
Get it Generate them from one image with an exif tool, or use a published orientation test set.
Verify
All eight fixtures import upright in the suite - have readyfree
Why Memory behaviour is invisible until the document is bigger than the ceiling, and a sample image from a tutorial never is.
Get it A full resolution photograph from a recent camera or phone, at least twenty megapixels.
Verify
It opens, edits and exports inside the stated memory ceiling - decidefree
Why Both are asserted by tests, and picking them after watching the app fall over means choosing whatever number happens to pass.
Get it Write down the tile memory ceiling, the history byte cap and the autosave interval before Phase 2.
Verify
All three are in the configuration and named in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- documents(id, name, width, height, colour_space, format_version, created_at, updated_at) - layers(id, document_id, ordinal, name, kind, opacity, blend_mode, visible, offset_x, offset_y) - tiles(layer_id, tx, ty, bytes) · fixed size unpremultiplied RGBA, the only place pixel data lives - operations(id, document_id, seq, kind, params_json, layer_id, at) · the undo history is this table - originals(document_id, path, sha256, orientation, icc_profile, exif_json) · the imported file, kept byte for byte - exports(id, document_id, at, format, settings_json, path, flattened, profile_written) - Nothing here is a canvas element. The canvas is redrawn from tiles and is never a source of truth.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DOCUMENTS_DIR | required | ~/.local/share/pixeleditor/documents | Project files. Versioned from the first release. |
ORIGINALS_DIR | required | ~/.local/share/pixeleditor/originals | Imported files, kept byte for byte and never written to. |
SCRATCH_DIR | required | ~/.cache/pixeleditor/tiles | Evicted tiles. Safe to delete when the app is not running. |
TILE_MEMORY_MB | required | 512 | The ceiling above which tiles are evicted to scratch. |
HISTORY_MAX_MB | required | 256 | Undo history cap in bytes, not in steps. |
AUTOSAVE_SECONDS | required | 30 | The most work a crash can cost. Written to a temporary file first. |
The build, in order
Pixels in buffers the browser cannot round
A document is tiles of unpremultiplied RGBA and the canvas only ever receives them.
This single decision is what keeps repeated edits from degrading the image.
Files
src/pixels/tiles.tssrc/pixels/tiles.test.tsTiles go to the screen. Nothing comes back.
Files
src/render/compositor.tsA grep test is crude and it will catch the regression a year from now.
Files
tests/no-canvas-readback.test.ts
done when · tick each as it passeswatch out- Holding each layer in its own canvas and calling getImageData before every operation loses accuracy on every pixel that is not fully opaque, because the output bitmap is premultiplied and the round trip divides and multiplies in finite precision, so soft brush edges and feathered masks drift grey after a few dozen edits. Keep the pixels in your own buffers and treat the canvas as write only.
Undo that does not cost a copy of the image
Every edit is a record with an inverse, and history has a memory ceiling.
The cost of an undo step should scale with what changed, not with the canvas size.
Files
src/history/operations.tssrc/history/patch.tsA count based cap means a hundred tiny steps and one enormous one look the same.
Files
src/history/limits.tsReorder, delete and resize are where inverse operations are usually wrong.
Files
src/history/history.test.ts
done when · tick each as it passeswatch out- Snapshotting the whole document before each stroke looks fine on a small test image and then eats a hundred and ninety two megabytes per undo step on a six by four thousand pixel photograph, so the app dies on the fifth brush stroke of real work. Store only the tiles an operation touched, and bound the history in bytes.
Selections, masks and a small set of tools that are correct
Fewer tools than the original, each one composited properly.
A path is a description; a mask is what the compositor can use.
Files
src/tools/selection.tsOne place to get premultiplication right, not one per tool.
Files
src/render/compositor.tssrc/tools/brush.tsDark fringes on cut-outs are invisible on white and obvious everywhere else.
Files
src/render/compositor.test.ts
done when · tick each as it passeswatch out- Compositing a feathered mask by multiplying colour channels that are already premultiplied darkens every soft edge towards black, and the error only shows on transparent backgrounds, which is where cut-outs are always used. Decide once that everything in the store is unpremultiplied, and do the multiply inside the compositor rather than in each tool.
Files that arrive and leave intact
Import keeps orientation and profile, and export says what it is about to lose.
Orientation and profile are lost the moment the browser hands you a drawn image.
Files
src-tauri/src/decode.rssrc/import/import.tsHash on import, hash again after export, assert they match.
Files
tests/original-untouched.test.tsBefore the write, not in the README.
Files
src/export/warnings.ts
done when · tick each as it passeswatch out- Loading a phone photograph through an image element and drawing it to a canvas gives you whatever the browser decided about rotation, and exporting from the canvas writes a file with no profile and no orientation tag, so the result looks right in your app and sideways or washed out everywhere else. Decode where you can see the metadata, normalise once, and record what you applied.
Large documents, autosave and a project file that opens next yearproduct builder
Real photographs do not exhaust memory, and an unexpected quit costs one autosave interval.
Real photographs are larger than the memory you want to use.
Files
src/pixels/eviction.tsA rewrite in place turns a crash into data loss.
Files
src/persist/autosave.tstests/crash-recovery.test.tsBefore there is a version two and no way back.
Files
src/persist/format.ts
done when · tick each as it passeswatch out- Autosaving by rewriting the whole project file over itself every thirty seconds turns a crash during the write into a lost manuscript of edits, which is the exact moment autosave was supposed to help. Append the operation log, write the project to a temporary file and replace it only once it reads back, and keep the previous one.
That is the whole plan for Pixlr. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Generative fill and background removal.
- Reading or writing commercial layered formats.
- A colour transform engine.
- Cloud documents and collaboration.
- cloud collaboration and mobile apps
- high-fidelity color, format, and export handling
- pixel-perfect professional tooling
- large template and asset libraries
- color-management edge cases
- A GPU compositor, once the reference tests exist to prove it matches
- Adjustment layers, which are operations that never write tiles at all
- A batch mode that applies a saved operation log to a folder of images
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.