web: import the camera's RAW, and grade it like the phone
The studio took JPEG, PNG and HEIC and nothing else, so a photographer's own negatives never reached it. A RAW now loads the way any other file does — `isRawName` reads the extension off a 24-entry list, the file goes into OPFS under one slot (`current_image.raw`, beside `current_image.name`, so a reload finds it again) and `rawDevelop` runs it through LibRaw-wasm: half size, 16-bit output, camera white balance and the camera's own 3x3 matrix, in bands of 2M pixels so a 30MB file never holds a second copy of itself. `example-sony.ARW` (30.3MB) lands as a 3120x2084 picture, no page error. DEHAZE joins the FX tab, where Lightroom keeps it: a chip off the same PARAM_DEFS entry (`dehaze`, 0..10) so nothing new renders chips, and the pass is the dark channel prior — `atmosphericLight` reads A off a 32x32 draw of the photo, `DEHAZE_SKSL` takes omega up to 0.95 over a floor of 0.1 — measured at 71.8% of the stage's pixels moved between 0 and 10. The gradient mask grows the six knobs the phone's has: HIGHLIGHT, SHADOW, WHITE, BLACK, CLARITY and DEHAZE. The mask's falloff is a smoothstep rather than a line, and CLARITY/DEHAZE inside a mask get a blurred copy of the photo plus the air A as a second child of the mask shader — so a mask's clarity is clarity and not a flat brightness lift. The column shows all nine rulers; CLARITY 9 moves 42.2% of the stage, DEHAZE 9 moves 27.9%. CLARITY stops reading the whole photo per pixel: the single pass that sampled a 15x15 box 225 times is now the three passes the same math wants — 1x15, then 15x1, then a blend, `orig + (orig - B) * 3.2` — about 30 reads. Both signs work (77.4% of the stage moves at +10, 79.6% at -10), and the negative branch keeps its mist as it was. The pointer reviews a look before it is taken: resting on a PHOTO STYLE chip or a recipe chip lays that look on the photo while it stays there and gives it back the moment it leaves — byte-identical, measured on four of them (24.9%, 23.8%, 24.5%, 25.3% of the stage moves on, 0.00% off) — while the recipe, the UNDO stack and the session stay on the look the click left. A hovered look brings its colour alone: the masks, the dust spots and the mosaic of the photo being edited ride along, or a pointer crossing a chip row would rub them off. A PRO sim is left out, since a hover that showed its look would hand over what the click gates. Probes: e2e-raw-verify, e2e-dehaze-mask, e2e-mask-verify, e2e-clarity-verify, e2e-hover-preview2.
This commit is contained in:
@@ -20,6 +20,11 @@ export interface ChipDef {
|
||||
// Present when the chip itself can be dragged somewhere: the payload the drop
|
||||
// target reads back (the recipe's id, for the FAVORITED rail button).
|
||||
drag?: string;
|
||||
// HOVER PREVIEW: a look the pointer lays on the photo before any click. Only
|
||||
// the chips that carry a whole look (a sim, a recipe) have one; the pair is
|
||||
// always both or neither, so leaving the chip always takes it back off.
|
||||
onHover?: () => void;
|
||||
onHoverEnd?: () => void;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
@@ -64,7 +69,16 @@ export function ChipRow({ chips }: { chips: ChipDef[] }) {
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onClick={chip.onClick}
|
||||
onMouseEnter={chip.onHover}
|
||||
onMouseLeave={chip.onHoverEnd}
|
||||
// The click takes the preview off first: a click that applies the look
|
||||
// (and closes the column the chip was in) would otherwise leave a
|
||||
// preview nothing is left to take off — and every later knob would
|
||||
// move the recipe under a photo still painted from the preview.
|
||||
onClick={() => {
|
||||
chip.onHoverEnd?.();
|
||||
chip.onClick();
|
||||
}}
|
||||
>
|
||||
{chip.color ? <span className="chip-dot" data-color={chip.color} style={{ background: chip.color }} /> : null}
|
||||
{chip.label}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } fro
|
||||
import { useI18n } from '../i18n/I18nProvider';
|
||||
import { MIN_CROP_FRAC, type CropRect, type GradientMask, type HealSpot, type MosaicSpot } from '../../shared/types';
|
||||
import { bitmapReader, samplePixel } from '../engine/imageOps';
|
||||
import { RAW_ACCEPT } from '../engine/rawDevelop';
|
||||
import { findHealSource } from '../../shared/utils/heal';
|
||||
import { MASK_DEFAULT_FEATHER, MASK_MIN } from '../../shared/utils/gradientMask';
|
||||
import { BRUSH_SPACING, wheelBrushR } from '../../shared/utils/brush';
|
||||
@@ -2048,7 +2049,7 @@ export function ImageStage({
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
accept={`image/*,${RAW_ACCEPT}`}
|
||||
hidden
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
|
||||
Reference in New Issue
Block a user