Build BeFunky
KINDAback to the verdict
A non-destructive local photo editor whose hard problems are keeping the original file untouched, keeping colour honest through a canvas that is sRGB by default, and making a screen-sized preview agree with a full-resolution export.
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 front end and runs the tests.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Tauri 2 needs it, and the file handling that keeps the original untouched lives on that side.
Get it rustup.rs, then the platform build dependencies Tauri lists for your operating system. open ↗
Verify
cargo --version prints a version and a Tauri dev build starts - have readyfree
Why One wide-gamut, one with location metadata, one very large. Each exists to make a specific test fail before it passes.
Get it Take them, or export them from your own library. Do not use stock images with unclear terms.
Verify
One has an embedded non-sRGB profile, one has GPS tags, one exceeds your pixel ceiling - have readyfree
Why Text rendering references a file, not a family name, so you need a face whose licence permits shipping it beside the project.
Get it An open licensed family from a font repository, with the licence file kept next to it.
Verify
The licence permits redistribution and the file loads in the renderer - decidefree
Why Whether you convert wide-gamut imports to sRGB or refuse them is a decision that changes what users see, and deciding it after the first complaint means choosing whatever is easiest.
Get it Write one paragraph: the working space, what happens to a non-matching embedded profile, and what the user is told.
Verify
The paragraph is in the README and a test asserts the behaviour it describes
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- projects(id, name, source_path, source_sha256, source_width, source_height, created_at, opened_at) - source_profile(project_id, declared_space, embedded_profile_name, converted_to, conversion_note) · what the file claimed and what you did about it - ops(project_id, ordinal, kind, params_json, enabled, created_at) · the edit stack, which IS the document - layers(project_id, id, kind, z, transform_json, asset_id, text_json) · for collages and text - assets(id, path, sha256, mime, width, height, bytes) · imported images, copied, never moved - exports(id, project_id, path, format, quality, long_edge, ops_ordinal_max, metadata_policy, written_at) - Nothing in this model stores pixels that an operation could recompute, which is why undo is free and a project file is a few kilobytes.
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 |
|---|---|---|---|
DATA_DIR | required | ~/.local/share/photoedit | Projects, the index and copied assets. Never the user's originals. |
EXPORT_DIR | required | ~/Pictures/exports | Where new files are written. Exports never land beside the source. |
WORKING_COLOUR_SPACE | required | srgb | Recorded in every project so an old project still renders the way it did. |
MAX_IMAGE_PIXELS | required | 60000000 | Above this the import is refused with the real dimensions in the message. |
PREVIEW_MAX_EDGE | required | 2048 | The preview downscale. The render scale derived from it must reach every operation. |
STRIP_METADATA_ON_EXPORT | required | true | Default true. Turning it off lists exactly what will be kept. |
The build, in order
Open a photograph without changing it
An imported file is read, hashed and left alone, and the colour decision is made explicitly rather than by accident.
The one test that would have saved every photograph an editor has ever destroyed.
Files
src-tauri/src/import.rstests/source-untouched.test.tsNot to implement colour management, but so the app can say what it did.
Files
src/colour.tssrc/types/project.tsA forty megapixel file on an eight gigabyte machine is a decision, not a crash.
Files
src/import.ts
done when · tick each as it passeswatch out- Loading a photograph shot in a wide-gamut space into a default canvas converts it to sRGB on the way in, and the user sees a flatter, duller version of their own picture with no message anywhere explaining why. Read the embedded profile, declare the working space in the project, show the user which one they are editing in, and record the conversion so the export can say what happened.
An edit stack, not a pixel buffer
Every adjustment is an entry in an ordered list, so undo, reorder and disable are free.
Input surface plus parameters out, no shared mutable bitmap.
Files
src/ops/index.tssrc/ops/exposure.tsRe-rendering ten operations on every slider drag is the reason people say this is slow.
Files
src/render/cache.tsUndo that is nearly right is undo that is wrong.
Files
tests/undo-roundtrip.test.ts
done when · tick each as it passeswatch out- Applying each adjustment destructively to a working bitmap is faster to write and traps you within a day, because undo then means keeping a full copy of the image for every step and a twelve step history on a large photograph exhausts memory. Keep the operation list and re-render from the source, cached at the last unchanged step.
A preview that predicts the export
What you see at screen size is what the full-resolution file contains.
The parameter that is forgotten in exactly one operation, every time.
Files
src/render/scale.tssrc/ops/blur.tsThe only check that catches the forgotten one.
Files
tests/scale-agreement.test.tsA full-resolution replay takes seconds and silence reads as a hang.
Files
src/export/progress.ts
done when · tick each as it passeswatch out- A blur radius of eight pixels on a preview downscaled to a quarter is a blur radius of thirty two pixels in the export, and so is every feather, stroke width, corner radius and vignette falloff in the stack, which is why the exported file looks nothing like the screen. Carry a render scale through every operation and multiply pixel-denominated parameters by it.
Collage and text, where fonts break things
A layout of several images plus text renders the same on export as on screen.
A substituted face is a different layout.
Files
src/text/fonts.tsLine breaking is the thing that moves between preview and export.
Files
src/text/measure.tstests/text-scale.test.tsSame inputs, same layout, or the export is a surprise.
Files
src/layout/grid.ts
done when · tick each as it passeswatch out- Setting a font by family name means the export silently substitutes whatever the system has when the named face is missing, and the line breaks move, so a caption that fitted on screen overruns the frame in the file. Reference a font file the project owns and refuse to render with a substitute, saying which face is missing.
Export, metadata and the way outproduct builder
Files leave in open formats, without carrying data the user did not mean to publish.
Read the exported file back in the test and assert the tags are gone.
Files
src/export/metadata.tstests/no-exif-leak.test.tsAn edit stack nobody else can read is a trap with extra steps.
Files
docs/project-format.mdImport, edit, export, close, compare the hash.
Files
tests/e2e-source-untouched.test.ts
done when · tick each as it passeswatch out- Copying the source metadata into the export because it seems polite republishes the GPS coordinates where the photograph was taken, which for a picture taken at home is the user's address, attached to a file they are about to put on the internet. Strip by default, keep only on an explicit choice, and list exactly what is retained.
That is the whole plan for BeFunky. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- A stock template and asset library.
- Generative editing features.
- Print colour management and CMYK.
- Cloud projects, sharing and mobile.
- pixel-perfect professional tooling
- large template and asset libraries
- color-management edge cases
- cloud collaboration and mobile apps
- high-fidelity color, format, and export handling
- Batch apply of one saved operation stack across a folder, which reuses everything already built
- A histogram and clipping warning computed from the cached render rather than from the source
- Raw file decoding, which is a genuine project and deserves its own colour decisions
Need the files? The project pack on the verdict page hands your agent the whole brief · more design.