Two Rust photo apps in the browser
LightCraft is another wonderful Lightroom replacement, written in Rust, that runs in a browser tab. LightPhotos does the same but with some major differences.
When the photo is sharp and when the screen settles
Next photo
Both apps prepare the next photo ahead of time. LightCraft's photo is sharp at 0.12 s; its histogram continues changing until 1.70 s. The photo does not change during that interval.
- First image
- 0.07 s
- Sharp photo
- 0.07 s
- Whole screen settled
- 0.13 s
- First image
- 0.05 s
- Sharp photo
- 0.12 s
- Whole screen settled
- 1.70 s
Open from the grid
This photo has not been prepared. LightPhotos first shows the camera's JPEG; LightCraft first shows a preview. The sharp photo arrives at 1.37 s and 3.60 s respectively.
- First image
- 0.15 s
- Sharp photo
- 1.37 s
- Whole screen settled
- 1.37 s
- First image
- 0.06 s
- Sharp photo
- 3.60 s
- Whole screen settled
- 3.60 s
Replay runs in real time. Drag the slider to inspect a moment.
Same language, different answers
Both apps are Rust from top to bottom. Both draw their interface with egui on a canvas, and each web version is its desktop app compiled to WebAssembly. So the language does not decide much. The apps still differ on storage, threads, graphics and toolchain, and LightPhotos used LightCraft's layout until late September (see challenges with WebAssembly).
| LightPhotos | LightCraft | |
|---|---|---|
| Your photos | Read in place from a folder you pick | Copied into the browser's own storage on import |
| Parallel work | 6 decode threads that share one memory, with the same queues as the desktop app | Up to 4 Web Workers, each with its own memory. Jobs go in as JSON, pixels come back |
| Graphics | WebGPU, the newer API | WebGL2, which more browsers support |
| Browsers | Chrome, Edge | No WebGPU or folder access needed |
| Rust toolchain | Nightly, standard library rebuilt | Stable |
| Server headers | Required (cross-origin isolation, COOP/COEP) | Optional |
| What a RAW shows at fit | The RAW data, developed at the window's size, with your edits. The camera's JPEG while that develop runs | The RAW data, developed, with your edits |
| The photos on either side | Developed ahead of time | Prepared ahead of time |
| RAW decoder | rawler (LGPL-2.1), disclosed with its license | Its own |
| License | MIT or Apache-2.0, plus rawler under LGPL-2.1 | MIT or Apache-2.0 |
| After a reload | Your folders are listed again. Click one and allow access to continue | Library is still there |
| Open in several tabs | Allowed, with no guard against two tabs that overwrite each other's saves | One tab at a time |
Import is the biggest difference
The row in the table that you feel first is the one about your photos. The two apps answer "where are my photos?" in opposite ways.
Where do your photos and edits live?
Choose a scenario to compare the results. Reload and clear start from an opened library; Firefox or Safari shows a new import. These are illustrations, not actions on your files.
These files remain on disk in every scenario, for both apps.
Open 20 photos
.lightphotos/Ratings, edits and thumbnails on disk.
No second copy of the originals.
Folder read in place
0 MB of originals copied. The browser asks for access to the folder.
Copies, catalog and saved edits in browser storage.
Originals copied
498 MB copied into browser storage. The copy is included in the measured 2.3 s to fill the grid.
LightPhotos reads your folder. LightCraft creates a separate copy in browser storage.
Reload the page
.lightphotos/Ratings, edits and thumbnails on disk.
No second copy of the originals.
Folder access required
Reopen the session and grant access again, unless you allowed access on every visit. Ratings and edits remain on disk.
Copies, catalog and saved edits in browser storage.
Library retained
The imported library opens again without a folder-access prompt.
LightCraft retains its library. LightPhotos needs folder access again unless permission was saved for every visit.
Clear site data
.lightphotos/Ratings, edits and thumbnails on disk.
No second copy of the originals.
Edits remain on disk
Ratings, edits and thumbnails remain in .lightphotos beside the originals. Folder access is required to reopen them.
Copied originals, catalog and saved edits removed.
Library removed
The browser's copies, catalog and saved edits are removed. Edits you have not exported are lost.
Clearing site data removes the browser library. Original files and LightPhotos' saved data on disk remain.
Firefox or Safari
.lightphotos/Ratings, edits and thumbnails on disk.
No second copy of the originals.
Folder access unsupported
LightPhotos cannot open the folder in these browsers. The original files and saved edits remain on disk.
Copies, catalog and saved edits in browser storage.
File import supported
LightCraft can import files using the file dialog and browser storage. Each browser keeps its own library; an existing Chrome library does not transfer.
LightPhotos' folder access requires Chrome or Edge. LightCraft can import into a separate library in Firefox or Safari.
LightPhotos does not import. Open Folder calls the
browser's showDirectoryPicker, you grant access to one
folder, and the app reads the files where they are. Nothing is
copied. Ratings, edits and thumbnails go into a hidden
.lightphotos folder inside the folder you picked (see where your data lives),
so you can see them in Finder or Explorer and they move with the
folder.
The cost is that this API exists only in Chrome and Edge, and after a reload the browser asks you to grant access again, unless you chose to allow it on every visit.
LightCraft imports. You pick files with the standard file dialog, or drop them on the window. It reads the bytes of each file and writes them into the browser's own file system, the Origin Private File System (OPFS), under a hash of their content. The catalog, presets and settings go there too, or into IndexedDB where OPFS cannot be written. This works in every modern browser, with no permission prompt. A reload opens the same library, and a photo imported twice is stored once.
The cost is a second copy of every photo. The twenty Sony RAW files in this test are 498 MB, so importing this set into an empty library stores a second 498 MB in browser storage. That storage has a quota, it is invisible in Finder or Explorer, and the browser can evict it under storage pressure, so LightCraft asks for persistent storage and warns you when it does not get it. If you clear the site's data, the library goes with it. Your original files are safe, but to get edited photos out, you export them.
LightPhotos reads the folder you choose and saves ratings and edits alongside the originals. It avoids duplicating the photos, but requires Chrome or Edge. LightCraft keeps a separate library in browser storage, which supports file import across more browsers and retains the library after a reload. Clearing site data removes that library and edits you have not exported; the originals on disk remain.
How I measured
I built each app from its current code and served it from a local address. Playwright drove Chrome 154 on an M4 Pro Mac with 48 GB of memory. The window filled a 4K display at its native size, so the page was 3840 by 2072 pixels. The photos were twenty RAW files from a Sony A7 II, 24 megapixels and about 25 MB each. Each run used a new browser profile, so neither app had seen the photos before.
The script takes a screenshot about every 70 ms and keeps each frame that changes. For each key press, I record two times. The first is when the photo area stops changing. The second is when the whole screen stops changing, side panels included. Each number is the median of five runs. For an open from the grid, the script waits until the screen is still for 4 s, because the sharp photo can come late. The method is coarse: 0.07 s is the first frame after the key. So compare the apps with each other, not with a stopwatch.
An earlier version of this post timed only the whole screen, in a 1600 by 1000 window. It gave 0.97 s for the next photo in LightCraft. The frames show that the photo was sharp in the first frame. The time came from the histogram.
The numbers
| LightPhotos | LightCraft | |
|---|---|---|
| Download, compressed (brotli) | 3.6 MB | 3.8 MB |
| WebAssembly file, uncompressed | 14.4 MB | 17.1 MB |
| Fill the grid with 20 RAW thumbnails, total | 1.4 s | 2.3 s |
| Next photo: sharp photo on screen (median of 40) | 0.07 s | 0.12 s |
| Next photo: the whole screen stops changing | 0.13 s | 1.70 s |
| Open from the grid: sharp photo on screen | 1.37 s | 3.6 s |
| Memory afterwards, the page's process | 2.0 GB | 2.8 GB |
LightCraft includes a full editing pipeline and its own RAW decoder, but its download is only 0.2 MB larger. Its release builds also include a Japanese font, which I removed for this test. The memory number is for the page's own process. That process holds the main thread, the workers and the LightPhotos decode threads. When the LightCraft grid stops changing, it shows only a few of the RAW thumbnails, because it scrolls to follow the import. LightPhotos shows all 20.
Why the numbers differ
The camera's embedded JPEG is 1616 px wide. Full screen, the photo is about 2700 px wide in both apps, so the JPEG is too small for a sharp photo. Thus both apps develop the sensor data at fit. The colours at fit then come from the same place as the colours at 100%.
Both apps prepare the photos on either side while you look at the current one. LightPhotos develops them on its decode threads. LightCraft decodes them in its Web Workers. So for the next photo, the work is done before you press the key, and both apps show the sharp photo in about 0.1 s. LightCraft shows a preview for about 0.07 s first. Then its histogram changes until about 1.7 s after the key. The photo does not change in that time.
A photo that you open from the grid is not prepared. LightPhotos shows the camera's JPEG at 0.15 s. Then it develops the sensor data and shows the sharp photo at 1.37 s. LightCraft shows a preview at 0.06 s and the sharp photo at 3.6 s. In two of five runs, LightCraft was faster, at 1.25 s. I did not find the cause.
The grid is closer. The LightPhotos decode threads take thumbnails from one shared queue, and the photo on screen gets priority. LightCraft copies all 498 MB into browser storage while it imports. That copy is part of its 2.3 s. It also keeps the library after a reload.
Why LightCraft uses more memory
Each LightCraft worker is a separate copy of the program with its own memory, and WebAssembly memory never shrinks after it grows. The main thread also keeps recently used originals, the 498 MB of RAW files. Four workers plus that come to 2.8 GB. With a photo zoomed to 1:1, it reached 3.4 GB.
Shared memory has a cost too. Each LightPhotos decode thread needs about 300 MB of scratch space for a RAW file, all from one heap with a limit of 4 GB. With ten threads, memory peaked at 3.4 GB. So I limited the browser build to six threads, which keeps the page at 2.0 GB.
Wrapping up
LightPhotos relies on your own folders and shared memory, so it feels quick when you go through a shoot. LightCraft takes on the whole RAW pipeline and a full Lightroom-style feature set, keeps its library in the browser and needs no special server configuration. I look forward to seeing where it goes. Both also run on Mac, Windows and Linux, and that comparison is next.