@photivra/engine 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +144 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -7
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +3 -0
- package/dist/camera/lens-projection.js +2 -1
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/capture/dng-import.js +1 -1
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/export-jpeg.d.ts +1 -1
- package/dist/capture/export-jpeg.js +6 -4
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/host-yield-cadence.js +1 -1
- package/dist/capture/host-yield-cadence.js.map +1 -1
- package/dist/capture/native-environment-optical-stage.js +1 -1
- package/dist/capture/native-environment-optical-stage.js.map +1 -1
- package/dist/capture/native-raw.js +1 -1
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.js +3 -3
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.js +1 -1
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/capture-color.js +3 -3
- package/dist/color/capture-color.js.map +1 -1
- package/dist/color/chromatic-adaptation.js +2 -2
- package/dist/color/chromatic-adaptation.js.map +1 -1
- package/dist/color/color-matching-functions.js +1 -1
- package/dist/color/color-matching-functions.js.map +1 -1
- package/dist/color/colorimetry-math.js +2 -2
- package/dist/color/colorimetry-math.js.map +1 -1
- package/dist/color/illuminants.js +3 -3
- package/dist/color/illuminants.js.map +1 -1
- package/dist/color/rgb-color-spaces.js +1 -1
- package/dist/color/rgb-color-spaces.js.map +1 -1
- package/dist/color/sensor-color-development.js +1 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/core/record-validation.js +3 -3
- package/dist/core/record-validation.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-prime-lenses.js +3 -3
- package/dist/equipment/generic-prime-lenses.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +23 -1
- package/dist/equipment/generic-tier-assets.js +58 -30
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.d.ts +6 -3
- package/dist/equipment/generic-tier-presets.js +26 -13
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/exposure/auto-iso-slowest-shutter.d.ts +120 -0
- package/dist/exposure/auto-iso-slowest-shutter.js +217 -0
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -0
- package/dist/exposure/exposure-mode-resolver.d.ts +57 -1
- package/dist/exposure/exposure-mode-resolver.js +238 -33
- package/dist/exposure/exposure-mode-resolver.js.map +1 -1
- package/dist/exposure/flash-ttl.js +1 -1
- package/dist/exposure/flash-ttl.js.map +1 -1
- package/dist/exposure/flash.js +1 -1
- package/dist/exposure/flash.js.map +1 -1
- package/dist/facade/chart-material-layers.js +2 -2
- package/dist/facade/chart-material-layers.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +1 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +45 -8
- package/dist/facade/chart-scenes.js +101 -44
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/color-patches.d.ts +129 -0
- package/dist/facade/color-patches.js +209 -0
- package/dist/facade/color-patches.js.map +1 -0
- package/dist/facade/diffraction-provider.js +12 -13
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu.js +1 -1
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view.d.ts +27 -0
- package/dist/facade/display-view.js +234 -65
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/scene-radiance.js +2 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +139 -12
- package/dist/facade/simulate-capture.js +209 -36
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -2
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.js +2 -2
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.js +1 -1
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -1
- package/dist/gpu/display-view-quality-gpu.js +24 -14
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-execution.js +2 -2
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/temporal-sum-gpu.js +23 -23
- package/dist/gpu/webgpu-runtime.js +1 -1
- package/dist/gpu/webgpu-runtime.js.map +1 -1
- package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
- package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
- package/dist/gpu/wgsl-chart-kernel.js +117 -117
- package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +13 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.js +522 -488
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-kernels.js +203 -203
- package/dist/gpu/wgsl-kernels.js.map +1 -1
- package/dist/gpu/wgsl-sensor-kernel.js +302 -302
- package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.js +1 -1
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.js +4 -4
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/front-of-lens-filter.js +1 -1
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/irradiance-convolution.d.ts +10 -0
- package/dist/optics/irradiance-convolution.js +49 -14
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.js +1 -1
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.js +2 -2
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-jpeg.js +13 -10
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/output/text-metadata.d.ts +22 -2
- package/dist/output/text-metadata.js +58 -9
- package/dist/output/text-metadata.js.map +1 -1
- package/dist/sensor/fixed-pattern-noise.js +1 -1
- package/dist/sensor/fixed-pattern-noise.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.js +1 -1
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/docs/API_REFERENCE.md +49 -1
- package/docs/QUICK_START.md +104 -9
- package/docs/USAGE.md +241 -2
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-tier-assets.md +41 -1
- package/docs/api/equipment-generic-tier-presets.md +11 -3
- package/docs/api/exports.json +172 -2
- package/docs/api/exposure-auto-iso-slowest-shutter.md +139 -0
- package/docs/api/exposure-exposure-mode-resolver.md +68 -0
- package/docs/api/facade-chart-scenes.md +36 -5
- package/docs/api/facade-color-patches.md +150 -0
- package/docs/api/facade-simulate-capture.md +140 -9
- package/docs/api/output-text-metadata.md +34 -0
- package/llms.txt +1 -0
- package/package.json +2 -2
|
@@ -499,73 +499,19 @@ const SITE_RING_LIMIT = 1 << 18;
|
|
|
499
499
|
/** Pilot points per axis: about one per two sites of the footprint plus its reach, 4 to 16. */
|
|
500
500
|
function pilotCount(span) { return Math.max(4, Math.min(16, Math.ceil(span / 2))); }
|
|
501
501
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* the
|
|
502
|
+
* createAhdDemosaicTask's steps at the centre pixel of an 11 × 11 patch (patch (dy + 5)·11 + dx + 5, normalized site values):
|
|
503
|
+
* `choose(phase)` fills the two candidates within 2 sites (`horizontal`, `vertical`: (dy + 2)·5 + dx + 2, three channels) and
|
|
504
|
+
* returns the centre's direction, 0 horizontal, 1 vertical, 2 equal homogeneity (both averaged). Greens are read by flat
|
|
505
|
+
* index (dy + 3)·7 + dx + 3; every value is formed with the same operations in the same order as the steps themselves,
|
|
506
|
+
* except that CIELAB's cube root is `portableCbrt` (within 1 ulp of Math.cbrt) and squares are products, so the result has
|
|
507
|
+
* the same bits in every JavaScript engine. (The task keeps its intermediate planes in float32; these are binary64.)
|
|
505
508
|
*/
|
|
506
|
-
function
|
|
507
|
-
const
|
|
508
|
-
const k = Float64Array.from(setup.readout, (r) => r.perElectron), c0 = Float64Array.from(setup.readout, (r) => r.constantVariance);
|
|
509
|
-
const M = setup.matrix, m00 = M[0][0], m01 = M[0][1], m02 = M[0][2], m10 = M[1][0], m11 = M[1][1], m12 = M[1][2], m20 = M[2][0], m21 = M[2][1], m22 = M[2][2];
|
|
510
|
-
const g0 = setup.gains[0], g1 = setup.gains[1], g2 = setup.gains[2], L = setup.clipLevel, reinhard = setup.toneCurve === "positive-reinhard-per-channel";
|
|
511
|
-
const correction = new Float64Array(3), patch = new Float64Array(AHD_SIDE * AHD_SIDE);
|
|
512
|
-
const readStd = Float64Array.from(c0, (v) => Math.sqrt(v));
|
|
513
|
-
/** The offset of a grid state's three values in states.values (computed and stored on first use). */
|
|
514
|
-
function gridCorrection(i0, i1, i2) {
|
|
515
|
-
const key = (i0 * AHD_KEY_LEVELS + i1) * AHD_KEY_LEVELS + i2, cached = states.index.get(key);
|
|
516
|
-
if (cached !== undefined)
|
|
517
|
-
return 3 * cached;
|
|
518
|
-
const electrons = [i0, i1, i2].map((i, c) => portableExp2(i / AHD_KEY_STEPS - AHD_KEY_OCTAVES) / k[c]);
|
|
519
|
-
const random = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, fmixKey(i0, i1, i2), DISPLAY_VIEW_AHD_STREAM_ID));
|
|
520
|
-
const sum = new Float64Array(3), draws = ahdCorrection.drawsPerState, poisson = electrons.map((e) => createPoissonSampler(e, true));
|
|
521
|
-
const normal = createPortableStandardNormalPairs(random);
|
|
522
|
-
for (let draw = 0; draw < draws; draw++) {
|
|
523
|
-
const phase = draw & 3, roles = AHD_ROLES[phase];
|
|
524
|
-
for (let p = 0; p < AHD_SIDE * AHD_SIDE; p++) {
|
|
525
|
-
// The 16 sites the AHD steps never read are not drawn.
|
|
526
|
-
if (AHD_READ[p] === 0)
|
|
527
|
-
continue;
|
|
528
|
-
const c = roles[p], value = k[c] * poisson[c](random) + readStd[c] * normal();
|
|
529
|
-
patch[p] = value < 1 ? value : 1;
|
|
530
|
-
}
|
|
531
|
-
ahdDifference(phase, correction);
|
|
532
|
-
for (let comp = 0; comp < 3; comp++)
|
|
533
|
-
sum[comp] = sum[comp] + correction[comp] / draws;
|
|
534
|
-
}
|
|
535
|
-
if (states.index.size >= AHD_STATE_LIMIT)
|
|
536
|
-
states.index.clear();
|
|
537
|
-
const n = states.index.size;
|
|
538
|
-
if (states.values.length < 3 * (n + 1)) {
|
|
539
|
-
const grown = new Float64Array(2 * states.values.length);
|
|
540
|
-
grown.set(states.values);
|
|
541
|
-
states.values = grown;
|
|
542
|
-
}
|
|
543
|
-
states.values.set(sum, 3 * n);
|
|
544
|
-
states.index.set(key, n);
|
|
545
|
-
return 3 * n;
|
|
546
|
-
}
|
|
547
|
-
const lab = ahdCorrection.labMatrix, labWhite = Float64Array.from(lab, (row) => row[0] + row[1] + row[2]);
|
|
509
|
+
export function createAhdCentreSteps(labMatrix, patch) {
|
|
510
|
+
const lab = labMatrix, labWhite = Float64Array.from(lab, (row) => row[0] + row[1] + row[2]);
|
|
548
511
|
const l00 = lab[0][0], l01 = lab[0][1], l02 = lab[0][2], l10 = lab[1][0], l11 = lab[1][1], l12 = lab[1][2], l20 = lab[2][0], l21 = lab[2][1], l22 = lab[2][2];
|
|
549
512
|
const lw0 = labWhite[0], lw1 = labWhite[1], lw2 = labWhite[2];
|
|
550
513
|
const gh = new Float64Array(49), gv = new Float64Array(49), cand = [new Float64Array(75), new Float64Array(75)], labs = [new Float64Array(75), new Float64Array(75)];
|
|
551
|
-
|
|
552
|
-
/** Develops one deterministic camera pixel and renders it: WB, highlight clip, colour matrix, tone curve, clip to [0, 1]. */
|
|
553
|
-
function renderPixel(r, gr, b, out) {
|
|
554
|
-
const u0 = Math.min(L, g0 * r), u1 = Math.min(L, g1 * gr), u2 = Math.min(L, g2 * b);
|
|
555
|
-
for (let comp = 0; comp < 3; comp++) {
|
|
556
|
-
let v = comp === 0 ? m00 * u0 + m01 * u1 + m02 * u2 : comp === 1 ? m10 * u0 + m11 * u1 + m12 * u2 : m20 * u0 + m21 * u1 + m22 * u2;
|
|
557
|
-
if (reinhard)
|
|
558
|
-
v = v > 0 ? v / (1 + v) : v;
|
|
559
|
-
out[comp] = v < 0 ? 0 : v > 1 ? 1 : v;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* h(AHD) − ½(h(H) + h(V)) at the centre of the drawn neighbourhood (createAhdDemosaicTask's steps, centre pixel only). The
|
|
564
|
-
* patch and the greens are read by flat index (patch (dy + 5)·11 + dx + 5, greens (dy + 3)·7 + dx + 3); every value is formed
|
|
565
|
-
* with the same operations in the same order as the steps themselves, except that CIELAB's cube root is `portableCbrt`
|
|
566
|
-
* (within 1 ulp of Math.cbrt) and squares are products, so the result has the same bits in every JavaScript engine.
|
|
567
|
-
*/
|
|
568
|
-
function ahdDifference(phase, out) {
|
|
514
|
+
function choose(phase) {
|
|
569
515
|
const roles = AHD_ROLES[phase];
|
|
570
516
|
// 1. Directional greens within 3 sites.
|
|
571
517
|
for (let dy = -3; dy <= 3; dy++)
|
|
@@ -625,9 +571,195 @@ function createGridCorrection(setup, states) {
|
|
|
625
571
|
sh += (hL0 <= epsL && hC0 <= epsC ? 1 : 0) + (hL1 <= epsL && hC1 <= epsC ? 1 : 0) + (hL2 <= epsL && hC2 <= epsC ? 1 : 0) + (hL3 <= epsL && hC3 <= epsC ? 1 : 0);
|
|
626
572
|
sv += (vL0 <= epsL && vC0 <= epsC ? 1 : 0) + (vL1 <= epsL && vC1 <= epsC ? 1 : 0) + (vL2 <= epsL && vC2 <= epsC ? 1 : 0) + (vL3 <= epsL && vC3 <= epsC ? 1 : 0);
|
|
627
573
|
}
|
|
628
|
-
|
|
574
|
+
return sh > sv ? 0 : sv > sh ? 1 : 2;
|
|
575
|
+
}
|
|
576
|
+
return { horizontal: cand[0], vertical: cand[1], choose };
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* #457: AHD's direction at real pixels of the noise-free mosaic, as `createAhdDemosaicTask` computes it on the whole raster
|
|
580
|
+
* (every neighbour read at its reflect-101 position, so a position's greens, candidates, CIELAB and homogeneity are those of
|
|
581
|
+
* the task's planes), with each per-position quantity computed once and kept in rings of native rows. `direction(x, y)` is 0
|
|
582
|
+
* horizontal, 1 vertical, 2 equal homogeneity; `site(x, y)` gives the normalized expected value of an in-raster site, clipped
|
|
583
|
+
* at white. The kept columns are [x0, x0 + columns), set by `origin(x0)` (which also forgets every kept value); they must hold
|
|
584
|
+
* every real pixel asked about and 5 sites around it, clipped to the raster. `rows` is a power of two at least the native rows
|
|
585
|
+
* the real pixels asked about between two `origin` calls span plus 10. The arithmetic is the task's, in binary64 (the task
|
|
586
|
+
* stores its planes in float32), with `portableCbrt` and products for squares, as `createAhdCentreSteps`.
|
|
587
|
+
*/
|
|
588
|
+
export function createAhdDirectionPlane(labMatrix, nativeWidth, nativeHeight, columns, rows, site) {
|
|
589
|
+
const lab = labMatrix, labWhite = Float64Array.from(lab, (row) => row[0] + row[1] + row[2]);
|
|
590
|
+
const l00 = lab[0][0], l01 = lab[0][1], l02 = lab[0][2], l10 = lab[1][0], l11 = lab[1][1], l12 = lab[1][2], l20 = lab[2][0], l21 = lab[2][1], l22 = lab[2][2];
|
|
591
|
+
const lw0 = labWhite[0], lw1 = labWhite[1], lw2 = labWhite[2];
|
|
592
|
+
const W = columns, mask = rows - 1, n = rows * W;
|
|
593
|
+
// Per position (slot (y & mask)·W + x − x0): the site value, the two greens, the two candidates' RGB and CIELAB and the two
|
|
594
|
+
// homogeneity counts, each valid while its stamp is epoch·2^24 + the position's row + 1 (exact in binary64; rows < 2^24).
|
|
595
|
+
const siteV = new Float64Array(n), siteS = new Float64Array(n), gH = new Float64Array(n), gV = new Float64Array(n), gS = new Float64Array(n);
|
|
596
|
+
const rgb = new Float64Array(6 * n), labs = new Float64Array(6 * n), cS = new Float64Array(n), hom = new Uint8Array(2 * n), hS = new Float64Array(n);
|
|
597
|
+
let x0 = 0, epoch = 0;
|
|
598
|
+
const rx = (x) => (x < 0 ? -x : x >= nativeWidth ? 2 * nativeWidth - 2 - x : x);
|
|
599
|
+
const ry = (y) => (y < 0 ? -y : y >= nativeHeight ? 2 * nativeHeight - 2 - y : y);
|
|
600
|
+
const role = (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2));
|
|
601
|
+
/** The site at (x, y) read reflect-101 (`at` of the task). */
|
|
602
|
+
function at(x, y) {
|
|
603
|
+
const X = rx(x), Y = ry(y), q = (Y & mask) * W + X - x0;
|
|
604
|
+
if (siteS[q] !== epoch + Y + 1) {
|
|
605
|
+
siteV[q] = site(X, Y);
|
|
606
|
+
siteS[q] = epoch + Y + 1;
|
|
607
|
+
}
|
|
608
|
+
return siteV[q];
|
|
609
|
+
}
|
|
610
|
+
/** Fills the two greens of in-raster position (x, y) (step 1) and returns its slot. */
|
|
611
|
+
function greens(x, y) {
|
|
612
|
+
const q = (y & mask) * W + x - x0;
|
|
613
|
+
if (gS[q] === epoch + y + 1)
|
|
614
|
+
return q;
|
|
615
|
+
const c = at(x, y);
|
|
616
|
+
if (role(x, y) === 1) {
|
|
617
|
+
gH[q] = c;
|
|
618
|
+
gV[q] = c;
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
gH[q] = (at(x - 1, y) + at(x + 1, y)) / 2 + (2 * c - at(x - 2, y) - at(x + 2, y)) / 4;
|
|
622
|
+
gV[q] = (at(x, y - 1) + at(x, y + 1)) / 2 + (2 * c - at(x, y - 2) - at(x, y + 2)) / 4;
|
|
623
|
+
}
|
|
624
|
+
gS[q] = epoch + y + 1;
|
|
625
|
+
return q;
|
|
626
|
+
}
|
|
627
|
+
/** s − g of direction d at the reflected position of (x, y). */
|
|
628
|
+
function difference(x, y, d) {
|
|
629
|
+
const X = rx(x), Y = ry(y), q = greens(X, Y);
|
|
630
|
+
return at(X, Y) - (d === 0 ? gH[q] : gV[q]);
|
|
631
|
+
}
|
|
632
|
+
/** Fills the two candidates and their CIELAB at in-raster position (x, y) (steps 2 and 3) and returns its slot. */
|
|
633
|
+
function candidates(x, y) {
|
|
634
|
+
const q = (y & mask) * W + x - x0;
|
|
635
|
+
if (cS[q] === epoch + y + 1)
|
|
636
|
+
return q;
|
|
637
|
+
greens(x, y);
|
|
638
|
+
const own = role(x, y);
|
|
639
|
+
for (let d = 0; d < 2; d++) {
|
|
640
|
+
const gi = d === 0 ? gH[q] : gV[q], o = 6 * q + 3 * d;
|
|
641
|
+
rgb[o + 1] = gi;
|
|
642
|
+
if (own === 1) {
|
|
643
|
+
const hRole = role(x + 1, y), vRole = 2 - hRole;
|
|
644
|
+
const a = difference(x - 1, y, d), b = difference(x + 1, y, d);
|
|
645
|
+
rgb[o + hRole] = gi + (a + b) / 2;
|
|
646
|
+
const cc = difference(x, y - 1, d), dd = difference(x, y + 1, d);
|
|
647
|
+
rgb[o + vRole] = gi + (cc + dd) / 2;
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
rgb[o + own] = at(x, y);
|
|
651
|
+
rgb[o + 2 - own] = gi + (difference(x - 1, y - 1, d) + difference(x + 1, y - 1, d) + difference(x - 1, y + 1, d) + difference(x + 1, y + 1, d)) / 4;
|
|
652
|
+
}
|
|
653
|
+
const R = rgb[o], G = rgb[o + 1], Bc = rgb[o + 2];
|
|
654
|
+
let tx = (l00 * R + l01 * G + l02 * Bc) / lw0, ty = (l10 * R + l11 * G + l12 * Bc) / lw1, tz = (l20 * R + l21 * G + l22 * Bc) / lw2;
|
|
655
|
+
tx = tx > LAB_EPSILON ? portableCbrt(tx) : (LAB_KAPPA * tx + 16) / 116;
|
|
656
|
+
ty = ty > LAB_EPSILON ? portableCbrt(ty) : (LAB_KAPPA * ty + 16) / 116;
|
|
657
|
+
tz = tz > LAB_EPSILON ? portableCbrt(tz) : (LAB_KAPPA * tz + 16) / 116;
|
|
658
|
+
labs[o] = 116 * ty - 16;
|
|
659
|
+
labs[o + 1] = 500 * (tx - ty);
|
|
660
|
+
labs[o + 2] = 200 * (ty - tz);
|
|
661
|
+
}
|
|
662
|
+
cS[q] = epoch + y + 1;
|
|
663
|
+
return q;
|
|
664
|
+
}
|
|
665
|
+
/** The two homogeneity counts of in-raster position (x, y) (step 4), at hom[2·slot] and hom[2·slot + 1]. */
|
|
666
|
+
function homogeneity(x, y) {
|
|
667
|
+
const q = (y & mask) * W + x - x0;
|
|
668
|
+
if (hS[q] === epoch + y + 1)
|
|
669
|
+
return q;
|
|
670
|
+
const i = 6 * candidates(x, y), jl = 6 * candidates(rx(x - 1), y), jr = 6 * candidates(rx(x + 1), y), ju = 6 * candidates(x, ry(y - 1)), jd = 6 * candidates(x, ry(y + 1));
|
|
671
|
+
const h = labs;
|
|
672
|
+
// Direction 0 at offsets +0, direction 1 at +3; neighbours left, right, up, down.
|
|
673
|
+
const hL0 = Math.abs(h[i] - h[jl]), hL1 = Math.abs(h[i] - h[jr]), hL2 = Math.abs(h[i] - h[ju]), hL3 = Math.abs(h[i] - h[jd]);
|
|
674
|
+
const vL0 = Math.abs(h[i + 3] - h[jl + 3]), vL1 = Math.abs(h[i + 3] - h[jr + 3]), vL2 = Math.abs(h[i + 3] - h[ju + 3]), vL3 = Math.abs(h[i + 3] - h[jd + 3]);
|
|
675
|
+
const hC0 = square(h[i + 1] - h[jl + 1]) + square(h[i + 2] - h[jl + 2]), hC1 = square(h[i + 1] - h[jr + 1]) + square(h[i + 2] - h[jr + 2]);
|
|
676
|
+
const hC2 = square(h[i + 1] - h[ju + 1]) + square(h[i + 2] - h[ju + 2]), hC3 = square(h[i + 1] - h[jd + 1]) + square(h[i + 2] - h[jd + 2]);
|
|
677
|
+
const vC0 = square(h[i + 4] - h[jl + 4]) + square(h[i + 5] - h[jl + 5]), vC1 = square(h[i + 4] - h[jr + 4]) + square(h[i + 5] - h[jr + 5]);
|
|
678
|
+
const vC2 = square(h[i + 4] - h[ju + 4]) + square(h[i + 5] - h[ju + 5]), vC3 = square(h[i + 4] - h[jd + 4]) + square(h[i + 5] - h[jd + 5]);
|
|
679
|
+
const epsL = Math.min(Math.max(hL0, hL1), Math.max(vL2, vL3)), epsC = Math.min(Math.max(hC0, hC1), Math.max(vC2, vC3));
|
|
680
|
+
hom[2 * q] = (hL0 <= epsL && hC0 <= epsC ? 1 : 0) + (hL1 <= epsL && hC1 <= epsC ? 1 : 0) + (hL2 <= epsL && hC2 <= epsC ? 1 : 0) + (hL3 <= epsL && hC3 <= epsC ? 1 : 0);
|
|
681
|
+
hom[2 * q + 1] = (vL0 <= epsL && vC0 <= epsC ? 1 : 0) + (vL1 <= epsL && vC1 <= epsC ? 1 : 0) + (vL2 <= epsL && vC2 <= epsC ? 1 : 0) + (vL3 <= epsL && vC3 <= epsC ? 1 : 0);
|
|
682
|
+
hS[q] = epoch + y + 1;
|
|
683
|
+
return q;
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
origin(first) { x0 = first; epoch += 16777216; },
|
|
687
|
+
/** Step 5: the 3 × 3 sums of homogeneity (reflected) select the direction. */
|
|
688
|
+
direction(x, y) {
|
|
689
|
+
let sh = 0, sv = 0;
|
|
690
|
+
for (let dy = -1; dy <= 1; dy++)
|
|
691
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
692
|
+
const q = homogeneity(rx(x + dx), ry(y + dy));
|
|
693
|
+
sh += hom[2 * q];
|
|
694
|
+
sv += hom[2 * q + 1];
|
|
695
|
+
}
|
|
696
|
+
return sh > sv ? 0 : sv > sh ? 1 : 2;
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The state computer of one setup's AHD correction (#447: shared by the render and `computeDisplayViewCorrectionStates`):
|
|
702
|
+
* gridCorrection(i0, i1, i2) returns the offset of the grid state's three values in `states.values`, computing and storing
|
|
703
|
+
* the state on first use. The code and its operations are those the render ran in place before.
|
|
704
|
+
*/
|
|
705
|
+
function createGridCorrection(setup, states) {
|
|
706
|
+
const { seed, ahdCorrection } = setup;
|
|
707
|
+
const k = Float64Array.from(setup.readout, (r) => r.perElectron), c0 = Float64Array.from(setup.readout, (r) => r.constantVariance);
|
|
708
|
+
const M = setup.matrix, m00 = M[0][0], m01 = M[0][1], m02 = M[0][2], m10 = M[1][0], m11 = M[1][1], m12 = M[1][2], m20 = M[2][0], m21 = M[2][1], m22 = M[2][2];
|
|
709
|
+
const g0 = setup.gains[0], g1 = setup.gains[1], g2 = setup.gains[2], L = setup.clipLevel, reinhard = setup.toneCurve === "positive-reinhard-per-channel";
|
|
710
|
+
const correction = new Float64Array(3), patch = new Float64Array(AHD_SIDE * AHD_SIDE);
|
|
711
|
+
const readStd = Float64Array.from(c0, (v) => Math.sqrt(v));
|
|
712
|
+
/** The offset of a grid state's three values in states.values (computed and stored on first use). */
|
|
713
|
+
function gridCorrection(i0, i1, i2) {
|
|
714
|
+
const key = (i0 * AHD_KEY_LEVELS + i1) * AHD_KEY_LEVELS + i2, cached = states.index.get(key);
|
|
715
|
+
if (cached !== undefined)
|
|
716
|
+
return 3 * cached;
|
|
717
|
+
const electrons = [i0, i1, i2].map((i, c) => portableExp2(i / AHD_KEY_STEPS - AHD_KEY_OCTAVES) / k[c]);
|
|
718
|
+
const random = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, fmixKey(i0, i1, i2), DISPLAY_VIEW_AHD_STREAM_ID));
|
|
719
|
+
const sum = new Float64Array(3), draws = ahdCorrection.drawsPerState, poisson = electrons.map((e) => createPoissonSampler(e, true));
|
|
720
|
+
const normal = createPortableStandardNormalPairs(random);
|
|
721
|
+
for (let draw = 0; draw < draws; draw++) {
|
|
722
|
+
const phase = draw & 3, roles = AHD_ROLES[phase];
|
|
723
|
+
for (let p = 0; p < AHD_SIDE * AHD_SIDE; p++) {
|
|
724
|
+
// The 16 sites the AHD steps never read are not drawn.
|
|
725
|
+
if (AHD_READ[p] === 0)
|
|
726
|
+
continue;
|
|
727
|
+
const c = roles[p], value = k[c] * poisson[c](random) + readStd[c] * normal();
|
|
728
|
+
patch[p] = value < 1 ? value : 1;
|
|
729
|
+
}
|
|
730
|
+
ahdDifference(phase, correction);
|
|
731
|
+
for (let comp = 0; comp < 3; comp++)
|
|
732
|
+
sum[comp] = sum[comp] + correction[comp] / draws;
|
|
733
|
+
}
|
|
734
|
+
if (states.index.size >= AHD_STATE_LIMIT)
|
|
735
|
+
states.index.clear();
|
|
736
|
+
const n = states.index.size;
|
|
737
|
+
if (states.values.length < 3 * (n + 1)) {
|
|
738
|
+
const grown = new Float64Array(2 * states.values.length);
|
|
739
|
+
grown.set(states.values);
|
|
740
|
+
states.values = grown;
|
|
741
|
+
}
|
|
742
|
+
states.values.set(sum, 3 * n);
|
|
743
|
+
states.index.set(key, n);
|
|
744
|
+
return 3 * n;
|
|
745
|
+
}
|
|
746
|
+
const steps = createAhdCentreSteps(ahdCorrection.labMatrix, patch);
|
|
747
|
+
const rgb = new Float64Array(3), rendered = new Float64Array(3), linear = new Float64Array(3);
|
|
748
|
+
/** Develops one deterministic camera pixel and renders it: WB, highlight clip, colour matrix, tone curve, clip to [0, 1]. */
|
|
749
|
+
function renderPixel(r, gr, b, out) {
|
|
750
|
+
const u0 = Math.min(L, g0 * r), u1 = Math.min(L, g1 * gr), u2 = Math.min(L, g2 * b);
|
|
751
|
+
for (let comp = 0; comp < 3; comp++) {
|
|
752
|
+
let v = comp === 0 ? m00 * u0 + m01 * u1 + m02 * u2 : comp === 1 ? m10 * u0 + m11 * u1 + m12 * u2 : m20 * u0 + m21 * u1 + m22 * u2;
|
|
753
|
+
if (reinhard)
|
|
754
|
+
v = v > 0 ? v / (1 + v) : v;
|
|
755
|
+
out[comp] = v < 0 ? 0 : v > 1 ? 1 : v;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/** h(AHD) − ½(h(H) + h(V)) at the centre of the drawn neighbourhood (`createAhdCentreSteps`). */
|
|
759
|
+
function ahdDifference(phase, out) {
|
|
760
|
+
const dir = steps.choose(phase), a = steps.horizontal, b = steps.vertical, centre = 12;
|
|
629
761
|
for (let c = 0; c < 3; c++)
|
|
630
|
-
rgb[c] =
|
|
762
|
+
rgb[c] = dir === 0 ? a[3 * centre + c] : dir === 1 ? b[3 * centre + c] : (a[3 * centre + c] + b[3 * centre + c]) / 2;
|
|
631
763
|
renderPixel(rgb[0], rgb[1], rgb[2], rendered);
|
|
632
764
|
renderPixel(a[3 * centre], a[3 * centre + 1], a[3 * centre + 2], linear);
|
|
633
765
|
for (let c = 0; c < 3; c++)
|
|
@@ -720,6 +852,19 @@ export async function renderDisplayView(setup) {
|
|
|
720
852
|
// AHD low-signal correction: an 11 × 11 noisy neighbourhood (AHD reads up to 5 sites away) and the local AHD steps.
|
|
721
853
|
const ahdCorrection = setup.ahdCorrection, windowReach = ahdCorrection ? Math.max(reach, AHD_REACH) : reach;
|
|
722
854
|
const cmAverage = new Float64Array(3);
|
|
855
|
+
// #457: AHD's choice on the noise-free mosaic. Per candidate the rendered mean and the camera-channel means of the sample.
|
|
856
|
+
const candidateRendered = new Float64Array(6), candidateChannels = new Float64Array(6);
|
|
857
|
+
/** The normalized expected value of an in-raster site, clipped at white (the noise-free mosaic AHD's choice reads). */
|
|
858
|
+
const noiseFreeSite = (x, y) => { const c = (y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2), v = k[c] * (setup.signal.site(x, y) + dark[c]); return v < 1 ? v : 1; };
|
|
859
|
+
// The exact path keeps the region's columns for the whole render; the stratified estimator keeps one display pixel's
|
|
860
|
+
// footprint at a time (its samples are scattered over it), so its plane is set to each display pixel's columns.
|
|
861
|
+
let choiceRows = 1;
|
|
862
|
+
while (choiceRows < Math.ceil(sy) + 2 + 2 * AHD_REACH)
|
|
863
|
+
choiceRows *= 2;
|
|
864
|
+
const choiceX0 = Math.max(0, exactX0 - AHD_REACH), choiceColumns = exactView ? Math.min(nativeWidth - 1, exactX0 + exactWidth - 1 + AHD_REACH) - choiceX0 + 1 : Math.ceil(sx) + 2 + 2 * AHD_REACH;
|
|
865
|
+
const choicePlane = ahdCorrection ? createAhdDirectionPlane(ahdCorrection.labMatrix, nativeWidth, nativeHeight, choiceColumns, exactView ? 2 * choiceRows : choiceRows, noiseFreeSite) : undefined;
|
|
866
|
+
if (choicePlane && exactView)
|
|
867
|
+
choicePlane.origin(choiceX0);
|
|
723
868
|
let correctedPixels = 0;
|
|
724
869
|
// The correction of a flat neighbourhood whose sites of channel c have normalized mean μ_c, on a logarithmic grid of states
|
|
725
870
|
// (AHD_KEY_STEPS per octave from 2^-AHD_KEY_OCTAVES of white); each grid state is computed once with draws seeded by the state
|
|
@@ -866,6 +1011,8 @@ export async function renderDisplayView(setup) {
|
|
|
866
1011
|
markM = new Int32Array(slotsCount);
|
|
867
1012
|
}
|
|
868
1013
|
const mx0 = Math.floor(vx * sx) + margin, my0 = Math.floor(vy * sy) + margin;
|
|
1014
|
+
if (choicePlane && !exactView)
|
|
1015
|
+
choicePlane.origin(Math.max(0, mx0 - AHD_REACH));
|
|
869
1016
|
const mw = Math.min(frameWidth - 1, Math.ceil((vx + 1) * sx)) + margin - mx0 + 1, mh = Math.min(frameHeight - 1, Math.ceil((vy + 1) * sy)) + margin - my0 + 1;
|
|
870
1017
|
const pixelMemo = !exactView && setup.signal.cachesSites && mw * mh <= PIXEL_MEMO_LIMIT;
|
|
871
1018
|
if (pixelMemo) {
|
|
@@ -1220,6 +1367,12 @@ export async function renderDisplayView(setup) {
|
|
|
1220
1367
|
e0 += share * hm[0];
|
|
1221
1368
|
e1 += share * hm[1];
|
|
1222
1369
|
e2 += share * hm[2];
|
|
1370
|
+
candidateRendered[3 * d] = hm[0];
|
|
1371
|
+
candidateRendered[3 * d + 1] = hm[1];
|
|
1372
|
+
candidateRendered[3 * d + 2] = hm[2];
|
|
1373
|
+
candidateChannels[3 * d] = cm[0];
|
|
1374
|
+
candidateChannels[3 * d + 1] = cm[1];
|
|
1375
|
+
candidateChannels[3 * d + 2] = cm[2];
|
|
1223
1376
|
// Linear (Stein) part: a_c = J ∘ M[:, c]·Γ_c, accumulated with B_c·σ_c² (site variance of channel c); the pixel's own
|
|
1224
1377
|
// linear variance (aᵀ·C·a, full channel covariance) for the residual.
|
|
1225
1378
|
let l0 = 0, l1 = 0, l2 = 0;
|
|
@@ -1261,6 +1414,22 @@ export async function renderDisplayView(setup) {
|
|
|
1261
1414
|
// Two-argument maxima (inlined; the same value as one four-argument Math.max, which V8 calls with boxed arguments).
|
|
1262
1415
|
pixelVariance = Math.max(Math.max(pixelVariance, hv[0]), Math.max(hv[1], hv[2]));
|
|
1263
1416
|
}
|
|
1417
|
+
// #457: AHD chooses one candidate per pixel by homogeneity. Where the scene decides that choice (structure finer than a
|
|
1418
|
+
// few sites, such as a Siemens star's centre), the expected value is the chosen candidate's, not the candidates' average:
|
|
1419
|
+
// the steps run on the noise-free mosaic (the expected normalized site values, clipped at white, mirrored at the raster
|
|
1420
|
+
// edges as the capture's AHD reads them), and a decided direction takes that candidate's rendered mean. A tie (every flat
|
|
1421
|
+
// neighbourhood, where the noise decides) keeps the average, and the low-signal correction below stays the noise's
|
|
1422
|
+
// bias of a flat neighbourhood. Skipped where the two candidates' channel means are equal (the choice cannot change the
|
|
1423
|
+
// value) and for one-material samples (equal up to rounding; their memo is keyed by material and phase, not by the
|
|
1424
|
+
// patch). The noise terms are unchanged.
|
|
1425
|
+
if (choicePlane && count === 2 && memoKey < 0 && (candidateChannels[0] !== candidateChannels[3] || candidateChannels[1] !== candidateChannels[4] || candidateChannels[2] !== candidateChannels[5])) {
|
|
1426
|
+
const direction = choicePlane.direction(x, y);
|
|
1427
|
+
if (direction !== 2) {
|
|
1428
|
+
e0 = candidateRendered[3 * direction];
|
|
1429
|
+
e1 = candidateRendered[3 * direction + 1];
|
|
1430
|
+
e2 = candidateRendered[3 * direction + 2];
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1264
1433
|
s0 += wE * e0;
|
|
1265
1434
|
s1 += wE * e1;
|
|
1266
1435
|
s2 += wE * e2;
|