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.
This commit is contained in:
@@ -425,51 +425,46 @@ function previewGrid(jpeg: Uint8Array, w: number, h: number, n = MATCH_GRID): Ui
|
||||
}
|
||||
}
|
||||
|
||||
function invert3x3(m: number[][]): number[][] | null {
|
||||
const [a, b, c] = m[0];
|
||||
const [d, e, f] = m[1];
|
||||
const [g, h, i] = m[2];
|
||||
const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
||||
if (Math.abs(det) < 1e-6) return null;
|
||||
const invDet = 1.0 / det;
|
||||
return [
|
||||
[(e * i - f * h) * invDet, (c * h - b * i) * invDet, (b * f - c * e) * invDet],
|
||||
[(f * g - d * i) * invDet, (a * i - c * g) * invDet, (c * d - a * f) * invDet],
|
||||
[(d * h - e * g) * invDet, (b * g - a * h) * invDet, (a * e - b * d) * invDet],
|
||||
];
|
||||
}
|
||||
// The camera -> sRGB matrix, applied to the WB'd sensor triple.
|
||||
//
|
||||
// `rgb_cam` is dcraw's own: it is the camera -> sRGB transform already, its rows
|
||||
// sum to one, and the 1/num dcraw scales it by rides in `pre_mul`. Dividing it by
|
||||
// pre_mul a second time undoes the normalisation and drags the frame towards
|
||||
// green. It is what every body LibRaw knows a `cam_xyz` for gets — the Olympus
|
||||
// ORF this was reported on included, whose `rgb_cam` and `cam_xyz` disagree
|
||||
// (measured: rgb_cam x cam_mul lands on R/G 1.021 B/G 0.920 against the file's own
|
||||
// preview's 1.013 / 0.841, the cam_xyz chain this used to prefer on 0.921 / 0.886
|
||||
// — green, and blue for a scene the camera left neutral).
|
||||
//
|
||||
// `cam_xyz` stays as the fallback for a file that has one and no `rgb_cam`: its
|
||||
// rows are the XYZ of each camera channel, so sRGB = xyz_rgb^-1 . cam_xyz^T . cam,
|
||||
// with the rows normalised so a neutral camera triple opens neutral, as dcraw's
|
||||
// cam_xyz_coeff normalises them.
|
||||
const XYZ_TO_SRGB = [
|
||||
[3.2404542, -1.5371385, -0.4985314],
|
||||
[-0.9692660, 1.8760108, 0.0415560],
|
||||
[0.0556434, -0.2040259, 1.0572252],
|
||||
];
|
||||
|
||||
function getCamToSrgbMatrix(cd: any): number[][] {
|
||||
if (cd?.rgb_cam && Array.isArray(cd.rgb_cam) && cd.rgb_cam.length >= 3) {
|
||||
return [0, 1, 2].map((i) => [0, 1, 2].map((j) => cd.rgb_cam[i]?.[j] || 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;
|
||||
const rX = (cx[0][0] || 0) / scale, rY = (cx[0][1] || 0) / scale, rZ = (cx[0][2] || 0) / scale;
|
||||
const gX = (cx[1][0] || 0) / scale, gY = (cx[1][1] || 0) / scale, gZ = (cx[1][2] || 0) / scale;
|
||||
const bX = (cx[2][0] || 0) / scale, bY = (cx[2][1] || 0) / scale, bZ = (cx[2][2] || 0) / scale;
|
||||
|
||||
const m00 = 3.2404542 * rX - 1.5371385 * rY - 0.4985314 * rZ;
|
||||
const m01 = 3.2404542 * gX - 1.5371385 * gY - 0.4985314 * gZ;
|
||||
const m02 = 3.2404542 * bX - 1.5371385 * bY - 0.4985314 * bZ;
|
||||
|
||||
const m10 = -0.9692660 * rX + 1.8760108 * rY + 0.0415560 * rZ;
|
||||
const m11 = -0.9692660 * gX + 1.8760108 * gY + 0.0415560 * gZ;
|
||||
const m12 = -0.9692660 * bX + 1.8760108 * bY + 0.0415560 * bZ;
|
||||
|
||||
const m20 = 0.0556434 * rX - 0.2040259 * rY + 1.0572252 * rZ;
|
||||
const m21 = 0.0556434 * gX - 0.2040259 * gY + 1.0572252 * gZ;
|
||||
const m22 = 0.0556434 * bX - 0.2040259 * bY + 1.0572252 * bZ;
|
||||
|
||||
return [[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]];
|
||||
}
|
||||
|
||||
if (cd?.rgb_cam && Array.isArray(cd.rgb_cam) && cd.rgb_cam.length >= 3) {
|
||||
const pm = (cd?.pre_mul && cd.pre_mul.length >= 3) ? cd.pre_mul : [1, 1, 1, 1];
|
||||
const pmR = pm[0] || 1, pmG = pm[1] || 1, pmB = pm[2] || 1;
|
||||
return [
|
||||
[(cd.rgb_cam[0][0] || 0) / pmR, (cd.rgb_cam[0][1] || 0) / pmG, (cd.rgb_cam[0][2] || 0) / pmB],
|
||||
[(cd.rgb_cam[1][0] || 0) / pmR, (cd.rgb_cam[1][1] || 0) / pmG, (cd.rgb_cam[1][2] || 0) / pmB],
|
||||
[(cd.rgb_cam[2][0] || 0) / pmR, (cd.rgb_cam[2][1] || 0) / pmG, (cd.rgb_cam[2][2] || 0) / pmB],
|
||||
];
|
||||
const m = [0, 1, 2].map((i) =>
|
||||
[0, 1, 2].map((j) =>
|
||||
XYZ_TO_SRGB[i][0] * ((cx[j]?.[0] || 0) / scale) +
|
||||
XYZ_TO_SRGB[i][1] * ((cx[j]?.[1] || 0) / scale) +
|
||||
XYZ_TO_SRGB[i][2] * ((cx[j]?.[2] || 0) / scale)
|
||||
)
|
||||
);
|
||||
return m.map((row) => {
|
||||
const num = row[0] + row[1] + row[2];
|
||||
return Math.abs(num) < 1e-6 ? row : row.map((v) => v / num);
|
||||
});
|
||||
}
|
||||
|
||||
return [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
||||
|
||||
Reference in New Issue
Block a user