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:
@@ -119,4 +119,48 @@ assert.match(src, /^ *uniforms\[21\] = f\[0\]; uniforms\[22\] = f\[1\]; uniforms
|
|||||||
// 6020x4024, and every one of the eight bodies checked doubled its frame.
|
// 6020x4024, and every one of the eight bodies checked doubled its frame.
|
||||||
assert.match(src, /^ *halfSize: false,$/m, 'the develop is half-size again');
|
assert.match(src, /^ *halfSize: false,$/m, 'the develop is half-size again');
|
||||||
|
|
||||||
|
// The matrix the develop applies is the camera->sRGB one LibRaw already hands
|
||||||
|
// back, not one derived from `cam_xyz` beside it. They disagree on the Olympus
|
||||||
|
// ORF this was reported on: 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 on
|
||||||
|
// 0.921 / 0.886 — green, and blue for a scene the camera left neutral, which is
|
||||||
|
// the cast the frame opened with. And the matrix must not be divided by `pre_mul`
|
||||||
|
// again: that undoes the row-sum-to-one normalisation dcraw built into rgb_cam.
|
||||||
|
{
|
||||||
|
const body = src.match(/const XYZ_TO_SRGB = \[[\s\S]*?\n\}\n/)?.[0];
|
||||||
|
assert.ok(body, 'getCamToSrgbMatrix not found');
|
||||||
|
// The file is TypeScript; the two annotations in this block are all that stop
|
||||||
|
// `new Function` from reading it.
|
||||||
|
const js = body.replace(/: any/g, '').replace(/: number\[\]\[\]/g, '');
|
||||||
|
const getCamToSrgbMatrix = new Function(`${js}; return getCamToSrgbMatrix;`)();
|
||||||
|
const rgb_cam = [
|
||||||
|
[1.631760597229004, -0.39896339178085327, -0.23279713094234467],
|
||||||
|
[-0.298429936170578, 1.7056032419204712, -0.4071732759475708],
|
||||||
|
[0.02502557262778282, -0.45518237352371216, 1.4301568269729614],
|
||||||
|
];
|
||||||
|
const cam_xyz = [
|
||||||
|
[0.9422, -0.3258, -0.0711],
|
||||||
|
[-0.2655, 1.0898, 0.2015],
|
||||||
|
[-0.0512, 0.1354, 0.5512],
|
||||||
|
];
|
||||||
|
const pre_mul = [2.0312116146087646, 0.9462084174156189, 1.4558180570602417];
|
||||||
|
assert.deepEqual(
|
||||||
|
getCamToSrgbMatrix({ rgb_cam, cam_xyz, pre_mul }),
|
||||||
|
rgb_cam.map((row) => row.slice(0, 3)),
|
||||||
|
'rgb_cam is no longer applied as LibRaw handed it back'
|
||||||
|
);
|
||||||
|
// A neutral camera triple stays neutral through what is left: every row of the
|
||||||
|
// matrix has to sum to one, or the frame opens with a cast of its own.
|
||||||
|
for (const [name, cd] of [
|
||||||
|
['rgb_cam', { rgb_cam }],
|
||||||
|
['cam_xyz', { cam_xyz }],
|
||||||
|
['nothing', {}],
|
||||||
|
]) {
|
||||||
|
const rows = getCamToSrgbMatrix(cd);
|
||||||
|
for (const row of rows) {
|
||||||
|
assert.ok(Math.abs(row[0] + row[1] + row[2] - 1) < 1e-6, `${name}: row sums to what white is not`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log('raw-develop-check ok');
|
console.log('raw-develop-check ok');
|
||||||
|
|||||||
@@ -425,51 +425,46 @@ function previewGrid(jpeg: Uint8Array, w: number, h: number, n = MATCH_GRID): Ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function invert3x3(m: number[][]): number[][] | null {
|
// The camera -> sRGB matrix, applied to the WB'd sensor triple.
|
||||||
const [a, b, c] = m[0];
|
//
|
||||||
const [d, e, f] = m[1];
|
// `rgb_cam` is dcraw's own: it is the camera -> sRGB transform already, its rows
|
||||||
const [g, h, i] = m[2];
|
// sum to one, and the 1/num dcraw scales it by rides in `pre_mul`. Dividing it by
|
||||||
const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
// pre_mul a second time undoes the normalisation and drags the frame towards
|
||||||
if (Math.abs(det) < 1e-6) return null;
|
// green. It is what every body LibRaw knows a `cam_xyz` for gets — the Olympus
|
||||||
const invDet = 1.0 / det;
|
// ORF this was reported on included, whose `rgb_cam` and `cam_xyz` disagree
|
||||||
return [
|
// (measured: rgb_cam x cam_mul lands on R/G 1.021 B/G 0.920 against the file's own
|
||||||
[(e * i - f * h) * invDet, (c * h - b * i) * invDet, (b * f - c * e) * invDet],
|
// preview's 1.013 / 0.841, the cam_xyz chain this used to prefer on 0.921 / 0.886
|
||||||
[(f * g - d * i) * invDet, (a * i - c * g) * invDet, (c * d - a * f) * invDet],
|
// — green, and blue for a scene the camera left neutral).
|
||||||
[(d * h - e * g) * invDet, (b * g - a * h) * invDet, (a * e - b * d) * invDet],
|
//
|
||||||
];
|
// `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[][] {
|
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) {
|
if (cd?.cam_xyz && Array.isArray(cd.cam_xyz) && cd.cam_xyz.length >= 3) {
|
||||||
const cx = cd.cam_xyz;
|
const cx = cd.cam_xyz;
|
||||||
const scale = (Math.abs(cx[0][0]) > 100) ? 10000.0 : 1.0;
|
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 m = [0, 1, 2].map((i) =>
|
||||||
const gX = (cx[1][0] || 0) / scale, gY = (cx[1][1] || 0) / scale, gZ = (cx[1][2] || 0) / scale;
|
[0, 1, 2].map((j) =>
|
||||||
const bX = (cx[2][0] || 0) / scale, bY = (cx[2][1] || 0) / scale, bZ = (cx[2][2] || 0) / scale;
|
XYZ_TO_SRGB[i][0] * ((cx[j]?.[0] || 0) / scale) +
|
||||||
|
XYZ_TO_SRGB[i][1] * ((cx[j]?.[1] || 0) / scale) +
|
||||||
const m00 = 3.2404542 * rX - 1.5371385 * rY - 0.4985314 * rZ;
|
XYZ_TO_SRGB[i][2] * ((cx[j]?.[2] || 0) / scale)
|
||||||
const m01 = 3.2404542 * gX - 1.5371385 * gY - 0.4985314 * gZ;
|
)
|
||||||
const m02 = 3.2404542 * bX - 1.5371385 * bY - 0.4985314 * bZ;
|
);
|
||||||
|
return m.map((row) => {
|
||||||
const m10 = -0.9692660 * rX + 1.8760108 * rY + 0.0415560 * rZ;
|
const num = row[0] + row[1] + row[2];
|
||||||
const m11 = -0.9692660 * gX + 1.8760108 * gY + 0.0415560 * gZ;
|
return Math.abs(num) < 1e-6 ? row : row.map((v) => v / num);
|
||||||
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],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
return [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
||||||
|
|||||||
Reference in New Issue
Block a user