@photivra/engine 2.13.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -7
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +2 -0
  6. package/dist/camera/lens-projection.js +2 -1
  7. package/dist/camera/lens-projection.js.map +1 -1
  8. package/dist/capture/dng-import.js +1 -1
  9. package/dist/capture/dng-import.js.map +1 -1
  10. package/dist/capture/host-yield-cadence.js +1 -1
  11. package/dist/capture/host-yield-cadence.js.map +1 -1
  12. package/dist/capture/native-environment-optical-stage.js +1 -1
  13. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  14. package/dist/capture/native-raw.js +1 -1
  15. package/dist/capture/native-raw.js.map +1 -1
  16. package/dist/capture/packed-sensor-raw.js +3 -3
  17. package/dist/capture/packed-sensor-raw.js.map +1 -1
  18. package/dist/capture/sensor-raw-producer.js +1 -1
  19. package/dist/capture/sensor-raw-producer.js.map +1 -1
  20. package/dist/color/capture-color.js +3 -3
  21. package/dist/color/capture-color.js.map +1 -1
  22. package/dist/color/chromatic-adaptation.js +2 -2
  23. package/dist/color/chromatic-adaptation.js.map +1 -1
  24. package/dist/color/color-matching-functions.js +1 -1
  25. package/dist/color/color-matching-functions.js.map +1 -1
  26. package/dist/color/colorimetry-math.js +2 -2
  27. package/dist/color/colorimetry-math.js.map +1 -1
  28. package/dist/color/illuminants.js +3 -3
  29. package/dist/color/illuminants.js.map +1 -1
  30. package/dist/color/rgb-color-spaces.js +1 -1
  31. package/dist/color/rgb-color-spaces.js.map +1 -1
  32. package/dist/color/sensor-color-development.js +1 -1
  33. package/dist/color/sensor-color-development.js.map +1 -1
  34. package/dist/core/record-validation.js +3 -3
  35. package/dist/core/record-validation.js.map +1 -1
  36. package/dist/core/version.d.ts +1 -1
  37. package/dist/core/version.js +1 -1
  38. package/dist/core/version.js.map +1 -1
  39. package/dist/equipment/generic-prime-lenses.js +3 -3
  40. package/dist/equipment/generic-prime-lenses.js.map +1 -1
  41. package/dist/equipment/generic-tier-assets.d.ts +23 -1
  42. package/dist/equipment/generic-tier-assets.js +58 -30
  43. package/dist/equipment/generic-tier-assets.js.map +1 -1
  44. package/dist/equipment/generic-tier-presets.d.ts +6 -3
  45. package/dist/equipment/generic-tier-presets.js +26 -13
  46. package/dist/equipment/generic-tier-presets.js.map +1 -1
  47. package/dist/exposure/auto-iso-slowest-shutter.js +2 -1
  48. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  49. package/dist/exposure/flash-ttl.js +1 -1
  50. package/dist/exposure/flash-ttl.js.map +1 -1
  51. package/dist/exposure/flash.js +1 -1
  52. package/dist/exposure/flash.js.map +1 -1
  53. package/dist/facade/chart-material-layers.js +2 -2
  54. package/dist/facade/chart-material-layers.js.map +1 -1
  55. package/dist/facade/chart-radiance-batch.js +1 -1
  56. package/dist/facade/chart-radiance-batch.js.map +1 -1
  57. package/dist/facade/chart-scenes.d.ts +45 -8
  58. package/dist/facade/chart-scenes.js +101 -44
  59. package/dist/facade/chart-scenes.js.map +1 -1
  60. package/dist/facade/color-patches.d.ts +129 -0
  61. package/dist/facade/color-patches.js +209 -0
  62. package/dist/facade/color-patches.js.map +1 -0
  63. package/dist/facade/display-view-gpu.js +1 -1
  64. package/dist/facade/display-view-gpu.js.map +1 -1
  65. package/dist/facade/scene-radiance.js +2 -2
  66. package/dist/facade/scene-radiance.js.map +1 -1
  67. package/dist/facade/simulate-capture.d.ts +47 -3
  68. package/dist/facade/simulate-capture.js +71 -22
  69. package/dist/facade/simulate-capture.js.map +1 -1
  70. package/dist/facade/simulate-display-view.js +2 -2
  71. package/dist/facade/simulate-display-view.js.map +1 -1
  72. package/dist/gpu/chart-composite-gpu.js +2 -2
  73. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  74. package/dist/gpu/display-view-composite-material-module.js +1 -1
  75. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  76. package/dist/gpu/image-kernel-execution.js +2 -2
  77. package/dist/gpu/image-kernel-execution.js.map +1 -1
  78. package/dist/gpu/temporal-sum-gpu.js +23 -23
  79. package/dist/gpu/webgpu-runtime.js +1 -1
  80. package/dist/gpu/webgpu-runtime.js.map +1 -1
  81. package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
  82. package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
  83. package/dist/gpu/wgsl-chart-kernel.js +117 -117
  84. package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
  85. package/dist/gpu/wgsl-display-view-quality-kernel.js +505 -505
  86. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  87. package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
  88. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  89. package/dist/gpu/wgsl-kernels.js +203 -203
  90. package/dist/gpu/wgsl-kernels.js.map +1 -1
  91. package/dist/gpu/wgsl-sensor-kernel.js +302 -302
  92. package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
  93. package/dist/index.d.ts +5 -3
  94. package/dist/index.js +4 -2
  95. package/dist/index.js.map +1 -1
  96. package/dist/motion/temporal-exposure-sum.js +1 -1
  97. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  98. package/dist/optics/diffraction-psf-profile.js +4 -4
  99. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  100. package/dist/optics/front-of-lens-filter.js +1 -1
  101. package/dist/optics/front-of-lens-filter.js.map +1 -1
  102. package/dist/optics/irradiance-convolution.js +1 -1
  103. package/dist/optics/irradiance-convolution.js.map +1 -1
  104. package/dist/output/native-capture-sdr.js +1 -1
  105. package/dist/output/native-capture-sdr.js.map +1 -1
  106. package/dist/output/png-encoder.js +2 -2
  107. package/dist/output/png-encoder.js.map +1 -1
  108. package/dist/output/print-jpeg.js +1 -1
  109. package/dist/output/print-jpeg.js.map +1 -1
  110. package/dist/sensor/fixed-pattern-noise.js +1 -1
  111. package/dist/sensor/fixed-pattern-noise.js.map +1 -1
  112. package/dist/sensor/readout-exposure-linkage.js +1 -1
  113. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  114. package/docs/API_REFERENCE.md +29 -1
  115. package/docs/QUICK_START.md +40 -5
  116. package/docs/USAGE.md +35 -1
  117. package/docs/api/core-version.md +1 -1
  118. package/docs/api/equipment-generic-tier-assets.md +41 -1
  119. package/docs/api/equipment-generic-tier-presets.md +11 -3
  120. package/docs/api/exports.json +107 -2
  121. package/docs/api/facade-chart-scenes.md +36 -5
  122. package/docs/api/facade-color-patches.md +150 -0
  123. package/docs/api/facade-simulate-capture.md +52 -2
  124. package/llms.txt +1 -0
  125. package/package.json +2 -2
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Photivra color patches (2.14): a built-in set of 30 reflectance spectra designed by Photivra for color test charts.
3
+ *
4
+ * Every spectrum is an engine-defined analytic function of wavelength, not measured or copied data: a constant plus a
5
+ * sum of Gaussian bumps and logistic steps,
6
+ *
7
+ * R(λ) = min(1, max(0, b + Σₖ aₖ·φₖ(λ))),
8
+ * gaussian: φ(λ) = exp(−½·((λ − μ)/σ)²)
9
+ * logistic-rise: φ(λ) = 1 / (1 + exp(−(λ − λ₀)/s))
10
+ * logistic-fall: φ(λ) = 1 − 1 / (1 + exp(−(λ − λ₀)/s)),
11
+ *
12
+ * with λ in nanometers on 360–830 nm (the CIE 1931 observer's domain). The parameters were fitted once, by
13
+ * Photivra, to Photivra's own target colors (docs/COLOR_PATCHES.md), then rounded. Every spectrum lies in
14
+ * [0.03, 0.901] on its whole domain, so the clamp to [0, 1] never acts; it only states the physical bound. The
15
+ * patches are non-fluorescent (pure reflectances) and the neutral patches are spectrally flat.
16
+ *
17
+ * No third-party chart is copied: the set uses no ColorChecker or other commercial target's patch data, names, order,
18
+ * layout or measured spectra. The names are Photivra's own, and every published color is checked to differ from openly
19
+ * published third-party colors (`scripts/check-color-patch-distinctness.mjs`). The reference CIELAB and sRGB values are computed from these spectra with the CIE 1931
20
+ * 2° observer and CIE illuminant D65 (`scripts/compute-color-patch-reference.mjs`).
21
+ *
22
+ * Captures evaluate the exponentials with `Math.exp`, like the engine's other continuous chart formulas. The display
23
+ * view passes the portable `portableExp` instead (`resolvePortableChartScene`), so its chart presets' patch
24
+ * reflectances have the same bits in every JavaScript engine (docs/DISPLAY_VIEW.md, "Same bytes in every engine").
25
+ */
26
+ import { type CalculationResult } from "../core/calculation-result.js";
27
+ import type { PreparedColorimetricSpectrum } from "../color/color-matching-functions.js";
28
+ /** Identity of the built-in patch set. */
29
+ export declare const COLOR_PATCH_SET_ID = "photivra-color-patches";
30
+ /** Version of the patch set's spectra and reference values; a change to any spectrum changes it. */
31
+ export declare const COLOR_PATCH_SET_VERSION = "1.0.0";
32
+ /** Wavelengths on which every patch spectrum is defined, nanometers, inclusive. */
33
+ export declare const COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS: Readonly<{
34
+ minimum: 360;
35
+ maximum: 830;
36
+ }>;
37
+ /** One term of a patch spectrum (see the module formula). */
38
+ export type ColorPatchSpectralTerm = {
39
+ kind: "gaussian";
40
+ amplitude: number;
41
+ centerNanometers: number;
42
+ standardDeviationNanometers: number;
43
+ } | {
44
+ kind: "logistic-rise" | "logistic-fall";
45
+ amplitude: number;
46
+ midpointNanometers: number;
47
+ scaleNanometers: number;
48
+ };
49
+ /** Groups of the patch set, in chart order. */
50
+ export type ColorPatchGroup = "neutral" | "strong" | "soft" | "skin" | "memory" | "gamut-edge";
51
+ /** The ids of the built-in patches, in the order of the `color-patches` chart (row-major, six per row). */
52
+ export declare const COLOR_PATCH_IDS: readonly ["neutral-1", "neutral-2", "neutral-3", "neutral-4", "neutral-5", "neutral-6", "red", "yellow", "green", "cyan", "blue", "magenta", "red-soft", "yellow-soft", "green-soft", "cyan-soft", "blue-soft", "magenta-soft", "skin-pale", "skin-medium", "skin-tan", "skin-deep", "sky-blue", "foliage-green", "brick-red", "sand", "teal", "lavender", "deep-orange", "deep-violet"];
53
+ export type ColorPatchId = (typeof COLOR_PATCH_IDS)[number];
54
+ /**
55
+ * One built-in patch: its id, short code, name, reason for being on the chart, spectrum definition and reference
56
+ * colorimetry.
57
+ */
58
+ export interface ColorPatch {
59
+ /** Stable API id, e.g. `"skin-medium"`. */
60
+ patchId: ColorPatchId;
61
+ /**
62
+ * Stable short code for labels and printed charts: `PV-` and a group letter and number, e.g. `"PV-N4"`. The letters are
63
+ * N neutral, S strong, T soft (tint), P skin (portrait), M memory and G gamut-edge, numbered in chart order.
64
+ */
65
+ code: string;
66
+ /** Photivra's own descriptive name, in US English, e.g. `"Studio Gray 4"`; not a third-party color name. */
67
+ name: string;
68
+ group: ColorPatchGroup;
69
+ /** One sentence: why the patch is on the chart. */
70
+ reason: string;
71
+ /** The constant b of the formula. */
72
+ base: number;
73
+ terms: readonly ColorPatchSpectralTerm[];
74
+ /**
75
+ * Reference colorimetry of the reflectance (not of a rendered capture): CIELAB under CIE illuminant D65 with the CIE
76
+ * 1931 2° observer, the CIE tables at 1 nm over 360–830 nm, white = the perfect diffuser under D65 (Y = 100); and the
77
+ * sRGB color of that XYZ (CSS Color 4 matrix, after XYZ scaling of the D65 table white to the sRGB white),
78
+ * 8-bit hex. Rounded for publication: L*, a*, b* to 0.01.
79
+ */
80
+ reference: {
81
+ cielabD65: readonly [number, number, number];
82
+ srgbHex: string;
83
+ inSrgbGamut: boolean;
84
+ };
85
+ }
86
+ /** The built-in patch set (see {@link getColorPatchSet}). */
87
+ export interface ColorPatchSet {
88
+ setId: typeof COLOR_PATCH_SET_ID;
89
+ version: typeof COLOR_PATCH_SET_VERSION;
90
+ wavelengthRangeNanometers: {
91
+ minimum: number;
92
+ maximum: number;
93
+ };
94
+ /** Patches per row of the `color-patches` chart's default layout. */
95
+ columns: number;
96
+ patches: readonly ColorPatch[];
97
+ }
98
+ /**
99
+ * The built-in Photivra color-patch set: every patch's id, code, name, group, reason, spectrum parameters and reference CIELAB (D65,
100
+ * CIE 1931 2°) and sRGB values. A static, deeply frozen engine contract. Pass a patch to a chart as
101
+ * `{ kind: "color-patch", patchId }` wherever a chart reflectance is accepted, or draw the whole set with the
102
+ * `color-patches` chart preset.
103
+ */
104
+ export declare function getColorPatchSet(): ColorPatchSet;
105
+ /** Internal: the patch with this id, or undefined. */
106
+ export declare function findColorPatch(patchId: unknown): ColorPatch | undefined;
107
+ /** An exponential: `Math.exp`, or the display view's portable one. */
108
+ type Exponential = (x: number) => number;
109
+ /** Internal (for tests): the formula before the clamp. */
110
+ export declare function evaluateUnclampedColorPatchFormula(patchId: ColorPatchId, wavelengthNanometers: number, exp?: Exponential): number;
111
+ /**
112
+ * Internal: a prepared reflectance spectrum of a built-in patch. Values are memoized per wavelength (the formula is pure),
113
+ * so a capture's per-query lookups cost one map access instead of the exponentials.
114
+ */
115
+ export declare function prepareColorPatchSpectrum(patch: ColorPatch, exp?: Exponential): PreparedColorimetricSpectrum;
116
+ /** Input of {@link evaluateColorPatchReflectance}. */
117
+ export interface EvaluateColorPatchReflectanceInput {
118
+ patchId: ColorPatchId;
119
+ /** Vacuum wavelength, nanometers, in 360–830. */
120
+ wavelengthNanometers: number;
121
+ }
122
+ /**
123
+ * The reflectance (dimensionless, in [0, 1]) of a built-in color patch at one wavelength, from its analytic formula.
124
+ * Labeled `approximation`: the spectra are Photivra's own smooth designs, not measurements of a physical sample.
125
+ */
126
+ export declare function evaluateColorPatchReflectance(input: EvaluateColorPatchReflectanceInput): CalculationResult<{
127
+ reflectance: number;
128
+ }>;
129
+ export {};
@@ -0,0 +1,209 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Photivra color patches (2.14): a built-in set of 30 reflectance spectra designed by Photivra for color test charts.
4
+ *
5
+ * Every spectrum is an engine-defined analytic function of wavelength, not measured or copied data: a constant plus a
6
+ * sum of Gaussian bumps and logistic steps,
7
+ *
8
+ * R(λ) = min(1, max(0, b + Σₖ aₖ·φₖ(λ))),
9
+ * gaussian: φ(λ) = exp(−½·((λ − μ)/σ)²)
10
+ * logistic-rise: φ(λ) = 1 / (1 + exp(−(λ − λ₀)/s))
11
+ * logistic-fall: φ(λ) = 1 − 1 / (1 + exp(−(λ − λ₀)/s)),
12
+ *
13
+ * with λ in nanometers on 360–830 nm (the CIE 1931 observer's domain). The parameters were fitted once, by
14
+ * Photivra, to Photivra's own target colors (docs/COLOR_PATCHES.md), then rounded. Every spectrum lies in
15
+ * [0.03, 0.901] on its whole domain, so the clamp to [0, 1] never acts; it only states the physical bound. The
16
+ * patches are non-fluorescent (pure reflectances) and the neutral patches are spectrally flat.
17
+ *
18
+ * No third-party chart is copied: the set uses no ColorChecker or other commercial target's patch data, names, order,
19
+ * layout or measured spectra. The names are Photivra's own, and every published color is checked to differ from openly
20
+ * published third-party colors (`scripts/check-color-patch-distinctness.mjs`). The reference CIELAB and sRGB values are computed from these spectra with the CIE 1931
21
+ * 2° observer and CIE illuminant D65 (`scripts/compute-color-patch-reference.mjs`).
22
+ *
23
+ * Captures evaluate the exponentials with `Math.exp`, like the engine's other continuous chart formulas. The display
24
+ * view passes the portable `portableExp` instead (`resolvePortableChartScene`), so its chart presets' patch
25
+ * reflectances have the same bits in every JavaScript engine (docs/DISPLAY_VIEW.md, "Same bytes in every engine").
26
+ */
27
+ import { approximationResult } from "../core/calculation-result.js";
28
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
29
+ import { InvalidScientificInputError } from "../core/validation.js";
30
+ import { requireInputKeys } from "../core/record-validation.js";
31
+ /** Identity of the built-in patch set. */
32
+ export const COLOR_PATCH_SET_ID = "photivra-color-patches";
33
+ /** Version of the patch set's spectra and reference values; a change to any spectrum changes it. */
34
+ export const COLOR_PATCH_SET_VERSION = "1.0.0";
35
+ /** Wavelengths on which every patch spectrum is defined, nanometers, inclusive. */
36
+ export const COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS = /* @__PURE__ */ Object.freeze({ minimum: 360, maximum: 830 });
37
+ /** The ids of the built-in patches, in the order of the `color-patches` chart (row-major, six per row). */
38
+ export const COLOR_PATCH_IDS = /* @__PURE__ */ Object.freeze([
39
+ "neutral-1", "neutral-2", "neutral-3", "neutral-4", "neutral-5", "neutral-6",
40
+ "red", "yellow", "green", "cyan", "blue", "magenta",
41
+ "red-soft", "yellow-soft", "green-soft", "cyan-soft", "blue-soft", "magenta-soft",
42
+ "skin-pale", "skin-medium", "skin-tan", "skin-deep", "sky-blue", "foliage-green",
43
+ "brick-red", "sand", "teal", "lavender", "deep-orange", "deep-violet"
44
+ ]);
45
+ const g = (amplitude, centerNanometers, standardDeviationNanometers) => ({ kind: "gaussian", amplitude, centerNanometers, standardDeviationNanometers });
46
+ const rise = (amplitude, midpointNanometers, scaleNanometers) => ({ kind: "logistic-rise", amplitude, midpointNanometers, scaleNanometers });
47
+ const fall = (amplitude, midpointNanometers, scaleNanometers) => ({ kind: "logistic-fall", amplitude, midpointNanometers, scaleNanometers });
48
+ /* Generated reference values: node scripts/compute-color-patch-reference.mjs --print (see docs/COLOR_PATCHES.md). */
49
+ const DEFINITIONS = [
50
+ ["neutral-1", "PV-N1", "Studio Gray 1", "neutral", 0.901, [], [96.04, 0, 0], "#F4F4F4",
51
+ "The white end of the flat gray ramp (90.1 %): the reference for white balance, exposure and highlight clipping."],
52
+ ["neutral-2", "PV-N2", "Studio Gray 2", "neutral", 0.585, [], [81.02, 0, 0], "#C9C9C9",
53
+ "A light flat gray about 15 L* below the white, for the upper tone curve and highlight roll-off."],
54
+ ["neutral-3", "PV-N3", "Studio Gray 3", "neutral", 0.354, [], [66.06, 0, 0], "#A1A1A1",
55
+ "A light-mid flat gray that continues the ramp in equal lightness steps, for tone-curve and gamma checks."],
56
+ ["neutral-4", "PV-N4", "Studio Gray 4", "neutral", 0.197, [], [51.5, 0, 0], "#7B7B7B",
57
+ "The mid-gray of the ramp, near an 18 % card's lightness, for exposure and neutral balance in the midtones."],
58
+ ["neutral-5", "PV-N5", "Studio Gray 5", "neutral", 0.093, [], [36.56, 0, 0], "#565656",
59
+ "A dark flat gray that shows shadow tone separation, noise and any color cast in the shadows."],
60
+ ["neutral-6", "PV-N6", "Studio Gray 6", "neutral", 0.035, [], [21.94, 0, 0], "#353535",
61
+ "The black end of the ramp (3.5 %), for black level, shadow noise and flare."],
62
+ ["red", "PV-S1", "Darkroom Red", "strong", 0.031, [rise(0.757, 611.6, 16.2)], [43.7, 54.15, 39.93], "#BF3428",
63
+ "A strong red, for saturated reds, which clip and shift in hue easily in a camera's red channel."],
64
+ ["yellow", "PV-S2", "Flashbulb Yellow", "strong", 0.071, [rise(0.602, 518.7, 13.2)], [78.87, -0.23, 73.26], "#E6C02A",
65
+ "A strong yellow, bright and high in chroma, that shows whether light saturated colors keep their hue."],
66
+ ["green", "PV-S3", "Hedgerow Green", "strong", 0.051, [g(0.317, 535.8, 36.9)], [57.08, -47.07, 33.2], "#359C4C",
67
+ "A strong mid green, for the green primary, where camera and display gamuts differ most."],
68
+ ["cyan", "PV-S4", "Harbor Cyan", "strong", 0.096, [g(0.339, 482.5, 60.4)], [58.4, -21.56, -17.23], "#3C98AA",
69
+ "A strong cyan between the green and blue primaries, a region where overlapping camera responses mix the channels."],
70
+ ["blue", "PV-S5", "Cyanotype Blue", "strong", 0.064, [g(0.336, 460.7, 24.7)], [35.27, 19.89, -46.48], "#3C4C9E",
71
+ "A strong dark blue, for the blue channel, its noise and the shift of saturated blues toward purple."],
72
+ ["magenta", "PV-S6", "Filter Magenta", "strong", 0.05, [g(0.258, 440, 32.8), rise(0.549, 605, 15)], [46.11, 55, -18.91], "#B4408E",
73
+ "A strong magenta, a non-spectral hue made of both ends of the spectrum, so it tests the red and blue channels together."],
74
+ ["red-soft", "PV-T1", "Faded Print Red", "soft", 0.196, [rise(0.472, 601.8, 13)], [59.63, 29.17, 14.19], "#C77B78",
75
+ "A soft red at lower chroma and higher lightness, for gentle reds that a color matrix should not oversaturate."],
76
+ ["yellow-soft", "PV-T2", "Window Glow Yellow", "soft", 0.246, [rise(0.414, 509, 17.6)], [81.15, -2.88, 36.43], "#DCC985",
77
+ "A soft light yellow, for pale warm tones whose hue drifts easily toward green or orange."],
78
+ ["green-soft", "PV-T3", "Misty Hedgerow Green", "soft", 0.184, [g(0.251, 535.7, 47.4)], [66.82, -24.11, 16.11], "#7FAE85",
79
+ "A soft green, for the muted greens that fill much of a natural scene."],
80
+ ["cyan-soft", "PV-T4", "Shallow Water Cyan", "soft", 0.246, [g(0.339, 487.4, 61.9)], [71.51, -16.99, -10.71], "#7DB9C2",
81
+ "A soft cyan, for pale blue-greens such as water and glass, where small hue errors are easy to see."],
82
+ ["blue-soft", "PV-T5", "Distant Hill Blue", "soft", 0.207, [g(0.319, 457.6, 34.7)], [56.48, 7.42, -30.88], "#7486BD",
83
+ "A soft blue, for the hazy blues of distance and open shade that white balance acts on."],
84
+ ["magenta-soft", "PV-T6", "Faded Filter Magenta", "soft", 0.196, [g(0.231, 440, 33.1), rise(0.256, 575.8, 15)], [61.36, 26.09, -10.98], "#B984A8",
85
+ "A soft magenta, for pinks and mauves that lie between the red and blue channels."],
86
+ ["skin-pale", "PV-P1", "Fair Portrait Tone", "skin", 0.408, [rise(0.394, 562.5, 31.7)], [81.35, 9.48, 17.48], "#E8C3AA",
87
+ "A fair skin tone, the lightest of four, for portrait color, where viewers notice small errors."],
88
+ ["skin-medium", "PV-P2", "Medium Portrait Tone", "skin", 0.213, [rise(0.376, 570.3, 30.4)], [67.25, 13.65, 22.01], "#C99A7D",
89
+ "A medium skin tone, the second of four that span a range of skin lightness at similar hue."],
90
+ ["skin-tan", "PV-P3", "Tan Portrait Tone", "skin", 0.084, [rise(0.295, 573.7, 31.3)], [52.01, 15.71, 26.72], "#A3714F",
91
+ "A tan skin tone, the third of four, for warm midtone skin under different lights."],
92
+ ["skin-deep", "PV-P4", "Deep Portrait Tone", "skin", 0.034, [rise(0.133, 590.3, 32.4)], [33, 13.57, 18.02], "#6A4532",
93
+ "A deep skin tone, the darkest of four, for skin color and detail in the darker tones of a portrait."],
94
+ ["sky-blue", "PV-M1", "Midday Sky", "memory", 0.18, [fall(0.455, 507.2, 29.7)], [60.05, 0.53, -33.31], "#6893CB",
95
+ "A clear-sky blue, a memory color that viewers judge against what they remember of the sky."],
96
+ ["foliage-green", "PV-M2", "Canopy Green", "memory", 0.034, [g(0.139, 555, 30.7), rise(0.411, 716.8, 12)], [40.81, -22.4, 30.31], "#4B692C",
97
+ "A leaf green with the steep rise of leaf reflectance at the red edge, a memory color that also exposes infrared leakage."],
98
+ ["brick-red", "PV-M3", "Warehouse Brick", "memory", 0.05, [rise(0.308, 592.7, 16.2)], [41.63, 32.81, 25.53], "#9C4A3A",
99
+ "A dull brick red, a common building color between the saturated reds and the skin tones."],
100
+ ["sand", "PV-M4", "Low Tide Sand", "memory", 0.272, [rise(0.371, 504.9, 37.6)], [79.14, 0.48, 22.19], "#D5C29B",
101
+ "A pale sand, a light warm near-neutral that shows small casts a gray patch would hide."],
102
+ ["teal", "PV-M5", "Estuary Teal", "memory", 0.085, [g(0.197, 498.2, 42)], [48.43, -24.88, -5.64], "#2E7F7C",
103
+ "A dark blue-green between the green and cyan hues, for water and shaded foliage in that region."],
104
+ ["lavender", "PV-M6", "Wildflower Lilac", "memory", 0.331, [g(0.298, 445, 30.4), rise(0.137, 623.4, 20)], [66.69, 14.8, -22.7], "#A99BCB",
105
+ "A pale blue-violet, a flower color that cameras often render too blue."],
106
+ ["deep-orange", "PV-G1", "Sunset Flare Orange", "gamut-edge", 0.03, [rise(0.74, 591.3, 16.7)], [53.19, 52.38, 56.53], "#DC511A",
107
+ "A deep orange close to the sRGB gamut boundary, for gamut mapping and clipping of saturated warm colors."],
108
+ ["deep-violet", "PV-G2", "Blacklight Violet", "gamut-edge", 0.031, [g(0.37, 441.6, 25.6), rise(0.097, 640, 20)], [26.42, 48.9, -59.69], "#4C1E9C",
109
+ "A deep violet close to the sRGB gamut boundary, for gamut mapping and the camera's response at short wavelengths."]
110
+ ];
111
+ function deepFreezePatch(patch) {
112
+ for (const term of patch.terms)
113
+ Object.freeze(term);
114
+ Object.freeze(patch.terms);
115
+ Object.freeze(patch.reference.cielabD65);
116
+ Object.freeze(patch.reference);
117
+ return Object.freeze(patch);
118
+ }
119
+ const PATCHES = Object.freeze(DEFINITIONS.map(([patchId, code, name, group, base, terms, lab, hex, reason]) => deepFreezePatch({
120
+ patchId, code, name, group, reason, base, terms, reference: { cielabD65: lab, srgbHex: hex, inSrgbGamut: true }
121
+ })));
122
+ const BY_ID = new Map(PATCHES.map((p) => [p.patchId, p]));
123
+ const SET = /* @__PURE__ */ Object.freeze({ setId: COLOR_PATCH_SET_ID, version: COLOR_PATCH_SET_VERSION,
124
+ wavelengthRangeNanometers: COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS, columns: 6, patches: PATCHES });
125
+ /**
126
+ * The built-in Photivra color-patch set: every patch's id, code, name, group, reason, spectrum parameters and reference CIELAB (D65,
127
+ * CIE 1931 2°) and sRGB values. A static, deeply frozen engine contract. Pass a patch to a chart as
128
+ * `{ kind: "color-patch", patchId }` wherever a chart reflectance is accepted, or draw the whole set with the
129
+ * `color-patches` chart preset.
130
+ */
131
+ export function getColorPatchSet() {
132
+ return SET;
133
+ }
134
+ /** Internal: the patch with this id, or undefined. */
135
+ export function findColorPatch(patchId) {
136
+ return typeof patchId === "string" ? BY_ID.get(patchId) : undefined;
137
+ }
138
+ /** The unclamped formula value b + Σ aₖφₖ(λ) (the clamp never acts on the built-in set; a test checks it). */
139
+ function formula(patch, nm, exp) {
140
+ let value = patch.base;
141
+ for (const term of patch.terms) {
142
+ if (term.kind === "gaussian") {
143
+ const z = (nm - term.centerNanometers) / term.standardDeviationNanometers;
144
+ value += term.amplitude * exp(-0.5 * z * z);
145
+ }
146
+ else {
147
+ const step = 1 / (1 + exp(-(nm - term.midpointNanometers) / term.scaleNanometers));
148
+ value += term.amplitude * (term.kind === "logistic-rise" ? step : 1 - step);
149
+ }
150
+ }
151
+ return value;
152
+ }
153
+ function reflectanceOf(patch, nm, exp = Math.exp) {
154
+ return Math.min(1, Math.max(0, formula(patch, nm, exp)));
155
+ }
156
+ /** Internal (for tests): the formula before the clamp. */
157
+ export function evaluateUnclampedColorPatchFormula(patchId, wavelengthNanometers, exp = Math.exp) {
158
+ return formula(BY_ID.get(patchId), wavelengthNanometers, exp);
159
+ }
160
+ /** Prepared spectra per exponential (Math.exp for captures, the portable one for the display view), then per patch. */
161
+ const PREPARED = new Map();
162
+ /** Most wavelengths whose values a prepared patch spectrum keeps (a capture uses a few dozen quadrature nodes). */
163
+ const PREPARED_CACHE_LIMIT = 4096;
164
+ /**
165
+ * Internal: a prepared reflectance spectrum of a built-in patch. Values are memoized per wavelength (the formula is pure),
166
+ * so a capture's per-query lookups cost one map access instead of the exponentials.
167
+ */
168
+ export function prepareColorPatchSpectrum(patch, exp = Math.exp) {
169
+ let byPatch = PREPARED.get(exp);
170
+ if (!byPatch)
171
+ PREPARED.set(exp, byPatch = new Map());
172
+ const existing = byPatch.get(patch.patchId);
173
+ if (existing)
174
+ return existing;
175
+ const range = COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS, cache = new Map();
176
+ const prepared = Object.freeze({
177
+ wavelengthRangeNanometers: range,
178
+ evaluate(nm) {
179
+ const hit = cache.get(nm);
180
+ if (hit !== undefined)
181
+ return hit;
182
+ if (typeof nm !== "number" || !(nm >= range.minimum && nm <= range.maximum)) {
183
+ throw new InvalidScientificInputError("Color patch " + patch.patchId + " is defined on " + range.minimum + "–" + range.maximum + " nm.");
184
+ }
185
+ const value = reflectanceOf(patch, nm, exp);
186
+ if (cache.size < PREPARED_CACHE_LIMIT)
187
+ cache.set(nm, value);
188
+ return value;
189
+ }
190
+ });
191
+ byPatch.set(patch.patchId, prepared);
192
+ return prepared;
193
+ }
194
+ /**
195
+ * The reflectance (dimensionless, in [0, 1]) of a built-in color patch at one wavelength, from its analytic formula.
196
+ * Labeled `approximation`: the spectra are Photivra's own smooth designs, not measurements of a physical sample.
197
+ */
198
+ export function evaluateColorPatchReflectance(input) {
199
+ requireInputKeys(input, "evaluateColorPatchReflectance", ["patchId", "wavelengthNanometers"]);
200
+ const patch = findColorPatch(input.patchId);
201
+ if (!patch)
202
+ throw new InvalidConfigurationError("patchId must be one of the built-in color patches (COLOR_PATCH_IDS).");
203
+ const nm = input.wavelengthNanometers, range = COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS;
204
+ if (typeof nm !== "number" || !Number.isFinite(nm) || nm < range.minimum || nm > range.maximum) {
205
+ throw new InvalidScientificInputError("wavelengthNanometers must be a finite wavelength from 360 to 830 nm.");
206
+ }
207
+ return approximationResult({ reflectance: reflectanceOf(patch, nm) }, "photivra-color-patch-spectrum", COLOR_PATCH_SET_VERSION, ["Photivra's own analytic design (a constant plus Gaussian and logistic terms), not a measured sample", "Non-fluorescent Lambertian reflectance"]);
208
+ }
209
+ //# sourceMappingURL=color-patches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-patches.js","sourceRoot":"","sources":["../../src/facade/color-patches.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Photivra color patches (2.14): a built-in set of 30 reflectance spectra designed by Photivra for color test charts.\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 bumps and logistic steps,\n *\n * R(λ) = min(1, max(0, b + Σₖ aₖ·φₖ(λ))),\n * gaussian: φ(λ) = exp(−½·((λ − μ)/σ)²)\n * logistic-rise: φ(λ) = 1 / (1 + exp(−(λ − λ₀)/s))\n * logistic-fall: φ(λ) = 1 − 1 / (1 + exp(−(λ − λ₀)/s)),\n *\n * with λ in nanometers on 360–830 nm (the CIE 1931 observer's domain). The parameters were fitted once, by\n * Photivra, to Photivra's own target colors (docs/COLOR_PATCHES.md), then rounded. Every spectrum lies in\n * [0.03, 0.901] on its whole domain, so the clamp to [0, 1] never acts; it only states the physical bound. The\n * patches are non-fluorescent (pure reflectances) and the neutral patches are spectrally flat.\n *\n * No third-party chart is copied: the set uses no ColorChecker or other commercial target's patch data, names, order,\n * layout or measured spectra. The names are Photivra's own, and every published color is checked to differ from openly\n * published third-party colors (`scripts/check-color-patch-distinctness.mjs`). The reference CIELAB and sRGB values are computed from these spectra with the CIE 1931\n * 2° observer and CIE illuminant D65 (`scripts/compute-color-patch-reference.mjs`).\n *\n * Captures evaluate the exponentials with `Math.exp`, like the engine's other continuous chart formulas. The display\n * view passes the portable `portableExp` instead (`resolvePortableChartScene`), so its chart presets' patch\n * reflectances have the same bits in every JavaScript engine (docs/DISPLAY_VIEW.md, \"Same bytes in every engine\").\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport type { PreparedColorimetricSpectrum } from \"../color/color-matching-functions.js\";\n\n/** Identity of the built-in patch set. */\nexport const COLOR_PATCH_SET_ID = \"photivra-color-patches\";\n/** Version of the patch set's spectra and reference values; a change to any spectrum changes it. */\nexport const COLOR_PATCH_SET_VERSION = \"1.0.0\";\n/** Wavelengths on which every patch spectrum is defined, nanometers, inclusive. */\nexport const COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS = /* @__PURE__ */ Object.freeze({ minimum: 360, maximum: 830 });\n\n/** One term of a patch spectrum (see the module formula). */\nexport type ColorPatchSpectralTerm =\n | { kind: \"gaussian\"; amplitude: number; centerNanometers: number; standardDeviationNanometers: number }\n | { kind: \"logistic-rise\" | \"logistic-fall\"; amplitude: number; midpointNanometers: number; scaleNanometers: number };\n\n/** Groups of the patch set, in chart order. */\nexport type ColorPatchGroup = \"neutral\" | \"strong\" | \"soft\" | \"skin\" | \"memory\" | \"gamut-edge\";\n\n/** The ids of the built-in patches, in the order of the `color-patches` chart (row-major, six per row). */\nexport const COLOR_PATCH_IDS = /* @__PURE__ */ Object.freeze([\n \"neutral-1\", \"neutral-2\", \"neutral-3\", \"neutral-4\", \"neutral-5\", \"neutral-6\",\n \"red\", \"yellow\", \"green\", \"cyan\", \"blue\", \"magenta\",\n \"red-soft\", \"yellow-soft\", \"green-soft\", \"cyan-soft\", \"blue-soft\", \"magenta-soft\",\n \"skin-pale\", \"skin-medium\", \"skin-tan\", \"skin-deep\", \"sky-blue\", \"foliage-green\",\n \"brick-red\", \"sand\", \"teal\", \"lavender\", \"deep-orange\", \"deep-violet\"\n] as const);\nexport type ColorPatchId = (typeof COLOR_PATCH_IDS)[number];\n\n/**\n * One built-in patch: its id, short code, name, reason for being on the chart, spectrum definition and reference\n * colorimetry.\n */\nexport interface ColorPatch {\n /** Stable API id, e.g. `\"skin-medium\"`. */\n patchId: ColorPatchId;\n /**\n * Stable short code for labels and printed charts: `PV-` and a group letter and number, e.g. `\"PV-N4\"`. The letters are\n * N neutral, S strong, T soft (tint), P skin (portrait), M memory and G gamut-edge, numbered in chart order.\n */\n code: string;\n /** Photivra's own descriptive name, in US English, e.g. `\"Studio Gray 4\"`; not a third-party color name. */\n name: string;\n group: ColorPatchGroup;\n /** One sentence: why the patch is on the chart. */\n reason: string;\n /** The constant b of the formula. */\n base: number;\n terms: readonly ColorPatchSpectralTerm[];\n /**\n * Reference colorimetry of the reflectance (not of a rendered capture): CIELAB under CIE illuminant D65 with the CIE\n * 1931 2° observer, the CIE tables at 1 nm over 360–830 nm, white = the perfect diffuser under D65 (Y = 100); and the\n * sRGB color of that XYZ (CSS Color 4 matrix, after XYZ scaling of the D65 table white to the sRGB white),\n * 8-bit hex. Rounded for publication: L*, a*, b* to 0.01.\n */\n reference: { cielabD65: readonly [number, number, number]; srgbHex: string; inSrgbGamut: boolean };\n}\n\n/** The built-in patch set (see {@link getColorPatchSet}). */\nexport interface ColorPatchSet {\n setId: typeof COLOR_PATCH_SET_ID;\n version: typeof COLOR_PATCH_SET_VERSION;\n wavelengthRangeNanometers: { minimum: number; maximum: number };\n /** Patches per row of the `color-patches` chart's default layout. */\n columns: number;\n patches: readonly ColorPatch[];\n}\n\nconst g = (amplitude: number, centerNanometers: number, standardDeviationNanometers: number): ColorPatchSpectralTerm =>\n ({ kind: \"gaussian\", amplitude, centerNanometers, standardDeviationNanometers });\nconst rise = (amplitude: number, midpointNanometers: number, scaleNanometers: number): ColorPatchSpectralTerm =>\n ({ kind: \"logistic-rise\", amplitude, midpointNanometers, scaleNanometers });\nconst fall = (amplitude: number, midpointNanometers: number, scaleNanometers: number): ColorPatchSpectralTerm =>\n ({ kind: \"logistic-fall\", amplitude, midpointNanometers, scaleNanometers });\ntype Definition = [ColorPatchId, string, string, ColorPatchGroup, number, ColorPatchSpectralTerm[], [number, number, number], string, string];\n/* Generated reference values: node scripts/compute-color-patch-reference.mjs --print (see docs/COLOR_PATCHES.md). */\nconst DEFINITIONS: readonly Definition[] = [\n [\"neutral-1\", \"PV-N1\", \"Studio Gray 1\", \"neutral\", 0.901, [], [96.04, 0, 0], \"#F4F4F4\",\n \"The white end of the flat gray ramp (90.1 %): the reference for white balance, exposure and highlight clipping.\"],\n [\"neutral-2\", \"PV-N2\", \"Studio Gray 2\", \"neutral\", 0.585, [], [81.02, 0, 0], \"#C9C9C9\",\n \"A light flat gray about 15 L* below the white, for the upper tone curve and highlight roll-off.\"],\n [\"neutral-3\", \"PV-N3\", \"Studio Gray 3\", \"neutral\", 0.354, [], [66.06, 0, 0], \"#A1A1A1\",\n \"A light-mid flat gray that continues the ramp in equal lightness steps, for tone-curve and gamma checks.\"],\n [\"neutral-4\", \"PV-N4\", \"Studio Gray 4\", \"neutral\", 0.197, [], [51.5, 0, 0], \"#7B7B7B\",\n \"The mid-gray of the ramp, near an 18 % card's lightness, for exposure and neutral balance in the midtones.\"],\n [\"neutral-5\", \"PV-N5\", \"Studio Gray 5\", \"neutral\", 0.093, [], [36.56, 0, 0], \"#565656\",\n \"A dark flat gray that shows shadow tone separation, noise and any color cast in the shadows.\"],\n [\"neutral-6\", \"PV-N6\", \"Studio Gray 6\", \"neutral\", 0.035, [], [21.94, 0, 0], \"#353535\",\n \"The black end of the ramp (3.5 %), for black level, shadow noise and flare.\"],\n [\"red\", \"PV-S1\", \"Darkroom Red\", \"strong\", 0.031, [rise(0.757, 611.6, 16.2)], [43.7, 54.15, 39.93], \"#BF3428\",\n \"A strong red, for saturated reds, which clip and shift in hue easily in a camera's red channel.\"],\n [\"yellow\", \"PV-S2\", \"Flashbulb Yellow\", \"strong\", 0.071, [rise(0.602, 518.7, 13.2)], [78.87, -0.23, 73.26], \"#E6C02A\",\n \"A strong yellow, bright and high in chroma, that shows whether light saturated colors keep their hue.\"],\n [\"green\", \"PV-S3\", \"Hedgerow Green\", \"strong\", 0.051, [g(0.317, 535.8, 36.9)], [57.08, -47.07, 33.2], \"#359C4C\",\n \"A strong mid green, for the green primary, where camera and display gamuts differ most.\"],\n [\"cyan\", \"PV-S4\", \"Harbor Cyan\", \"strong\", 0.096, [g(0.339, 482.5, 60.4)], [58.4, -21.56, -17.23], \"#3C98AA\",\n \"A strong cyan between the green and blue primaries, a region where overlapping camera responses mix the channels.\"],\n [\"blue\", \"PV-S5\", \"Cyanotype Blue\", \"strong\", 0.064, [g(0.336, 460.7, 24.7)], [35.27, 19.89, -46.48], \"#3C4C9E\",\n \"A strong dark blue, for the blue channel, its noise and the shift of saturated blues toward purple.\"],\n [\"magenta\", \"PV-S6\", \"Filter Magenta\", \"strong\", 0.05, [g(0.258, 440, 32.8), rise(0.549, 605, 15)], [46.11, 55, -18.91], \"#B4408E\",\n \"A strong magenta, a non-spectral hue made of both ends of the spectrum, so it tests the red and blue channels together.\"],\n [\"red-soft\", \"PV-T1\", \"Faded Print Red\", \"soft\", 0.196, [rise(0.472, 601.8, 13)], [59.63, 29.17, 14.19], \"#C77B78\",\n \"A soft red at lower chroma and higher lightness, for gentle reds that a color matrix should not oversaturate.\"],\n [\"yellow-soft\", \"PV-T2\", \"Window Glow Yellow\", \"soft\", 0.246, [rise(0.414, 509, 17.6)], [81.15, -2.88, 36.43], \"#DCC985\",\n \"A soft light yellow, for pale warm tones whose hue drifts easily toward green or orange.\"],\n [\"green-soft\", \"PV-T3\", \"Misty Hedgerow Green\", \"soft\", 0.184, [g(0.251, 535.7, 47.4)], [66.82, -24.11, 16.11], \"#7FAE85\",\n \"A soft green, for the muted greens that fill much of a natural scene.\"],\n [\"cyan-soft\", \"PV-T4\", \"Shallow Water Cyan\", \"soft\", 0.246, [g(0.339, 487.4, 61.9)], [71.51, -16.99, -10.71], \"#7DB9C2\",\n \"A soft cyan, for pale blue-greens such as water and glass, where small hue errors are easy to see.\"],\n [\"blue-soft\", \"PV-T5\", \"Distant Hill Blue\", \"soft\", 0.207, [g(0.319, 457.6, 34.7)], [56.48, 7.42, -30.88], \"#7486BD\",\n \"A soft blue, for the hazy blues of distance and open shade that white balance acts on.\"],\n [\"magenta-soft\", \"PV-T6\", \"Faded Filter Magenta\", \"soft\", 0.196, [g(0.231, 440, 33.1), rise(0.256, 575.8, 15)], [61.36, 26.09, -10.98], \"#B984A8\",\n \"A soft magenta, for pinks and mauves that lie between the red and blue channels.\"],\n [\"skin-pale\", \"PV-P1\", \"Fair Portrait Tone\", \"skin\", 0.408, [rise(0.394, 562.5, 31.7)], [81.35, 9.48, 17.48], \"#E8C3AA\",\n \"A fair skin tone, the lightest of four, for portrait color, where viewers notice small errors.\"],\n [\"skin-medium\", \"PV-P2\", \"Medium Portrait Tone\", \"skin\", 0.213, [rise(0.376, 570.3, 30.4)], [67.25, 13.65, 22.01], \"#C99A7D\",\n \"A medium skin tone, the second of four that span a range of skin lightness at similar hue.\"],\n [\"skin-tan\", \"PV-P3\", \"Tan Portrait Tone\", \"skin\", 0.084, [rise(0.295, 573.7, 31.3)], [52.01, 15.71, 26.72], \"#A3714F\",\n \"A tan skin tone, the third of four, for warm midtone skin under different lights.\"],\n [\"skin-deep\", \"PV-P4\", \"Deep Portrait Tone\", \"skin\", 0.034, [rise(0.133, 590.3, 32.4)], [33, 13.57, 18.02], \"#6A4532\",\n \"A deep skin tone, the darkest of four, for skin color and detail in the darker tones of a portrait.\"],\n [\"sky-blue\", \"PV-M1\", \"Midday Sky\", \"memory\", 0.18, [fall(0.455, 507.2, 29.7)], [60.05, 0.53, -33.31], \"#6893CB\",\n \"A clear-sky blue, a memory color that viewers judge against what they remember of the sky.\"],\n [\"foliage-green\", \"PV-M2\", \"Canopy Green\", \"memory\", 0.034, [g(0.139, 555, 30.7), rise(0.411, 716.8, 12)], [40.81, -22.4, 30.31], \"#4B692C\",\n \"A leaf green with the steep rise of leaf reflectance at the red edge, a memory color that also exposes infrared leakage.\"],\n [\"brick-red\", \"PV-M3\", \"Warehouse Brick\", \"memory\", 0.05, [rise(0.308, 592.7, 16.2)], [41.63, 32.81, 25.53], \"#9C4A3A\",\n \"A dull brick red, a common building color between the saturated reds and the skin tones.\"],\n [\"sand\", \"PV-M4\", \"Low Tide Sand\", \"memory\", 0.272, [rise(0.371, 504.9, 37.6)], [79.14, 0.48, 22.19], \"#D5C29B\",\n \"A pale sand, a light warm near-neutral that shows small casts a gray patch would hide.\"],\n [\"teal\", \"PV-M5\", \"Estuary Teal\", \"memory\", 0.085, [g(0.197, 498.2, 42)], [48.43, -24.88, -5.64], \"#2E7F7C\",\n \"A dark blue-green between the green and cyan hues, for water and shaded foliage in that region.\"],\n [\"lavender\", \"PV-M6\", \"Wildflower Lilac\", \"memory\", 0.331, [g(0.298, 445, 30.4), rise(0.137, 623.4, 20)], [66.69, 14.8, -22.7], \"#A99BCB\",\n \"A pale blue-violet, a flower color that cameras often render too blue.\"],\n [\"deep-orange\", \"PV-G1\", \"Sunset Flare Orange\", \"gamut-edge\", 0.03, [rise(0.74, 591.3, 16.7)], [53.19, 52.38, 56.53], \"#DC511A\",\n \"A deep orange close to the sRGB gamut boundary, for gamut mapping and clipping of saturated warm colors.\"],\n [\"deep-violet\", \"PV-G2\", \"Blacklight Violet\", \"gamut-edge\", 0.031, [g(0.37, 441.6, 25.6), rise(0.097, 640, 20)], [26.42, 48.9, -59.69], \"#4C1E9C\",\n \"A deep violet close to the sRGB gamut boundary, for gamut mapping and the camera's response at short wavelengths.\"]\n];\n\nfunction deepFreezePatch(patch: ColorPatch): ColorPatch {\n for (const term of patch.terms) Object.freeze(term);\n Object.freeze(patch.terms);\n Object.freeze(patch.reference.cielabD65);\n Object.freeze(patch.reference);\n return Object.freeze(patch);\n}\nconst PATCHES: readonly ColorPatch[] = Object.freeze(DEFINITIONS.map(([patchId, code, name, group, base, terms, lab, hex, reason]) => deepFreezePatch({\n patchId, code, name, group, reason, base, terms, reference: { cielabD65: lab, srgbHex: hex, inSrgbGamut: true }\n})));\nconst BY_ID = new Map<string, ColorPatch>(PATCHES.map((p) => [p.patchId, p]));\nconst SET: ColorPatchSet = /* @__PURE__ */ Object.freeze({ setId: COLOR_PATCH_SET_ID, version: COLOR_PATCH_SET_VERSION,\n wavelengthRangeNanometers: COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS, columns: 6, patches: PATCHES });\n\n/**\n * The built-in Photivra color-patch set: every patch's id, code, name, group, reason, spectrum parameters and reference CIELAB (D65,\n * CIE 1931 2°) and sRGB values. A static, deeply frozen engine contract. Pass a patch to a chart as\n * `{ kind: \"color-patch\", patchId }` wherever a chart reflectance is accepted, or draw the whole set with the\n * `color-patches` chart preset.\n */\nexport function getColorPatchSet(): ColorPatchSet {\n return SET;\n}\n\n/** Internal: the patch with this id, or undefined. */\nexport function findColorPatch(patchId: unknown): ColorPatch | undefined {\n return typeof patchId === \"string\" ? BY_ID.get(patchId) : undefined;\n}\n\n/** An exponential: `Math.exp`, or the display view's portable one. */\ntype Exponential = (x: number) => number;\n/** The unclamped formula value b + Σ aₖφₖ(λ) (the clamp never acts on the built-in set; a test checks it). */\nfunction formula(patch: ColorPatch, nm: number, exp: Exponential): number {\n let value = patch.base;\n for (const term of patch.terms) {\n if (term.kind === \"gaussian\") {\n const z = (nm - term.centerNanometers) / term.standardDeviationNanometers;\n value += term.amplitude * exp(-0.5 * z * z);\n } else {\n const step = 1 / (1 + exp(-(nm - term.midpointNanometers) / term.scaleNanometers));\n value += term.amplitude * (term.kind === \"logistic-rise\" ? step : 1 - step);\n }\n }\n return value;\n}\nfunction reflectanceOf(patch: ColorPatch, nm: number, exp: Exponential = Math.exp): number {\n return Math.min(1, Math.max(0, formula(patch, nm, exp)));\n}\n/** Internal (for tests): the formula before the clamp. */\nexport function evaluateUnclampedColorPatchFormula(patchId: ColorPatchId, wavelengthNanometers: number, exp: Exponential = Math.exp): number {\n return formula(BY_ID.get(patchId)!, wavelengthNanometers, exp);\n}\n\n/** Prepared spectra per exponential (Math.exp for captures, the portable one for the display view), then per patch. */\nconst PREPARED = new Map<Exponential, Map<string, PreparedColorimetricSpectrum>>();\n/** Most wavelengths whose values a prepared patch spectrum keeps (a capture uses a few dozen quadrature nodes). */\nconst PREPARED_CACHE_LIMIT = 4096;\n/**\n * Internal: a prepared reflectance spectrum of a built-in patch. Values are memoized per wavelength (the formula is pure),\n * so a capture's per-query lookups cost one map access instead of the exponentials.\n */\nexport function prepareColorPatchSpectrum(patch: ColorPatch, exp: Exponential = Math.exp): PreparedColorimetricSpectrum {\n let byPatch = PREPARED.get(exp);\n if (!byPatch) PREPARED.set(exp, byPatch = new Map());\n const existing = byPatch.get(patch.patchId);\n if (existing) return existing;\n const range = COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS, cache = new Map<number, number>();\n const prepared: PreparedColorimetricSpectrum = Object.freeze({\n wavelengthRangeNanometers: range,\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(\"Color patch \" + patch.patchId + \" is defined on \" + range.minimum + \"–\" + range.maximum + \" nm.\");\n }\n const value = reflectanceOf(patch, nm, exp);\n if (cache.size < PREPARED_CACHE_LIMIT) cache.set(nm, value);\n return value;\n }\n });\n byPatch.set(patch.patchId, prepared);\n return prepared;\n}\n\n/** Input of {@link evaluateColorPatchReflectance}. */\nexport interface EvaluateColorPatchReflectanceInput {\n patchId: ColorPatchId;\n /** Vacuum wavelength, nanometers, in 360–830. */\n wavelengthNanometers: number;\n}\n\n/**\n * The reflectance (dimensionless, in [0, 1]) of a built-in color patch at one wavelength, from its analytic formula.\n * Labeled `approximation`: the spectra are Photivra's own smooth designs, not measurements of a physical sample.\n */\nexport function evaluateColorPatchReflectance(input: EvaluateColorPatchReflectanceInput): CalculationResult<{ reflectance: number }> {\n requireInputKeys(input, \"evaluateColorPatchReflectance\", [\"patchId\", \"wavelengthNanometers\"]);\n const patch = findColorPatch(input.patchId);\n if (!patch) throw new InvalidConfigurationError(\"patchId must be one of the built-in color patches (COLOR_PATCH_IDS).\");\n const nm = input.wavelengthNanometers, range = COLOR_PATCH_WAVELENGTH_RANGE_NANOMETERS;\n if (typeof nm !== \"number\" || !Number.isFinite(nm) || nm < range.minimum || nm > range.maximum) {\n throw new InvalidScientificInputError(\"wavelengthNanometers must be a finite wavelength from 360 to 830 nm.\");\n }\n return approximationResult({ reflectance: reflectanceOf(patch, nm) }, \"photivra-color-patch-spectrum\", COLOR_PATCH_SET_VERSION,\n [\"Photivra's own analytic design (a constant plus Gaussian and logistic terms), not a measured sample\", \"Non-fluorescent Lambertian reflectance\"]);\n}\n"]}
@@ -33,7 +33,7 @@ export function parsePrepareDisplayViewOptions(value) {
33
33
  throw new InvalidConfigurationError("prepareDisplayView options.gpuDevice needs gpu \"auto\" or \"require\".");
34
34
  return { gpu: mode, gpuDevice };
35
35
  }
36
- const CPU_LIMITATIONS = Object.freeze(["Float64 engine reference execution; results are the reference by definition"]);
36
+ const CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze(["Float64 engine reference execution; results are the reference by definition"]);
37
37
  /** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */
38
38
  export function cpuDisplayViewExecution(gpu, fallbackReason, adapter, conformance, detail, compositeProbe = null) {
39
39
  const requestedBackend = gpu === "off" ? "cpu-reference" : "webgpu-f32";
@@ -1 +1 @@
1
- {"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
1
+ {"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
@@ -26,7 +26,7 @@ import { resolveChartMaterialLayers } from "./chart-material-layers.js";
26
26
  /** CIE maximum luminous efficacy for photopic vision, lm/W. */
27
27
  export const PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W = 683;
28
28
  /** Wavelength range of every facade spectrum and of the facade sensor quadrature, nm. */
29
- export const FACADE_SPECTRAL_RANGE_NANOMETERS = Object.freeze({ minimum: 380, maximum: 780 });
29
+ export const FACADE_SPECTRAL_RANGE_NANOMETERS = /* @__PURE__ */ Object.freeze({ minimum: 380, maximum: 780 });
30
30
  export function resolveCaptureIlluminant(value) {
31
31
  return resolveIlluminant(value, CAPTURE_SPECTRA);
32
32
  }
@@ -134,7 +134,7 @@ const MATERIAL_OF_DIRECTION = new WeakMap();
134
134
  export function planarProviderMaterialOfDirection(provider) {
135
135
  return MATERIAL_OF_DIRECTION.get(provider);
136
136
  }
137
- const UNCERTAINTY = Object.freeze({ kind: "not-quantified", limitation: "Analytic planar Lambertian scene; no measured scene, BRDF or illumination." });
137
+ const UNCERTAINTY = /* @__PURE__ */ Object.freeze({ kind: "not-quantified", limitation: "Analytic planar Lambertian scene; no measured scene, BRDF or illumination." });
138
138
  /** Most distinct wavelengths {@link tabulatedSpectrum} keeps; later ones are evaluated on every call. */
139
139
  const TABULATED_SPECTRUM_MAX_ENTRIES = 4096;
140
140
  /**