fix(raw): resolve RAW green-yellow color cast via cam_xyz prioritization, daylight WB fallback, and fitMatch sanity checks

This commit is contained in:
2026-10-05 20:16:53 +07:00
parent c486397388
commit 8021529657
2 changed files with 16 additions and 12 deletions
@@ -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.
+12 -12
View File
@@ -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<Uint8Array> {
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<Uint8Array> {
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);