@photivra/engine 2.19.0 → 2.21.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 +289 -1
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +7 -5
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +21 -10
- package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
- package/dist/capture/dng-lens-correction-opcodes.js +119 -0
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +42 -78
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +6 -0
- package/dist/capture/native-raw-dng.js +16 -2
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +15 -8
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +2 -0
- package/dist/core/shared-helpers.js +4 -0
- package/dist/core/shared-helpers.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-lens-catalogue.d.ts +11 -4
- package/dist/equipment/generic-lens-catalogue.js +49 -7
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +18 -14
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.js +2 -3
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +2 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +13 -5
- package/dist/facade/display-view-gpu.js +4 -4
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +1 -1
- package/dist/facade/display-view-lens-signal.js +30 -26
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.d.ts +3 -1
- package/dist/facade/display-view.js +6 -24
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +1 -1
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +50 -0
- package/dist/facade/fast-methods.js +58 -0
- package/dist/facade/fast-methods.js.map +1 -0
- package/dist/facade/fast-resolution.d.ts +12 -0
- package/dist/facade/fast-resolution.js +18 -0
- package/dist/facade/fast-resolution.js.map +1 -0
- package/dist/facade/generic-lens-capture.d.ts +5 -4
- package/dist/facade/generic-lens-capture.js +5 -4
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +353 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -19
- package/dist/facade/simulate-capture.js +374 -145
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +3 -2
- package/dist/facade/simulate-display-view.js +36 -18
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/aperture-diameter.d.ts +47 -0
- package/dist/optics/aperture-diameter.js +50 -0
- package/dist/optics/aperture-diameter.js.map +1 -0
- package/dist/optics/detail-limit.d.ts +182 -0
- package/dist/optics/detail-limit.js +336 -0
- package/dist/optics/detail-limit.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.d.ts +18 -2
- package/dist/optics/generic-lens-psf.js +79 -48
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
- package/dist/optics/scene-to-sensor-irradiance.js +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/crop-to-match-focal-length.d.ts +62 -0
- package/dist/output/crop-to-match-focal-length.js +63 -0
- package/dist/output/crop-to-match-focal-length.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +30 -11
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -3
- package/dist/output/generic-lens-corrections.js +57 -9
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +12 -2
- package/dist/output/lens-corrections.js +22 -7
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/print-contrast-reference.js +1 -1
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/print-size-blur-noise.d.ts +110 -0
- package/dist/output/print-size-blur-noise.js +138 -0
- package/dist/output/print-size-blur-noise.js.map +1 -0
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/eqe-electron-rate.js +1 -7
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +2 -0
- package/dist/sensor/exposure-window.js +2 -2
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/readout-timing.js +2 -23
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.js +1 -6
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +1 -7
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sensor-characteristics.d.ts +147 -0
- package/dist/sensor/sensor-characteristics.js +126 -0
- package/dist/sensor/sensor-characteristics.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +1 -5
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +1 -6
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +3 -0
- package/dist/sensor/temporal-photo-signal.js +5 -2
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/stabilization/system.d.ts +9 -0
- package/dist/stabilization/system.js +5 -6
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +5 -0
- package/docs/API_REFERENCE.md +173 -2
- package/docs/QUICK_START.md +213 -6
- package/docs/USAGE.md +157 -3
- package/docs/api/capture-native-raw-dng.md +6 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +17 -7
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +357 -2
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +99 -0
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +60 -15
- package/docs/api/facade-simulate-display-view.md +3 -2
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-aperture-diameter.md +51 -0
- package/docs/api/optics-detail-limit.md +203 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-crop-to-match-focal-length.md +67 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -2
- package/docs/api/output-lens-corrections.md +11 -1
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-print-size-blur-noise.md +115 -0
- package/docs/api/sensor-sensor-characteristics.md +105 -0
- package/llms.txt +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Representative light sources (#557): four relative spectral power distributions designed by Photivra for indoor
|
|
4
|
+
* light presets, so that two lights at the same correlated color temperature (CCT) can render colors differently.
|
|
5
|
+
*
|
|
6
|
+
* Every spectrum is an engine-defined analytic function of wavelength, not measured or copied data: a constant plus a
|
|
7
|
+
* sum of Gaussian bands,
|
|
8
|
+
*
|
|
9
|
+
* S(λ) = b + Σₖ aₖ·exp(−½·((λ − μₖ)/σₖ)²),
|
|
10
|
+
*
|
|
11
|
+
* with λ in nanometers on 360–830 nm (the CIE 1931 observer's domain), in relative units (the capture facade scales an
|
|
12
|
+
* illuminant to its lux). Each source's band shapes were chosen by Photivra to represent a kind of lamp; two amplitudes
|
|
13
|
+
* (or one amplitude and one band center) were then solved so the chromaticity lies on the Planckian locus at the stated
|
|
14
|
+
* CCT, and rounded (amplitudes to 0.001, wavelengths to 0.1 nm). Those rounded values are the definition. The CCT and
|
|
15
|
+
* Duv of every rounded spectrum are checked with the engine's own `calculateCorrelatedColorTemperature` and
|
|
16
|
+
* `estimateCorrelatedColorTemperatureMcCamy` (docs/LIGHT_SOURCES.md, test/representative-light-sources.test.ts).
|
|
17
|
+
*
|
|
18
|
+
* These are representative constructions, not named products: no CIE, IES or manufacturer spectral data, names or
|
|
19
|
+
* tables are used. The bands are wider than the emission lines of real fluorescent phosphors so that the 5 nm sensor
|
|
20
|
+
* quadrature recommended for color work integrates them; real lamps of each kind vary widely.
|
|
21
|
+
*
|
|
22
|
+
* The exponentials use the portable `portableExp`, so a spectrum's values have the same bits in every JavaScript
|
|
23
|
+
* engine, in captures and in the display view alike.
|
|
24
|
+
*
|
|
25
|
+
* The module is not reached by `simulateCapture` itself: an application passes a spectrum as
|
|
26
|
+
* `illuminant: { kind: "spectrum", spectrum }` (or {@link createRepresentativeLightSourceIlluminant}), so bundles that
|
|
27
|
+
* do not use the sources do not contain them.
|
|
28
|
+
*/
|
|
29
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
30
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
31
|
+
import { requireInputKeys } from "../core/record-validation.js";
|
|
32
|
+
import { portableExp } from "../core/portable-math.js";
|
|
33
|
+
/** Identity of the built-in light-source set. */
|
|
34
|
+
export const REPRESENTATIVE_LIGHT_SOURCE_SET_ID = "photivra-representative-light-sources";
|
|
35
|
+
/** Version of the set's spectra; a change to any spectrum changes it. */
|
|
36
|
+
export const REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION = "1.0.0";
|
|
37
|
+
/** Domain of every spectrum, nm (the CIE 1931 observer's domain; the capture facade uses 380–780 nm of it). */
|
|
38
|
+
export const REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS = /* @__PURE__ */ Object.freeze({ minimum: 360, maximum: 830 });
|
|
39
|
+
/** The source ids, in set order. */
|
|
40
|
+
export const REPRESENTATIVE_LIGHT_SOURCE_IDS = /* @__PURE__ */ Object.freeze([
|
|
41
|
+
"tri-phosphor-fluorescent-4000k", "white-led-low-rendering-4000k", "white-led-high-rendering-4000k", "warm-white-led-2700k"
|
|
42
|
+
]);
|
|
43
|
+
const LABEL = "representative, not a named product";
|
|
44
|
+
/* Photivra's design (docs/LIGHT_SOURCES.md, "The spectra"); the solved values were rounded and are the definition. */
|
|
45
|
+
const ROWS = [
|
|
46
|
+
["tri-phosphor-fluorescent-4000k", "Tri-phosphor fluorescent, 4000 K", "fluorescent", 4000,
|
|
47
|
+
"Three narrow emission bands in the blue, green and red on a low continuum, the construction of a tri-phosphor fluorescent tube, which leaves gaps between the bands.",
|
|
48
|
+
0.02, [["blue phosphor band", 0.285, 450, 6], ["green phosphor line", 1, 544, 3.5], ["red phosphor line", 1.02, 611, 3.5]]],
|
|
49
|
+
["white-led-low-rendering-4000k", "White LED, low color rendering, 4000 K", "led", 4000,
|
|
50
|
+
"A blue pump peak and one broad yellow phosphor band, the simplest white LED, which is weak in the cyan and the deep red.",
|
|
51
|
+
0, [["blue pump", 1.123, 450, 9], ["yellow phosphor", 1, 579.1, 50]]],
|
|
52
|
+
["white-led-high-rendering-4000k", "White LED, high color rendering, 4000 K", "led", 4000,
|
|
53
|
+
"A blue pump with a green and a red phosphor band that fill the cyan and the red, a white LED built for high color rendering at the same CCT as the low-rendering one.",
|
|
54
|
+
0, [["blue pump", 1.306, 450, 9], ["green phosphor", 1, 530, 35], ["red phosphor", 1.264, 630, 40]]],
|
|
55
|
+
["warm-white-led-2700k", "Warm white LED, 2700 K", "led", 2700,
|
|
56
|
+
"The same pump and two-phosphor construction with more red phosphor and less pump, a warm white LED for home lighting.",
|
|
57
|
+
0, [["blue pump", 0.795, 450, 9], ["green phosphor", 1, 540, 38], ["red phosphor", 2.133, 630, 40]]]
|
|
58
|
+
];
|
|
59
|
+
const SET = /* @__PURE__ */ (() => Object.freeze({
|
|
60
|
+
setId: REPRESENTATIVE_LIGHT_SOURCE_SET_ID,
|
|
61
|
+
setVersion: REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION,
|
|
62
|
+
wavelengthRangeNanometers: REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS,
|
|
63
|
+
sources: Object.freeze(ROWS.map(([sourceId, name, family, cct, reason, base, bands]) => Object.freeze({
|
|
64
|
+
sourceId, name, family, nominalCorrelatedColorTemperatureKelvin: cct, label: LABEL, reason, base,
|
|
65
|
+
bands: Object.freeze(bands.map(([role, amplitude, centerNanometers, standardDeviationNanometers]) => Object.freeze({ kind: "gaussian", role, amplitude, centerNanometers, standardDeviationNanometers })))
|
|
66
|
+
})))
|
|
67
|
+
}))();
|
|
68
|
+
/**
|
|
69
|
+
* The built-in representative light sources: Photivra's own parametric spectra of a tri-phosphor fluorescent tube and
|
|
70
|
+
* three white LEDs (low and high color rendering at 4000 K, warm at 2700 K). A static, deeply frozen engine contract.
|
|
71
|
+
* Each is "representative, not a named product"; docs/LIGHT_SOURCES.md gives every formula and the reasons for it.
|
|
72
|
+
*/
|
|
73
|
+
export function getRepresentativeLightSourceSet() {
|
|
74
|
+
return SET;
|
|
75
|
+
}
|
|
76
|
+
function findSource(input, functionName) {
|
|
77
|
+
requireInputKeys(input, functionName, ["sourceId"]);
|
|
78
|
+
const id = input.sourceId;
|
|
79
|
+
const source = typeof id === "string" ? SET.sources.find((s) => s.sourceId === id) : undefined;
|
|
80
|
+
if (!source)
|
|
81
|
+
throw new InvalidConfigurationError("sourceId must be one of the built-in representative light sources (REPRESENTATIVE_LIGHT_SOURCE_IDS).");
|
|
82
|
+
return source;
|
|
83
|
+
}
|
|
84
|
+
/** Most wavelengths whose values one spectrum keeps (a capture uses a few hundred). */
|
|
85
|
+
const CACHE_LIMIT = 4096;
|
|
86
|
+
/**
|
|
87
|
+
* A source's relative spectral power distribution S(λ) = b + Σ aₖ·exp(−½((λ − μₖ)/σₖ)²) as a function spectrum on
|
|
88
|
+
* 360–830 nm (relative units; values memoized per wavelength). Pass it to a capture as
|
|
89
|
+
* `illuminant: { kind: "spectrum", spectrum }`; for color work use `sensor.spectralStepNanometers: 5` or finer
|
|
90
|
+
* (docs/LIGHT_SOURCES.md, "Quadrature"). Throws `InvalidScientificInputError` outside 360–830 nm.
|
|
91
|
+
*/
|
|
92
|
+
export function createRepresentativeLightSourceSpectrum(input) {
|
|
93
|
+
const source = findSource(input, "createRepresentativeLightSourceSpectrum");
|
|
94
|
+
const range = REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS, cache = new Map();
|
|
95
|
+
return {
|
|
96
|
+
kind: "function",
|
|
97
|
+
wavelengthRangeNanometers: { minimum: range.minimum, maximum: range.maximum },
|
|
98
|
+
evaluate(nm) {
|
|
99
|
+
const hit = cache.get(nm);
|
|
100
|
+
if (hit !== undefined)
|
|
101
|
+
return hit;
|
|
102
|
+
if (typeof nm !== "number" || !(nm >= range.minimum && nm <= range.maximum)) {
|
|
103
|
+
throw new InvalidScientificInputError("Representative light source " + source.sourceId + " is defined on 360–830 nm.");
|
|
104
|
+
}
|
|
105
|
+
let value = source.base;
|
|
106
|
+
for (const band of source.bands) {
|
|
107
|
+
const z = (nm - band.centerNanometers) / band.standardDeviationNanometers;
|
|
108
|
+
value += band.amplitude * portableExp(-0.5 * z * z);
|
|
109
|
+
}
|
|
110
|
+
if (cache.size < CACHE_LIMIT)
|
|
111
|
+
cache.set(nm, value);
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A capture illuminant for a source: `{ kind: "spectrum", spectrum, label }` with the spectrum of
|
|
118
|
+
* {@link createRepresentativeLightSourceSpectrum} and the label "<name> (representative, not a named product)".
|
|
119
|
+
*/
|
|
120
|
+
export function createRepresentativeLightSourceIlluminant(input) {
|
|
121
|
+
const source = findSource(input, "createRepresentativeLightSourceIlluminant");
|
|
122
|
+
return { kind: "spectrum", spectrum: createRepresentativeLightSourceSpectrum({ sourceId: source.sourceId }), label: source.name + " (" + LABEL + ")" };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=representative-light-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"representative-light-sources.js","sourceRoot":"","sources":["../../src/facade/representative-light-sources.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Representative light sources (#557): four relative spectral power distributions designed by Photivra for indoor\n * light presets, so that two lights at the same correlated color temperature (CCT) can render colors differently.\n *\n * Every spectrum is an engine-defined analytic function of wavelength, not measured or copied data: a constant plus a\n * sum of Gaussian bands,\n *\n * S(λ) = b + Σₖ aₖ·exp(−½·((λ − μₖ)/σₖ)²),\n *\n * with λ in nanometers on 360–830 nm (the CIE 1931 observer's domain), in relative units (the capture facade scales an\n * illuminant to its lux). Each source's band shapes were chosen by Photivra to represent a kind of lamp; two amplitudes\n * (or one amplitude and one band center) were then solved so the chromaticity lies on the Planckian locus at the stated\n * CCT, and rounded (amplitudes to 0.001, wavelengths to 0.1 nm). Those rounded values are the definition. The CCT and\n * Duv of every rounded spectrum are checked with the engine's own `calculateCorrelatedColorTemperature` and\n * `estimateCorrelatedColorTemperatureMcCamy` (docs/LIGHT_SOURCES.md, test/representative-light-sources.test.ts).\n *\n * These are representative constructions, not named products: no CIE, IES or manufacturer spectral data, names or\n * tables are used. The bands are wider than the emission lines of real fluorescent phosphors so that the 5 nm sensor\n * quadrature recommended for color work integrates them; real lamps of each kind vary widely.\n *\n * The exponentials use the portable `portableExp`, so a spectrum's values have the same bits in every JavaScript\n * engine, in captures and in the display view alike.\n *\n * The module is not reached by `simulateCapture` itself: an application passes a spectrum as\n * `illuminant: { kind: \"spectrum\", spectrum }` (or {@link createRepresentativeLightSourceIlluminant}), so bundles that\n * do not use the sources do not contain them.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { portableExp } from \"../core/portable-math.js\";\nimport type { ColorimetricSpectrum } from \"../color/color-matching-functions.js\";\nimport type { CaptureIlluminant } from \"./scene-radiance.js\";\n\n/** Identity of the built-in light-source set. */\nexport const REPRESENTATIVE_LIGHT_SOURCE_SET_ID = \"photivra-representative-light-sources\";\n/** Version of the set's spectra; a change to any spectrum changes it. */\nexport const REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION = \"1.0.0\";\n/** Domain of every spectrum, nm (the CIE 1931 observer's domain; the capture facade uses 380–780 nm of it). */\nexport const REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS = /* @__PURE__ */ Object.freeze({ minimum: 360, maximum: 830 });\n\n/** The source ids, in set order. */\nexport const REPRESENTATIVE_LIGHT_SOURCE_IDS = /* @__PURE__ */ Object.freeze([\n \"tri-phosphor-fluorescent-4000k\", \"white-led-low-rendering-4000k\", \"white-led-high-rendering-4000k\", \"warm-white-led-2700k\"\n] as const);\nexport type RepresentativeLightSourceId = (typeof REPRESENTATIVE_LIGHT_SOURCE_IDS)[number];\n\n/** One Gaussian band a·exp(−½((λ − μ)/σ)²) of a source spectrum. */\nexport interface RepresentativeLightSourceBand {\n kind: \"gaussian\";\n /** What the band represents, for example \"blue pump\" or \"green phosphor line\". */\n role: string;\n amplitude: number;\n centerNanometers: number;\n standardDeviationNanometers: number;\n}\n\n/** One built-in source: its id, name, design reason and spectrum definition. */\nexport interface RepresentativeLightSource {\n sourceId: RepresentativeLightSourceId;\n /** Photivra's descriptive name, for example \"White LED, high color rendering, 4000 K\". */\n name: string;\n family: \"fluorescent\" | \"led\";\n /** The CCT the spectrum was designed for, K (its computed CCT is within 1 K of it, Duv within 0.0001; docs/LIGHT_SOURCES.md). */\n nominalCorrelatedColorTemperatureKelvin: number;\n /** Always \"representative, not a named product\". */\n label: \"representative, not a named product\";\n /** One sentence: what the source represents and why it is built this way. */\n reason: string;\n /** The constant b (a low continuum), relative units. */\n base: number;\n bands: readonly RepresentativeLightSourceBand[];\n}\n\nexport interface RepresentativeLightSourceSet {\n setId: typeof REPRESENTATIVE_LIGHT_SOURCE_SET_ID;\n setVersion: typeof REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION;\n wavelengthRangeNanometers: { readonly minimum: number; readonly maximum: number };\n sources: readonly RepresentativeLightSource[];\n}\n\nconst LABEL = \"representative, not a named product\" as const;\ntype BandRow = readonly [role: string, amplitude: number, centerNanometers: number, standardDeviationNanometers: number];\n/* Photivra's design (docs/LIGHT_SOURCES.md, \"The spectra\"); the solved values were rounded and are the definition. */\nconst ROWS: readonly (readonly [RepresentativeLightSourceId, string, \"fluorescent\" | \"led\", number, string, number, readonly BandRow[]])[] = [\n [\"tri-phosphor-fluorescent-4000k\", \"Tri-phosphor fluorescent, 4000 K\", \"fluorescent\", 4000,\n \"Three narrow emission bands in the blue, green and red on a low continuum, the construction of a tri-phosphor fluorescent tube, which leaves gaps between the bands.\",\n 0.02, [[\"blue phosphor band\", 0.285, 450, 6], [\"green phosphor line\", 1, 544, 3.5], [\"red phosphor line\", 1.02, 611, 3.5]]],\n [\"white-led-low-rendering-4000k\", \"White LED, low color rendering, 4000 K\", \"led\", 4000,\n \"A blue pump peak and one broad yellow phosphor band, the simplest white LED, which is weak in the cyan and the deep red.\",\n 0, [[\"blue pump\", 1.123, 450, 9], [\"yellow phosphor\", 1, 579.1, 50]]],\n [\"white-led-high-rendering-4000k\", \"White LED, high color rendering, 4000 K\", \"led\", 4000,\n \"A blue pump with a green and a red phosphor band that fill the cyan and the red, a white LED built for high color rendering at the same CCT as the low-rendering one.\",\n 0, [[\"blue pump\", 1.306, 450, 9], [\"green phosphor\", 1, 530, 35], [\"red phosphor\", 1.264, 630, 40]]],\n [\"warm-white-led-2700k\", \"Warm white LED, 2700 K\", \"led\", 2700,\n \"The same pump and two-phosphor construction with more red phosphor and less pump, a warm white LED for home lighting.\",\n 0, [[\"blue pump\", 0.795, 450, 9], [\"green phosphor\", 1, 540, 38], [\"red phosphor\", 2.133, 630, 40]]]\n];\n\nconst SET: RepresentativeLightSourceSet = /* @__PURE__ */ ((): RepresentativeLightSourceSet => Object.freeze({\n setId: REPRESENTATIVE_LIGHT_SOURCE_SET_ID,\n setVersion: REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION,\n wavelengthRangeNanometers: REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS,\n sources: Object.freeze(ROWS.map(([sourceId, name, family, cct, reason, base, bands]) => Object.freeze({\n sourceId, name, family, nominalCorrelatedColorTemperatureKelvin: cct, label: LABEL, reason, base,\n bands: Object.freeze(bands.map(([role, amplitude, centerNanometers, standardDeviationNanometers]) =>\n Object.freeze({ kind: \"gaussian\" as const, role, amplitude, centerNanometers, standardDeviationNanometers })))\n })))\n}))();\n\n/**\n * The built-in representative light sources: Photivra's own parametric spectra of a tri-phosphor fluorescent tube and\n * three white LEDs (low and high color rendering at 4000 K, warm at 2700 K). A static, deeply frozen engine contract.\n * Each is \"representative, not a named product\"; docs/LIGHT_SOURCES.md gives every formula and the reasons for it.\n */\nexport function getRepresentativeLightSourceSet(): RepresentativeLightSourceSet {\n return SET;\n}\n\nfunction findSource(input: unknown, functionName: string): RepresentativeLightSource {\n requireInputKeys(input, functionName, [\"sourceId\"]);\n const id = (input as { sourceId?: unknown }).sourceId;\n const source = typeof id === \"string\" ? SET.sources.find((s) => s.sourceId === id) : undefined;\n if (!source) throw new InvalidConfigurationError(\"sourceId must be one of the built-in representative light sources (REPRESENTATIVE_LIGHT_SOURCE_IDS).\");\n return source;\n}\n\n/** Most wavelengths whose values one spectrum keeps (a capture uses a few hundred). */\nconst CACHE_LIMIT = 4096;\n\n/**\n * A source's relative spectral power distribution S(λ) = b + Σ aₖ·exp(−½((λ − μₖ)/σₖ)²) as a function spectrum on\n * 360–830 nm (relative units; values memoized per wavelength). Pass it to a capture as\n * `illuminant: { kind: \"spectrum\", spectrum }`; for color work use `sensor.spectralStepNanometers: 5` or finer\n * (docs/LIGHT_SOURCES.md, \"Quadrature\"). Throws `InvalidScientificInputError` outside 360–830 nm.\n */\nexport function createRepresentativeLightSourceSpectrum(input: { sourceId: RepresentativeLightSourceId }): ColorimetricSpectrum {\n const source = findSource(input, \"createRepresentativeLightSourceSpectrum\");\n const range = REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS, cache = new Map<number, number>();\n return {\n kind: \"function\",\n wavelengthRangeNanometers: { minimum: range.minimum, maximum: range.maximum },\n evaluate(nm: number): number {\n const hit = cache.get(nm);\n if (hit !== undefined) return hit;\n if (typeof nm !== \"number\" || !(nm >= range.minimum && nm <= range.maximum)) {\n throw new InvalidScientificInputError(\"Representative light source \" + source.sourceId + \" is defined on 360–830 nm.\");\n }\n let value = source.base;\n for (const band of source.bands) {\n const z = (nm - band.centerNanometers) / band.standardDeviationNanometers;\n value += band.amplitude * portableExp(-0.5 * z * z);\n }\n if (cache.size < CACHE_LIMIT) cache.set(nm, value);\n return value;\n }\n };\n}\n\n/**\n * A capture illuminant for a source: `{ kind: \"spectrum\", spectrum, label }` with the spectrum of\n * {@link createRepresentativeLightSourceSpectrum} and the label \"<name> (representative, not a named product)\".\n */\nexport function createRepresentativeLightSourceIlluminant(input: { sourceId: RepresentativeLightSourceId }): Extract<CaptureIlluminant, { kind: \"spectrum\" }> {\n const source = findSource(input, \"createRepresentativeLightSourceIlluminant\");\n return { kind: \"spectrum\", spectrum: createRepresentativeLightSourceSpectrum({ sourceId: source.sourceId }), label: source.name + \" (\" + LABEL + \")\" };\n}\n"]}
|
|
@@ -101,6 +101,115 @@ export interface CallerMaterialScene {
|
|
|
101
101
|
}
|
|
102
102
|
/** Most materials a {@link CallerMaterialScene} may list. */
|
|
103
103
|
export declare const CALLER_MATERIAL_SCENE_MAX_MATERIALS = 65536;
|
|
104
|
+
/**
|
|
105
|
+
* 2.21 (#581): an image the caller supplies, for an {@link ImagePlaneScene}. `data` holds `width × height` pixels row by row
|
|
106
|
+
* from the top, `channels` (3: RGB, 4: RGBA) samples each, in the encoding's type: `srgb-8` Uint8Array or Uint8ClampedArray
|
|
107
|
+
* (canvas `ImageData.data`), `srgb-16` Uint16Array, `linear-srgb` Float32Array. sRGB primaries and white (IEC 61966-2-1) in
|
|
108
|
+
* every encoding; `srgb-8` and `srgb-16` are decoded with the sRGB transfer function, `linear-srgb` is already linear and is
|
|
109
|
+
* clipped to [0, 1] (the clipped samples are counted). Alpha is linear coverage over the paper. The array is read, never
|
|
110
|
+
* changed or kept beyond the call.
|
|
111
|
+
*/
|
|
112
|
+
export interface ImagePlaneImage {
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
channels: 3 | 4;
|
|
116
|
+
encoding: "srgb-8" | "srgb-16" | "linear-srgb";
|
|
117
|
+
data: Uint8Array | Uint8ClampedArray | Uint16Array | Float32Array;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 2.21 (#581): a photo as a flat matte print on the scene plane, lit by the request's illuminant like a chart. Each pixel's
|
|
121
|
+
* color becomes a reflectance spectrum through the engine's print reflectance lattice (docs/IMAGE_PLANE.md): an
|
|
122
|
+
* approximation, a print of an image that was already processed, clipped and blurred by the camera that took it. The print's
|
|
123
|
+
* physical size is `chartSize` (its height defaults to the image's aspect); without it the print is fitted inside the frame,
|
|
124
|
+
* centered, on the 18 % surround.
|
|
125
|
+
*/
|
|
126
|
+
export interface ImagePlaneScene {
|
|
127
|
+
kind: "image-plane";
|
|
128
|
+
sceneId: string;
|
|
129
|
+
image: ImagePlaneImage;
|
|
130
|
+
/** The paper and the deepest ink: reflectances of sRGB white and black, 0 ≤ black < white ≤ 1. Defaults 0.90 and 0.01. */
|
|
131
|
+
paper?: {
|
|
132
|
+
whiteReflectance?: number;
|
|
133
|
+
blackReflectance?: number;
|
|
134
|
+
};
|
|
135
|
+
/** How the print is reconstructed between pixel centers. Default `bilinear` (the pixels' spectra, bilinearly weighted). */
|
|
136
|
+
interpolation?: "bilinear" | "nearest";
|
|
137
|
+
/** Opt-in raised pixel limit: an integer up to the full-resolution ceiling. Default 24 000 000 pixels. */
|
|
138
|
+
maximumPixels?: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Internal (#581): what a pixel's signals are integrated from (`ResolvedImagePlaneScene.pixelSignals`): the lattice's node spectra at
|
|
142
|
+
* the N spectral nodes (node k's reflectance at λ_n at k·N + n), the white radiance and the channel responses per node.
|
|
143
|
+
*/
|
|
144
|
+
export interface ImagePlaneSpectralNodes {
|
|
145
|
+
count: number;
|
|
146
|
+
nodeSpectra: Float64Array;
|
|
147
|
+
whiteRadiance: Float64Array;
|
|
148
|
+
response: readonly Float64Array[];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Internal (#581): an image-plane print as the tabulated expected signal's scene (`ResolvedImagePlaneScene.signalScene`): its radiance
|
|
152
|
+
* per node, the direct path's signal (each pixel's Σ_n A_c(λ_n)·L_n integrated from its reconstructed spectrum, `pixelSignals`, then
|
|
153
|
+
* interpolated between pixels), and (for the GPU module) the scene, its spectral nodes and the surround's channel signals.
|
|
154
|
+
*/
|
|
155
|
+
export interface ImagePlaneSignalScene {
|
|
156
|
+
kind: "radiance";
|
|
157
|
+
radianceAt(u: number, v: number, nm: number): number;
|
|
158
|
+
signalAt(u: number, v: number, c: number): number;
|
|
159
|
+
image: {
|
|
160
|
+
scene: ResolvedImagePlaneScene;
|
|
161
|
+
nodes: ImagePlaneSpectralNodes;
|
|
162
|
+
surround: readonly [number, number, number];
|
|
163
|
+
whiteRadiance: Float64Array;
|
|
164
|
+
surroundReflectance: number;
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Internal (#581): a validated image-plane scene (src/facade/image-plane.ts, a lazily loaded module), placed on a print of
|
|
169
|
+
* aspect `aspect` (height / width). Print-local coordinates are the chart's: cu = ±1 at the print's left and right edges, cv up
|
|
170
|
+
* on the same scale, ±aspect at its top and bottom.
|
|
171
|
+
*/
|
|
172
|
+
export interface ResolvedImagePlaneScene {
|
|
173
|
+
kind: "image-plane";
|
|
174
|
+
sceneId: string;
|
|
175
|
+
width: number;
|
|
176
|
+
height: number;
|
|
177
|
+
aspect: number;
|
|
178
|
+
channels: 3 | 4;
|
|
179
|
+
encoding: ImagePlaneImage["encoding"];
|
|
180
|
+
interpolation: "bilinear" | "nearest";
|
|
181
|
+
paperWhiteReflectance: number;
|
|
182
|
+
paperBlackReflectance: number;
|
|
183
|
+
maximumPixels: number;
|
|
184
|
+
/** `linear-srgb` samples (color and alpha) clipped to [0, 1]; 0 for the integer encodings. */
|
|
185
|
+
clippedSampleCount: number;
|
|
186
|
+
/** SHA-256 of the image data's bytes, hex. */
|
|
187
|
+
dataSha256: string;
|
|
188
|
+
/** The print's reflectance at a print-local point and wavelength (nm, 380–780). */
|
|
189
|
+
reflectanceAt(cu: number, cv: number, nm: number): number;
|
|
190
|
+
/**
|
|
191
|
+
* One pixel's three channel signals Σ_n A_c(λ_n)·ρ_n·W_n, integrated from its reconstructed spectrum ρ_n (its cell's node spectra
|
|
192
|
+
* blended, then the paper) at the time of use, into `out[at..at + 2]`. No per-channel values of the lattice nodes are formed.
|
|
193
|
+
*/
|
|
194
|
+
pixelSignals(index: number, nodes: ImagePlaneSpectralNodes, out: Float64Array, at: number): void;
|
|
195
|
+
/** The print at a print-local point in node values (`nodeValues[k]` of node k, `neutral` of the flat reflectance 1). */
|
|
196
|
+
sampleAt(cu: number, cv: number, nodeValues: Float64Array, neutral: number): number;
|
|
197
|
+
/** One pixel (row-major index) in node values. */
|
|
198
|
+
pixelValue(index: number, nodeValues: Float64Array, neutral: number): number;
|
|
199
|
+
/** One pixel's lattice cell (its base node r·25 + g·5 + b, returned) and its fractions within the cell per channel (written to `out[0..2]`). */
|
|
200
|
+
pixelLattice(index: number, out: Float64Array): number;
|
|
201
|
+
/** Every lattice node's reflectance at one wavelength (nm, 380–780). */
|
|
202
|
+
nodeReflectances(nm: number): Float64Array;
|
|
203
|
+
/**
|
|
204
|
+
* The print placed on the plane (frame point → print-local cu = u·scale/half, cv = v·scale/half; outside |cu| ≤ 1, |cv| ≤ aspect the
|
|
205
|
+
* surround) as the expected signal's scene at these nodes, responses and white radiance (see `ImagePlaneSignalScene`).
|
|
206
|
+
*/
|
|
207
|
+
signalScene(wavelengths: readonly number[], perNode: readonly Float64Array[], whiteRadiance: Float64Array, radianceAt: (u: number, v: number, nm: number) => number, scale: number, half: number, surroundReflectance: number): ImagePlaneSignalScene;
|
|
208
|
+
/** The scene's form in the facade's source digest (the image's SHA-256 and shape, the paper, the interpolation, the lattice version). */
|
|
209
|
+
digestForm: Readonly<Record<string, unknown>>;
|
|
210
|
+
/** The result's `scene.parameters`: the image, paper and interpolation, the clipped samples and the image's SHA-256. */
|
|
211
|
+
parameters: Readonly<Record<string, unknown>>;
|
|
212
|
+
}
|
|
104
213
|
/** Internal: a validated material scene with its materials prepared (and the caller inputs, for the scene digest). */
|
|
105
214
|
export interface ResolvedCallerMaterialScene {
|
|
106
215
|
kind: "material-function";
|
|
@@ -110,7 +219,7 @@ export interface ResolvedCallerMaterialScene {
|
|
|
110
219
|
materialAt(point: Readonly<SceneMaterialPoint>): number;
|
|
111
220
|
}
|
|
112
221
|
/** Internal: a validated caller scene. */
|
|
113
|
-
export type ResolvedCallerScene = CallerReflectanceScene | CallerRadianceScene | ResolvedCallerMaterialScene;
|
|
222
|
+
export type ResolvedCallerScene = CallerReflectanceScene | CallerRadianceScene | ResolvedCallerMaterialScene | ResolvedImagePlaneScene;
|
|
114
223
|
export interface PlanarSceneGeometry {
|
|
115
224
|
distanceM: number;
|
|
116
225
|
/** Metres per frame unit on the plane (half the framed width). */
|
|
@@ -131,8 +240,11 @@ export interface PlanarSceneRadianceProvider {
|
|
|
131
240
|
providerProfileId: string;
|
|
132
241
|
evaluateRadiance(request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult;
|
|
133
242
|
evaluateApertureRadiance(request: Readonly<SceneRadianceEvaluationRequest>, ray: Readonly<SensorApertureRay>): SceneRadianceEvaluationResult;
|
|
134
|
-
/**
|
|
135
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Spectral radiance at a plane point (frame units), the same value the callbacks return. #551: a moving subject's provider
|
|
245
|
+
* reads it at time `t` (seconds from the first opening, default 0); a still scene ignores it.
|
|
246
|
+
*/
|
|
247
|
+
radianceAt(u: number, v: number, wavelengthNanometers: number, t?: number): number;
|
|
136
248
|
/**
|
|
137
249
|
* 2.3, chart scenes only: the chart material at a plane point (frame units), or `materials.length` for the
|
|
138
250
|
* surround of a fixed-size chart; `materialRadiance` gives that material's spectral radiance, the same value
|
|
@@ -140,6 +252,12 @@ export interface PlanarSceneRadianceProvider {
|
|
|
140
252
|
*/
|
|
141
253
|
chartMaterialAt?(u: number, v: number): number;
|
|
142
254
|
materialRadiance?(material: number, wavelengthNanometers: number): number;
|
|
255
|
+
/**
|
|
256
|
+
* 2.21 (#581), image-plane scenes only: the tabulated expected signal's scene at these spectral nodes and channel responses, its
|
|
257
|
+
* radiance per node and, for the direct path, Σ_n A_c(λ_n)·L(λ_n) integrated from each pixel's reconstructed spectrum
|
|
258
|
+
* (`ResolvedImagePlaneScene.pixelSignals`, the surround's reflectance times the neutral signal outside the print).
|
|
259
|
+
*/
|
|
260
|
+
imageSignalScene?(wavelengths: readonly number[], perNode: readonly Float64Array[]): ImagePlaneSignalScene;
|
|
143
261
|
}
|
|
144
262
|
/** Internal (#394): the material lookup of a provider built by {@link buildPlanarSceneRadianceProvider}, if it has one. */
|
|
145
263
|
export declare function planarProviderMaterialOfDirection(provider: PlanarSceneRadianceProvider): ((direction: Readonly<{
|
|
@@ -171,6 +289,12 @@ export declare function buildPlanarSceneRadianceProvider(input: {
|
|
|
171
289
|
* radiance instead of failing; the plane fills the forward hemisphere only. Omitted: such a query fails, as before.
|
|
172
290
|
*/
|
|
173
291
|
zeroBeyondHorizon?: true;
|
|
292
|
+
/**
|
|
293
|
+
* #550, mixed illumination: the plane's spectral irradiance at a plane point, given in chart units (chart-local with a fixed-size
|
|
294
|
+
* chart, frame units without) and in metres from the optical axis, used instead of `irradianceScale`·S(λ). The provider then has no material access (the radiance at a point is
|
|
295
|
+
* no longer a function of its material alone), so every renderer reads `radianceAt`. A radiance-function scene is refused.
|
|
296
|
+
*/
|
|
297
|
+
irradianceAt?: (u: number, v: number, xM: number, yM: number, wavelengthNanometers: number) => number;
|
|
174
298
|
}): PlanarSceneRadianceProvider;
|
|
175
299
|
/** 2.3: a chart's physical size on the scene plane (`simulateCapture` `chartSize`, `createPlanarSceneRadianceProvider` `chartSize`). */
|
|
176
300
|
export interface ChartSizeOption {
|
|
@@ -199,7 +323,8 @@ export declare function resolveChartSizeOption(value: unknown, frameAspect: numb
|
|
|
199
323
|
* provider `providerProfileId`.
|
|
200
324
|
*/
|
|
201
325
|
export declare function createPlanarSceneRadianceProvider(input: {
|
|
202
|
-
scene:
|
|
326
|
+
/** An `image-plane` scene (#581) is refused: it is taken by the facade's capture and display-view functions, which load its module. */
|
|
327
|
+
scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene | ImagePlaneScene;
|
|
203
328
|
illuminant: CaptureIlluminant;
|
|
204
329
|
illuminanceLux: number;
|
|
205
330
|
distanceM: number;
|
|
@@ -164,6 +164,7 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
164
164
|
const evidence = input.evidence, limitations = input.limitations;
|
|
165
165
|
const caller = "kind" in scene ? scene : undefined, chart = caller ? undefined : scene;
|
|
166
166
|
const materialScene = caller?.kind === "material-function" ? caller : undefined, materials = chart ? chart.materials : materialScene?.materials;
|
|
167
|
+
const imageScene = caller?.kind === "image-plane" ? caller : undefined;
|
|
167
168
|
const placed = geometry.chart, half = placed ? placed.widthM / 2 : 0, chartAspect = placed ? placed.heightM / placed.widthM : 0;
|
|
168
169
|
const shapeAt = tabulatedSpectrum(illuminant.shape);
|
|
169
170
|
/** A material scene's material at a frame point: the caller's index, or `materials.length` for a fixed-size chart's surround. */
|
|
@@ -183,6 +184,29 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
183
184
|
throw new InvalidConfigurationError("Scene materialAt must return an index into its materials.");
|
|
184
185
|
return index;
|
|
185
186
|
}
|
|
187
|
+
const light = input.irradianceAt;
|
|
188
|
+
if (light && caller?.kind === "radiance-function")
|
|
189
|
+
throw new InvalidConfigurationError("illumination needs a lit scene; a radiance-function scene ignores the illuminant.");
|
|
190
|
+
// #581 with #550: a print's signals are integrated per pixel under one light (its spectral nodes); mixed light over a print is not modelled.
|
|
191
|
+
if (light && imageScene)
|
|
192
|
+
throw new InvalidConfigurationError("illumination cannot light an image-plane scene yet: the print's signals are integrated under one illuminant.");
|
|
193
|
+
/** #550: reflectance × the blended irradiance at the point (chart-local units with a fixed-size chart) / π. */
|
|
194
|
+
function litRadianceAt(u, v, nm) {
|
|
195
|
+
let cu = u, cv = v, reflectance;
|
|
196
|
+
if (placed) {
|
|
197
|
+
cu = u * scale / half;
|
|
198
|
+
cv = v * scale / half;
|
|
199
|
+
}
|
|
200
|
+
if (materialScene)
|
|
201
|
+
reflectance = materialReflectanceOf(callerMaterialAt(u, v), nm);
|
|
202
|
+
else if (placed && !(Math.abs(cu) <= 1 && Math.abs(cv) <= chartAspect))
|
|
203
|
+
reflectance = placed.surroundReflectance;
|
|
204
|
+
else if (chart)
|
|
205
|
+
reflectance = chart.reflectanceAt(cu, cv, nm);
|
|
206
|
+
else
|
|
207
|
+
reflectance = callerReflectance(placed ? { xM: u * scale, yM: v * scale, u, v, wavelengthNanometers: nm, chartU: cu, chartV: cv } : { xM: u * scale, yM: v * scale, u, v, wavelengthNanometers: nm });
|
|
208
|
+
return reflectance * light(cu, cv, u * scale, v * scale, nm) / Math.PI;
|
|
209
|
+
}
|
|
186
210
|
function radianceAt(u, v, nm) {
|
|
187
211
|
if (materialScene)
|
|
188
212
|
return materialRadianceOf(callerMaterialAt(u, v), nm);
|
|
@@ -191,21 +215,26 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
191
215
|
const xM = u * scale, yM = v * scale, cu = xM / half, cv = yM / half;
|
|
192
216
|
if (!(Math.abs(cu) <= 1 && Math.abs(cv) <= chartAspect))
|
|
193
217
|
return placed.surroundReflectance * irradianceScale * shapeAt(nm) / Math.PI;
|
|
194
|
-
|
|
195
|
-
|
|
218
|
+
// #581: an image-plane print, like a chart, in print-local units.
|
|
219
|
+
const planar = chart ?? imageScene;
|
|
220
|
+
if (planar)
|
|
221
|
+
return planar.reflectanceAt(cu, cv, nm) * irradianceScale * shapeAt(nm) / Math.PI;
|
|
196
222
|
return callerValue({ xM, yM, u, v, wavelengthNanometers: nm, chartU: cu, chartV: cv }, nm);
|
|
197
223
|
}
|
|
198
224
|
if (chart)
|
|
199
225
|
return chart.reflectanceAt(u, v, nm) * irradianceScale * shapeAt(nm) / Math.PI;
|
|
200
226
|
return callerValue({ xM: u * scale, yM: v * scale, u, v, wavelengthNanometers: nm }, nm);
|
|
201
227
|
}
|
|
228
|
+
function callerReflectance(point) {
|
|
229
|
+
const value = caller.evaluate(point);
|
|
230
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1)
|
|
231
|
+
throw new InvalidConfigurationError("Scene reflectance function must return a value in [0, 1].");
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
202
234
|
function callerValue(point, nm) {
|
|
235
|
+
if (caller.kind === "reflectance-function")
|
|
236
|
+
return callerReflectance(point) * irradianceScale * shapeAt(nm) / Math.PI;
|
|
203
237
|
const value = caller.evaluate(point);
|
|
204
|
-
if (caller.kind === "reflectance-function") {
|
|
205
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1)
|
|
206
|
-
throw new InvalidConfigurationError("Scene reflectance function must return a value in [0, 1].");
|
|
207
|
-
return value * irradianceScale * shapeAt(nm) / Math.PI;
|
|
208
|
-
}
|
|
209
238
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
210
239
|
throw new InvalidConfigurationError("Scene radiance function must return a finite non-negative radiance.");
|
|
211
240
|
return value;
|
|
@@ -217,6 +246,9 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
217
246
|
}
|
|
218
247
|
/** Spectral radiance of material `material` (`materials.length`: a fixed-size chart's surround), in the chart's expression order. */
|
|
219
248
|
function materialRadianceOf(material, nm) {
|
|
249
|
+
return materialReflectanceOf(material, nm) * irradianceScale * shapeAt(nm) / Math.PI;
|
|
250
|
+
}
|
|
251
|
+
function materialReflectanceOf(material, nm) {
|
|
220
252
|
const m = materials[material];
|
|
221
253
|
let value;
|
|
222
254
|
if (m === undefined)
|
|
@@ -230,7 +262,7 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
230
262
|
}
|
|
231
263
|
if (!(value >= 0))
|
|
232
264
|
throw new InvalidConfigurationError("Unknown chart material.");
|
|
233
|
-
return value
|
|
265
|
+
return value;
|
|
234
266
|
}
|
|
235
267
|
function chartMaterialAt(u, v) {
|
|
236
268
|
if (!placed)
|
|
@@ -238,10 +270,12 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
238
270
|
const cu = u * scale / half, cv = v * scale / half;
|
|
239
271
|
return Math.abs(cu) <= 1 && Math.abs(cv) <= chartAspect ? chart.materialAt(cu, cv) : chart.materials.length;
|
|
240
272
|
}
|
|
241
|
-
const chartAccess = chart ? { chartMaterialAt, materialRadiance: materialRadianceOf } :
|
|
273
|
+
const chartAccess = light ? {} : chart ? { chartMaterialAt, materialRadiance: materialRadianceOf } :
|
|
242
274
|
materialScene ? { chartMaterialAt: callerMaterialAt, materialRadiance: materialRadianceOf } : {};
|
|
275
|
+
// #581: an image-plane print in node values at a frame point (the surround outside it), for the tabulated expected signal.
|
|
276
|
+
const imageAccess = imageScene ? { imageSignalScene: (wavelengths, perNode) => imageScene.signalScene(wavelengths, perNode, Float64Array.from(wavelengths, (nm) => irradianceScale * shapeAt(nm) / Math.PI), radianceAt, scale, half, placed.surroundReflectance) } : {};
|
|
243
277
|
// #394: evaluateRadiance's own plane-point arithmetic; radianceAt is then materialRadianceOf(material, nm) for both scene forms.
|
|
244
|
-
const materialAtPoint = chart ? chartMaterialAt : materialScene ? callerMaterialAt : undefined;
|
|
278
|
+
const materialAtPoint = light ? undefined : chart ? chartMaterialAt : materialScene ? callerMaterialAt : undefined;
|
|
245
279
|
const materialOfDirection = materialAtPoint === undefined ? undefined : (o) => {
|
|
246
280
|
const lz = -o.z;
|
|
247
281
|
if (!(lz > 0))
|
|
@@ -249,8 +283,9 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
249
283
|
const t = distance / lz;
|
|
250
284
|
return materialAtPoint(-o.x * t / scale, -o.y * t / scale);
|
|
251
285
|
};
|
|
286
|
+
const radiance = light ? litRadianceAt : radianceAt;
|
|
252
287
|
const provider = {
|
|
253
|
-
sceneId, providerProfileId, radianceAt, ...chartAccess,
|
|
288
|
+
sceneId, providerProfileId, radianceAt: radiance, ...chartAccess, ...imageAccess,
|
|
254
289
|
evaluateRadiance(request) {
|
|
255
290
|
const target = request.target;
|
|
256
291
|
if (target.kind !== "environment-direction")
|
|
@@ -262,14 +297,14 @@ export function buildPlanarSceneRadianceProvider(input) {
|
|
|
262
297
|
throw new InvalidConfigurationError("Planar scene query does not point toward the scene plane.");
|
|
263
298
|
}
|
|
264
299
|
const t = distance / lz;
|
|
265
|
-
return result(request,
|
|
300
|
+
return result(request, radiance(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers));
|
|
266
301
|
},
|
|
267
302
|
evaluateApertureRadiance(request, ray) {
|
|
268
303
|
const d = ray.directionUnitVector, origin = ray.originM;
|
|
269
304
|
if (!(d.z > 0))
|
|
270
305
|
throw new InvalidConfigurationError("Planar scene pupil ray does not point toward the scene plane.");
|
|
271
306
|
const t = (distance - origin.z) / d.z;
|
|
272
|
-
return result(request,
|
|
307
|
+
return result(request, radiance((origin.x + d.x * t) / scale, (origin.y + d.y * t) / scale, request.wavelengthNanometers));
|
|
273
308
|
}
|
|
274
309
|
};
|
|
275
310
|
if (materialOfDirection)
|