Commit Graph

128 Commits

Author SHA1 Message Date
3dtours c26a4c21a7 fix(library): update real-time folder tree photo counts and persist scan state across page reloads 2026-10-08 20:28:29 +07:00
3dtours b163676e67 fix: realtime folder photo counts during scan discovery and decode 2026-10-08 20:08:19 +07:00
3dtours 779ae14a21 fix: realtime subfolder discovery, count updates, and deep level scan indicator 2026-10-08 19:09:27 +07:00
3dtours bfda4b4b9e feat: show scanning icon for parent and subfolder nodes along scan path 2026-10-08 19:02:06 +07:00
3dtours 582599049c fix(library): guarantee trailing slash in rollWalk relative paths so active scan spinner highlights all ancestor folders 2026-10-08 18:14:48 +07:00
3dtours 0533ee7080 fix(library): propagate active scanning spinner icon along full folder path hierarchy to root 2026-10-08 18:00:49 +07:00
3dtours 8f286506b2 feat(studio): hand an export straight to a social network
EXPORT only ever wrote a file, and posting it meant leaving the studio, finding
the file and uploading it again. The dialog now offers FACEBOOK, INSTAGRAM,
REDDIT and X beside the sizes, and the render goes to the network instead of the
download folder.

What the browser can do decides how, since a page may not touch another origin's
composer: a phone's share sheet opens the target app with the picture already
attached, a desktop gets the picture on the clipboard and the site in a new tab
for one paste, and an origin with no clipboard API downloads the file beside the
open site. The line under the row says so before the click.
2026-10-08 14:38:22 +07:00
3dtours af23f3a7c9 feat(library): read the walk without tiling, and scope the filters to the open folder
The scan made a JPEG tile for every frame as it walked, so a wall of 12MP
files spent its whole budget in the walk; the tile now waits until the card
is actually drawn (makeTile + putPhotoThumb), and a rawish frame whose date
is unreadable retries at 4MB instead of stopping at the head.

The year menu was built from the whole catalogue, so it offered values the
open folder could not produce; it now follows the rows on screen and clears
the choice when a move leaves it stale.
2026-10-08 11:28:14 +07:00
3dtours cda08c3908 fix(library): keep the shell the deploy replaced out of a failed navigation, and say when another tab holds the library open 2026-10-08 09:23:23 +07:00
3dtours 3242b3aa48 fix(library): mark the roll's own row while it is read, and count rows from the catalogue up 2026-10-08 08:05:18 +07:00
3dtours c80c8729a8 fix(library): mark only the folder a reading stands in, and keep a one-folder reading out of the roll's count
The column's ring was drawn from the open node's roll: a row of another roll
had no path under it, so the empty prefix matched every row of the roll being
read and the whole column lit up. The roll's own row was marked too, at every
moment of a reading of it, and a reading kept to one folder counted its frames
onto the roll's row as well — a roll counted down to one branch of itself.
A queued request was drawn from the reading's roll instead of its own, so a
request on a second roll showed nothing at all.
2026-10-07 23:10:21 +07:00
3dtours aa981e4e65 fix(library): fix photo count live update and optimize scan speed 2026-10-07 21:16:10 +07:00
3dtours 801877d763 fix(library): fix open location folder tree scroll and folder update path matching 2026-10-07 20:12:12 +07:00
3dtours 9bfb39be91 fix(library): resolve root folder rename, open in explorer, open at location centering, UPDATE rescan bug, and optimize scan RAM memory usage 2026-10-07 19:16:03 +07:00
3dtours b835415c20 fix(library): support thumbnail generation and display for HEIC and TIF files 2026-10-07 18:43:11 +07:00
3dtours 4704e936ba feat(studio): add right-click context menu (Solo mode, Collapse all, Expand all) for DevelopPanels, pin Color Chrome sub-chips, and fix library single view sorting & time range select 2026-10-07 17:35:20 +07:00
3dtours 1c7be6dfd4 perf(library): persistent SPA shell, in-memory folder tree caching, and zero-flicker LRU thumbnail caching 2026-10-07 12:05:58 +07:00
3dtours 3b4c49c41d feat(library & studio): add EXIF info bar, portrait frame panning, and multi-photo selection with Panorama/HDR context menu 2026-10-07 10:44:59 +07:00
3dtours 5338f66134 fix(raw): prioritize camera embedded preview for 100% exact color matching across SONY, OLYMPUS, FUJIFILM, NIKON, CANON, RICOH, LUMIX, LEICA, IPHONE, DNG 2026-10-07 07:29:42 +07:00
3dtours 93f56186b9 raw: decode the preview already oriented and drop the second turn
Skia's Image.MakeImageFromEncoded applies the embedded JPEG's EXIF
orientation itself. A Panasonic RW2 preview stored 1920x1280 with
orientation 8 therefore decodes as 1280x1920 already — the frame the
camera meant.

previewGrid turned it a second time by its own per-orientation canvas
rotation, so a portrait frame's reference grid met the develop a quarter
turn out, previewMatch rejected the fit as folded and the RW2 opened with
no tone curve at all (~9-13 levels off across the shadows).

Drop the rotation and read the crop rectangle and aspect straight from
bmp.width()/bmp.height(), the display dimensions Skia hands back.

Panasonic RW2 now fits a real curve (dRGB 2.3,-0.0,0.4, was -9.1,-10.3,
-13.2). The five bodies that were already stable — ORF, NEF, DNG, both
RAF — measure exactly as before.

getJpegOrientation had no other caller and goes with it; raw-orf-check
drops its own byte-scanning preview finder, which crashed on DNG and RAF,
and reads the reference through the shipped extractEmbeddedJpeg.
2026-10-06 22:10:47 +07:00
3dtours 3edafcfe1e library: open a phone's HEIC, and lock the shelves to accounts
A HEIC is the one camera file no engine on this platform will paint:
Chromium answers createImageBitmap with InvalidStateError and its
WebCodecs take no hvc1 still, and unlike a RAW there is no JPEG preview
inside to lift. It is decoded in software instead, by libheif built to
wasm, in a worker of its own — so a folder of them reads without the tab
stopping for a second, and what leaves the worker is the same JPEG
developRaw hands the studio. Nothing downstream knows the difference.

Into the studio it goes on the same terms as every other frame: dropped,
picked, or opened from the catalogue, no tier asked and the engine's own
colour untouched. The frame's date, ISO and position come off the HEIC
itself, which is passed along as the bytes the stamps read.

The catalogue's tile does not pay the decode: a phone writes a small
HEVC copy of its frame as an item of its own (thmb, 320x240 on an
iPhone), reachable only by walking the container by hand, and it decodes
in milliseconds where the frame takes a second — from the same 256KB the
catalogue already reads for a header.

The shelves themselves now want an account: a guest gets the note and
the way in, and the scan never starts.
2026-10-06 20:59:40 +07:00
3dtours 9fc03e28f7 assets: eight more mark faces from google/fonts
VT323, Alex Brush, Tilt Warp, Hahmlet, Pinyon Script, Allura, Dancing
Script and Amatic SC join the two the bundle already carried, each
subset to the same latin / latin-ext / vietnamese ranges and pinned to
its upright 400 instance where it is variable.
2026-10-06 20:59:34 +07:00
3dtours 2e06e5ab5d library: stand a frame up in the catalogue, wall, strip and studio
The library stage carried no way to correct a frame shot on its side, and
nothing kept the correction. A quarter turn now rides on the catalogue row
(`rot`, beside `star`, carried over by a rescan), the two chips under the
preview step it, and the tile, the strip thumbnail and the stage all read it
through one bake so a frame is never stood on its side in one place and
upright in another. The studio opens a frame at that angle and a turn made
there is filed straight back onto the row, so either end of the turn is
what the frame comes up at next time.
2026-10-06 17:22:56 +07:00
3dtours 8ed094cd9e fix(raw): stop the develop from handing the studio zero bytes
libraw-wasm moves the buffer it is given into its worker, so raw.open() detaches
the caller's array. developRaw read its embedded JPEG preview as a view of that
same array, so the open emptied the preview, Skia rejected the detached buffer
and the catch returned the emptied preview as the develop - every RAW opened from
the LIBRARY reached the studio as 0 bytes and the app said "Could not develop
this RAW file. Try again, or use its JPG."

Hand the worker a copy instead, in the develop and in the thumbnail path. The
EXIF stamps read off the caller's bytes afterwards are intact again.

The ORF check now hands the bytes over in the transfer list the way the package
does, so it fails on any develop that forgets this.
2026-10-06 16:16:32 +07:00
3dtours 16c098cd6a fix(raw): fix infinite loop in JPEG binary scanner and ensure sub-100ms preview extraction 2026-10-06 15:35:30 +07:00
3dtours 5db943ddd5 fix(library): fix RAW develop fallback & optimize STUDIO to LIBRARY transition speed 2026-10-06 13:24:20 +07:00
3dtours 2e1feb195a fix(raw): fix RAW preview matching, EXIF orientation alignment & studio initial state 2026-10-06 12:23:38 +07:00
3dtours ce691aa8cb fix(raw): keep the blown highlights neutral after the tone curve
The blown gate draws a clipped pixel at its own maximum -- one value across
the three channels, so the frame's highlights carry no cast -- but the
per-channel tone curve runs after it and re-tints what the gate had just
made neutral. One cubic a channel, fitted on a grid that has no block left
to fit where the frame ran out (previewMatch drops the fully blown ones),
so at the plateau the three curves agree only at 1.0 and part company
either side of it.

On the ORF this was reported on, the moonlit sky came back 254,255,255 and
253,255,254 -- red under green across a quarter of the frame.

The gate is the develop's own statement that the pixel had no colour of its
own, so it is re-read on the value that leaves the shader. The blown pixels
now measure 254.95,254.94,254.97 (R/G 1.0000) against 253.44,254.94,254.51
(0.9941) before; their two most common triples, 254,255,255 at 149315 and
253,255,254 at 122127, collapse to 255,255,255 at 278099. The bright bands
land at -0.0,-0.0,-0.0 against the embedded preview where they were
-2.1,-0.0,-1.0, and midtones and the lower half are untouched.
2026-10-06 10:45:15 +07:00
3dtours 9aa9bc0db0 fix(raw): keep the ORF off olive by dropping the 3x3 preview match
The least-squares 3x3 that mapped the develop onto the camera's JPEG won
on luminance, whose variance is ~80x the chroma's, so it paid for its
match by crushing the R-G axis: 11.6 -> 4.6 standard deviations, against
8.3 in the camera's own preview and 12.3 in its JPEG. That is the olive
cast -- the sky and the rice both shifted yellow-green.

The colour chain was never at fault: camera_mul x rgb_cam already agrees
with LibRaw to the digit, and the app's fit is bit-identical to a numpy
reference of the same problem, which is how the 3x3 was cleared.

Fit only the per-channel tone curve now. R-G comes back to 8.6 (900px)
and 10.5 (full), and B/G lands on the preview's 0.841 to three places.
2026-10-06 08:29:07 +07:00
3dtours 2e36acd18b fix(raw): open Olympus ORF at the colour of its own preview via rgb_cam
The develop preferred cam_xyz, whose rows are the XYZ of each camera channel and
which nothing normalised: on the ORF this was reported on it left the frame
green and blue (R/G 0.921, B/G 0.886) where the file's own preview sits at
1.013 / 0.841, and saturated reds came back as the dark purple the frame was
reported for — a red pixel's green ran negative through a row that carries
-2.64, so it clipped to 0 while the knee pulled red down with it.

LibRaw hands back dcraw's own rgb_cam, already the camera -> sRGB transform with
rows summing to one, and it is now applied as handed back: R/G 1.020 B/G 0.920,
and the fit against the embedded preview follows to mean 167.5,164.8,138.5
against the preview's 167.0,164.8,138.6 and the camera's own JPEG's 165,162,133.
Dividing rgb_cam by pre_mul — which carries that row normalisation — is what the
frame before this one did instead, and it undoes it.

The cam_xyz chain stays as the fallback for a file with no rgb_cam, with its
rows normalised so a neutral frame opens neutral. invert3x3, unused since the
frame stopped going through the chain, is dropped.
2026-10-05 22:51:10 +07:00
3dtours cef7868db8 fix(raw): eliminate double white balance application by un-scaling pre_mul on rgb_cam 2026-10-05 21:59:51 +07:00
3dtours d7ec00c08e fix(raw): correct matrix direction and previewMatch block clipping condition for accurate RAW colors 2026-10-05 20:58:13 +07:00
3dtours 1be75efd5a fix(raw): apply invert3x3 matrix conversion to eliminate red-to-purple shadow artifacts 2026-10-05 20:37:19 +07:00
3dtours 8021529657 fix(raw): resolve RAW green-yellow color cast via cam_xyz prioritization, daylight WB fallback, and fitMatch sanity checks 2026-10-05 20:16:53 +07:00
3dtours c486397388 fix(library): fix Olympus ORF raw color conversion and add custom photo context menu modal 2026-10-05 18:22:22 +07:00
3dtours fbed70b9da fix(raw): pin tone curve ends to eliminate shadow purple artifacts and fix camera matrix WB color cast 2026-10-05 16:29:10 +07:00
3dtours f98ce2c042 fix(raw): fix RAW color matrix calculation, enable highlight recovery mode, and add dedicated tiffThumbnail decoder 2026-10-05 16:03:41 +07:00
3dtours 480fd148a3 fix(raw): enable camera white balance multipliers and fallback extraction to fix RAW color cast on Olympus and other bodies 2026-10-05 15:43:20 +07:00
3dtours 140b4f88b2 fix(library): resolve main-thread UI freeze and process kill data loss via OPFS payload capping, yield throttling, and UI batching 2026-10-05 12:13:45 +07:00
3dtours 1e93c8484b fix(library): optimize TIF/TIFF file handling via embedded JPEG extraction to prevent RAM spikes and render thumbnails 2026-10-05 10:26:59 +07:00
3dtours e63183b298 feat(library): smart subfolder auto-link and instant handle reconnection 2026-10-05 10:03:09 +07:00
3dtours c4d904a207 fix(library): instant Lightroom-style catalog restore with lazy tile fallback 2026-10-05 08:05:09 +07:00
3dtours b30c7403e3 fix(library): add one-time DB cursor migration to clean legacy Blobs and implement hierarchical Add Folder tree matching 2026-10-05 07:01:11 +07:00
3dtours 77cf872b16 fix(library): optimize image scanning, fast raw preview extraction and lazy thumbnail loading 2026-10-05 06:34:37 +07:00
3dtours 79f0d77a8e feat(library): update, rescan and stop on the folder menu, queued per roll
A folder row now asks for the rest of a roll, the whole of it from the top,
or takes its request back. Requests queue behind the reading in flight and
run one at a time; a row waiting its turn draws a ring that does not turn.
2026-10-02 22:25:43 +07:00
3dtours e43c02c36f fix(library): spell the names the browser refuses to write
A backup folder is picked, not created: the frames in it carry the names a
camera or a person gave them, and Chromium's IsSafePathComponent will not
spell a name back onto a disk if it holds a control or format character, one
of ": * ? " < > | \ /", a space, dot or tilde at either end, or a .lnk/.scf/
.url tail. The library is read through a handle, which lets all of those
through, so the refusal only surfaced on the way out: getDirectoryHandle threw
"Name is not allowed" on the first such folder, the run stopped there and left
an empty thumbs/ behind it.

Spell a component that would be refused as %XX per its UTF-8 bytes, on the way
out and on the way back in, so those frames keep their tiles in the backup and
the restore still finds them. A name that was already safe is handed back
untouched, which keeps an existing folder readable, copyable and rsyncable by
hand.

The run also no longer dies on the one frame that will not write: it skips it,
names it in the console, and finishes the rest.
2026-10-02 21:51:25 +07:00
3dtours 07a38be5a9 fix(super-res): warm the export runtime's bytes, not the runtime, and release its session on the way out
Opening the export menu imported onnxruntime-web, and importing that runtime is
what starts its thread pool: one Web Worker per thread, each holding a copy of
the 27MB wasm build, held by the page until it dies whether or not an export is
ever asked for. Those workers are the child processes a visitor finds hanging off
the tab (and the tab, and the browser, goes when one of them is killed), and the
memory is what a laptop has none of when the studio is closed and opened again.

The menu now fetches the files by name and lets the browser's cache do the
warming; the runtime is imported by the export that needs it. The threads are
capped at four, and pagehide hands the session back instead of leaving the
renderer to reap it with the page.
2026-10-02 21:04:56 +07:00
3dtours c802c43e04 fix(assets): give the frame sheets a build-stamped URL, so a replaced sheet stops arriving from cache 2026-10-02 20:10:02 +07:00
3dtours 8d0a46e7f5 fix(frame): give OLD FILM PORTRAIT a real portrait output
The sheet was stretched over the visitor's frame, so the portrait variant
only turned the paper inside whatever shape the photo had: a landscape
photo stayed landscape. The film now has its own opening like the walls
do — the frame is scaled up to the photo, the photo is cover-cropped into
the whole sheet, and `old-film-portrait` is the PNG turned 90° CW, so the
pair is one frame standing and one lying. The torn edge has no straight
sides, so the photo runs under all of it instead of being cut against a
measured window.
2026-10-02 19:54:42 +07:00
3dtours 84b8e64652 feat(frame): add OLD FILM LANDSCAPE and OLD FILM PORTRAIT from old_film.png 2026-10-02 19:46:06 +07:00