diff --git a/docker/frontend/src/engine/previewMatch.ts b/docker/frontend/src/engine/previewMatch.ts index e714940..81d87d8 100644 --- a/docker/frontend/src/engine/previewMatch.ts +++ b/docker/frontend/src/engine/previewMatch.ts @@ -169,6 +169,10 @@ export function fitMatch(dev: Uint8Array, ref: Uint8Array, n = MATCH_GRID): Matc // and -1.67), and refusing those refused the whole fit — those bodies opened // byte-identical to an unfitted develop. for (const v of m) if (!Number.isFinite(v) || Math.abs(v) > 4) return null; + // Sanity check: diagonal elements should be positive and reasonable + if (m[0] < 0.1 || m[4] < 0.1 || m[8] < 0.1 || m[0] > 3.0 || m[4] > 3.0 || m[8] > 3.0) return null; + // Reject fitted matrix if green channel is unnaturally boosted over red and blue (prevents yellow-green cast) + if (m[4] > m[0] * 2.5 || m[4] > m[8] * 2.5) return null; // The 3x3 alone cannot bend a channel — it can only scale it — and what is left // between the two frames is mostly exactly that bend. So the residual after the // 3x3 is fitted a cubic a channel, on the value the 3x3 leaves. diff --git a/docker/frontend/src/engine/rawDevelop.ts b/docker/frontend/src/engine/rawDevelop.ts index dc6c264..3acefc5 100644 --- a/docker/frontend/src/engine/rawDevelop.ts +++ b/docker/frontend/src/engine/rawDevelop.ts @@ -145,7 +145,7 @@ half4 main(float2 pos) { // The matrix was fitted luma-preserving, so mx is the value to hold. float hi = max(max(n.r, n.g), n.b); hi = max(hi, max(max(lin.r, lin.g), lin.b)); - rgb = mix(rgb, float3(mx), smoothstep(0.92, 1.0, hi)); + rgb = mix(rgb, float3(mx), smoothstep(0.99, 1.02, hi)); // The overflow used to fade towards white — mix(rgb / mx, 1, 1 - 1 / mx) — // which put every pixel of a blown sky on exactly 1.0 and threw the two stops // the sensor held above white away with it: LIGHT's HIGHLIGHT row then had a @@ -440,14 +440,6 @@ function invert3x3(m: number[][]): number[][] | null { } function getCamToSrgbMatrix(cd: any): number[][] { - if (cd?.rgb_cam && Array.isArray(cd.rgb_cam) && cd.rgb_cam.length >= 3) { - return [ - [cd.rgb_cam[0][0] || 0, cd.rgb_cam[0][1] || 0, cd.rgb_cam[0][2] || 0], - [cd.rgb_cam[1][0] || 0, cd.rgb_cam[1][1] || 0, cd.rgb_cam[1][2] || 0], - [cd.rgb_cam[2][0] || 0, cd.rgb_cam[2][1] || 0, cd.rgb_cam[2][2] || 0], - ]; - } - if (cd?.cam_xyz && Array.isArray(cd.cam_xyz) && cd.cam_xyz.length >= 3) { const cx = cd.cam_xyz; const scale = (Math.abs(cx[0][0]) > 100) ? 10000.0 : 1.0; @@ -470,6 +462,14 @@ function getCamToSrgbMatrix(cd: any): number[][] { return [[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]]; } + if (cd?.rgb_cam && Array.isArray(cd.rgb_cam) && cd.rgb_cam.length >= 3) { + return [ + [cd.rgb_cam[0][0] || 0, cd.rgb_cam[0][1] || 0, cd.rgb_cam[0][2] || 0], + [cd.rgb_cam[1][0] || 0, cd.rgb_cam[1][1] || 0, cd.rgb_cam[1][2] || 0], + [cd.rgb_cam[2][0] || 0, cd.rgb_cam[2][1] || 0, cd.rgb_cam[2][2] || 0], + ]; + } + return [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; } @@ -483,7 +483,7 @@ export async function developRaw(bytes: Uint8Array): Promise { const meta = await raw.metadata(true); const img = await raw.imageData(); const cd = meta?.color_data; - if (!img || !cd?.cam_mul || (!cd.rgb_cam && !cd.cam_xyz)) throw new Error('RAW has no colour data'); + if (!img || (!cd?.cam_mul && !cd?.pre_mul) || (!cd.rgb_cam && !cd.cam_xyz)) throw new Error('RAW has no colour data'); const { width: w, height: h } = img; const data = img.data as Uint16Array; @@ -494,9 +494,9 @@ export async function developRaw(bytes: Uint8Array): Promise { const rawMul = (cd?.cam_mul && cd.cam_mul.some((v: number) => v > 0 && v !== 1)) ? cd.cam_mul - : (cd?.pre_mul && cd.pre_mul.some((v: number) => v > 0)) + : (cd?.pre_mul && cd.pre_mul.some((v: number) => v > 0 && v !== 1)) ? cd.pre_mul - : [1, 1, 1, 1]; + : [2.0, 1.0, 1.5, 1.0]; const green = rawMul[1] || rawMul[3] || 1; const mul = rawMul.map((v: number) => v / green); const [r0, r1, r2] = getCamToSrgbMatrix(cd);