@photivra/engine 2.21.0 → 2.22.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 (169) hide show
  1. package/CHANGELOG.md +118 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +11 -0
  6. package/dist/capture/c2pa-embed.d.ts +116 -0
  7. package/dist/capture/c2pa-embed.js +493 -0
  8. package/dist/capture/c2pa-embed.js.map +1 -0
  9. package/dist/capture/dng-import.d.ts +73 -8
  10. package/dist/capture/dng-import.js +315 -80
  11. package/dist/capture/dng-import.js.map +1 -1
  12. package/dist/capture/dng-opcodes.d.ts +96 -0
  13. package/dist/capture/dng-opcodes.js +391 -0
  14. package/dist/capture/dng-opcodes.js.map +1 -0
  15. package/dist/capture/dng-profile-maps.d.ts +99 -0
  16. package/dist/capture/dng-profile-maps.js +381 -0
  17. package/dist/capture/dng-profile-maps.js.map +1 -0
  18. package/dist/capture/dng-stage-image.d.ts +23 -0
  19. package/dist/capture/dng-stage-image.js +65 -0
  20. package/dist/capture/dng-stage-image.js.map +1 -0
  21. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  22. package/dist/capture/jpeg-xl-tiles.js +151 -0
  23. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  24. package/dist/capture/lossy-jpeg.d.ts +17 -0
  25. package/dist/capture/lossy-jpeg.js +369 -0
  26. package/dist/capture/lossy-jpeg.js.map +1 -0
  27. package/dist/capture/native-raw-development.d.ts +2 -1
  28. package/dist/capture/native-raw-development.js.map +1 -1
  29. package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
  30. package/dist/color/neighbor-highlight-reconstruction.js +234 -0
  31. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  32. package/dist/color/sensor-color-development.d.ts +5 -0
  33. package/dist/color/sensor-color-development.js +7 -1
  34. package/dist/color/sensor-color-development.js.map +1 -1
  35. package/dist/color/spectral-spikiness.d.ts +11 -0
  36. package/dist/color/spectral-spikiness.js +23 -0
  37. package/dist/color/spectral-spikiness.js.map +1 -0
  38. package/dist/core/execution-error.d.ts +21 -0
  39. package/dist/core/execution-error.js +12 -0
  40. package/dist/core/execution-error.js.map +1 -0
  41. package/dist/core/version.d.ts +1 -1
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/version.js.map +1 -1
  44. package/dist/facade/catalogue-lens-signal.d.ts +26 -1
  45. package/dist/facade/catalogue-lens-signal.js +25 -7
  46. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  47. package/dist/facade/diffraction-provider.d.ts +1 -1
  48. package/dist/facade/diffraction-provider.js +4 -4
  49. package/dist/facade/diffraction-provider.js.map +1 -1
  50. package/dist/facade/display-view-gpu-session.js +38 -6
  51. package/dist/facade/display-view-gpu-session.js.map +1 -1
  52. package/dist/facade/display-view-gpu.d.ts +13 -5
  53. package/dist/facade/display-view-gpu.js.map +1 -1
  54. package/dist/facade/display-view-normal.d.ts +15 -0
  55. package/dist/facade/display-view-normal.js +44 -0
  56. package/dist/facade/display-view-normal.js.map +1 -0
  57. package/dist/facade/display-view-rendering.d.ts +97 -0
  58. package/dist/facade/display-view-rendering.js +598 -0
  59. package/dist/facade/display-view-rendering.js.map +1 -0
  60. package/dist/facade/display-view.d.ts +37 -65
  61. package/dist/facade/display-view.js +147 -476
  62. package/dist/facade/display-view.js.map +1 -1
  63. package/dist/facade/expected-signal-setup.js +2 -2
  64. package/dist/facade/expected-signal-setup.js.map +1 -1
  65. package/dist/facade/fast-methods.d.ts +1 -1
  66. package/dist/facade/fast-methods.js +37 -8
  67. package/dist/facade/fast-methods.js.map +1 -1
  68. package/dist/facade/generic-lens-capture.d.ts +1 -1
  69. package/dist/facade/generic-lens-capture.js +1 -1
  70. package/dist/facade/generic-lens-capture.js.map +1 -1
  71. package/dist/facade/image-plane.js +41 -10
  72. package/dist/facade/image-plane.js.map +1 -1
  73. package/dist/facade/on-demand-kits.d.ts +14 -0
  74. package/dist/facade/on-demand-kits.js +36 -0
  75. package/dist/facade/on-demand-kits.js.map +1 -0
  76. package/dist/facade/opt-in-kit.d.ts +9 -0
  77. package/dist/facade/opt-in-kit.js +11 -0
  78. package/dist/facade/opt-in-kit.js.map +1 -0
  79. package/dist/facade/quality-kit.d.ts +21 -0
  80. package/dist/facade/quality-kit.js +23 -0
  81. package/dist/facade/quality-kit.js.map +1 -0
  82. package/dist/facade/quality-paths.d.ts +22 -0
  83. package/dist/facade/quality-paths.js +44 -0
  84. package/dist/facade/quality-paths.js.map +1 -0
  85. package/dist/facade/scene-radiance.d.ts +18 -2
  86. package/dist/facade/scene-radiance.js +6 -4
  87. package/dist/facade/scene-radiance.js.map +1 -1
  88. package/dist/facade/simulate-capture.d.ts +32 -13
  89. package/dist/facade/simulate-capture.js +162 -69
  90. package/dist/facade/simulate-capture.js.map +1 -1
  91. package/dist/facade/simulate-display-view.d.ts +10 -12
  92. package/dist/facade/simulate-display-view.js +57 -48
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  95. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  96. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  97. package/dist/gpu/display-view-image-material-module.d.ts +3 -2
  98. package/dist/gpu/display-view-image-material-module.js +25 -3
  99. package/dist/gpu/display-view-image-material-module.js.map +1 -1
  100. package/dist/gpu/display-view-image-quality.d.ts +43 -1
  101. package/dist/gpu/display-view-image-quality.js +256 -11
  102. package/dist/gpu/display-view-image-quality.js.map +1 -1
  103. package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
  104. package/dist/gpu/display-view-quality-gpu.js +60 -249
  105. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  106. package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
  107. package/dist/gpu/display-view-speed-gpu.js +50 -12
  108. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  109. package/dist/gpu/image-kernel-accelerator.js +3 -0
  110. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  111. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  112. package/dist/gpu/image-kernel-execution.js +2 -10
  113. package/dist/gpu/image-kernel-execution.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
  115. package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
  116. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  117. package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
  118. package/dist/gpu/wgsl-display-view-coupled.js +353 -0
  119. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  120. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -9
  121. package/dist/gpu/wgsl-display-view-quality-kernel.js +109 -111
  122. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  123. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
  124. package/dist/gpu/wgsl-display-view-speed-kernel.js +71 -8
  125. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  126. package/dist/index.d.ts +11 -3
  127. package/dist/index.js +9 -1
  128. package/dist/index.js.map +1 -1
  129. package/dist/output/camera-channel-response.d.ts +50 -0
  130. package/dist/output/camera-channel-response.js +69 -0
  131. package/dist/output/camera-channel-response.js.map +1 -0
  132. package/dist/output/native-capture-sdr.js +5 -2
  133. package/dist/output/native-capture-sdr.js.map +1 -1
  134. package/dist/output/sdr-rendering.d.ts +18 -1
  135. package/dist/output/sdr-rendering.js +39 -2
  136. package/dist/output/sdr-rendering.js.map +1 -1
  137. package/dist/sensor/native-sensor-report.d.ts +10 -0
  138. package/dist/sensor/native-sensor-report.js +16 -0
  139. package/dist/sensor/native-sensor-report.js.map +1 -0
  140. package/dist/sensor/sensor-characteristics.d.ts +81 -13
  141. package/dist/sensor/sensor-characteristics.js +10 -6
  142. package/dist/sensor/sensor-characteristics.js.map +1 -1
  143. package/docs/API_REFERENCE.md +117 -4
  144. package/docs/QUICK_START.md +37 -8
  145. package/docs/USAGE.md +104 -22
  146. package/docs/api/capture-c2pa-embed.md +177 -0
  147. package/docs/api/capture-dng-import.md +86 -10
  148. package/docs/api/capture-dng-opcodes.md +93 -0
  149. package/docs/api/capture-dng-profile-maps.md +141 -0
  150. package/docs/api/capture-dng-stage-image.md +43 -0
  151. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  152. package/docs/api/capture-lossy-jpeg.md +41 -0
  153. package/docs/api/capture-native-raw-development.md +2 -1
  154. package/docs/api/core-execution-error.md +43 -0
  155. package/docs/api/core-version.md +1 -1
  156. package/docs/api/exports.json +255 -5
  157. package/docs/api/facade-display-view-gpu.md +5 -2
  158. package/docs/api/facade-display-view.md +11 -15
  159. package/docs/api/facade-fast-methods.md +38 -9
  160. package/docs/api/facade-quality-paths.md +24 -0
  161. package/docs/api/facade-scene-radiance.md +1 -1
  162. package/docs/api/facade-simulate-capture.md +24 -7
  163. package/docs/api/facade-simulate-display-view.md +10 -12
  164. package/docs/api/gpu-image-kernel-execution.md +0 -36
  165. package/docs/api/output-camera-channel-response.md +55 -0
  166. package/docs/api/output-sdr-rendering.md +6 -1
  167. package/docs/api/sensor-sensor-characteristics.md +54 -2
  168. package/llms.txt +1 -0
  169. package/package.json +6 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-channel-response.js","sourceRoot":"","sources":["../../src/output/camera-channel-response.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #591: a camera's channel response on the capture's own spectral grid, and optionally each channel's effective dispersion under\n * an illuminant, for the calculator's colour-fringe numbers (`calculateLateralColorFringe`'s `channelDispersion`). Pure, and not\n * imported by `simulateCapture` or the capture worker.\n *\n * Every camera of the facade (every tier body and every `SensorProfile`) has the same synthetic colorimetric response shape\n * (simulate-capture.ts, \"Synthetic colorimetric sensor\"): EQE_c(λ) = a_c·cmf_c(λ)·(555 nm/λ)·q with cmf = x̄, ȳ, z̄ (the engine's CIE\n * 1931 fit), a = 0.5, 0.5, 0.25 for red, green and blue, and q the peak-quantum-efficiency scale (1 for a tier body, the profile's\n * peak QE / 0.5 for a profile, or the explicit `peakQuantumEfficiency` / 0.5). The capture samples EQE at breakpoints 380 + kΔ nm,\n * Δ = 400 / ⌈400 / step⌉, and integrates with the midpoint rule over 380–780 nm, so EQE at a node is the mean of its two\n * breakpoints (linear interpolation). Electrons per joule of incident light at λ are EQE(λ)·λ/(h·c) (photon energy h·c/λ; CODATA\n * exact h and c; vacuum wavelength, where the capture converts air to vacuum with the air's phase index, a factor that is\n * constant to about 1e-6 and cancels in the dispersion).\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { estimateCie1931ColorMatchingFunctions } from \"../color/color-matching-functions.js\";\nimport type { SensorProfile } from \"../equipment/sensor-profile-body.js\";\nimport { resolveCaptureIlluminant, FACADE_SPECTRAL_RANGE_NANOMETERS, type CaptureIlluminant } from \"../facade/scene-radiance.js\";\nimport { channelEffectiveDispersion, type ChannelDispersion } from \"./developed-lens-corrections.js\";\n\nexport interface CalculateCameraChannelResponseInput {\n /**\n * The capture's `sensor.profile` record (a `SensorProfile`, for example `resolveSensorProfile(id)`): only its\n * `peakQuantumEfficiency` is read, the one field that changes the channels. Omitted: a tier body (peak QE 0.5).\n */\n sensorProfile?: Pick<SensorProfile, \"peakQuantumEfficiency\">;\n /** Peak quantum efficiency in (0, 1] (as `sensor.peakQuantumEfficiency`); wins over the profile's. */\n peakQuantumEfficiency?: number;\n /**\n * The capture's `sensor.spectralStepNanometers`, 1–100. Default 5: the calculator runs at full quality, and a spiky light\n * needs the fine grid. Pass the capture's step to reproduce a capture's channels.\n */\n spectralStepNanometers?: number;\n /** A `simulateCapture` illuminant: when given, the result also carries each channel's effective dispersion under it. */\n illuminant?: CaptureIlluminant;\n}\n\nexport interface CameraChannelResponse {\n /** The capture's midpoint nodes, nm, increasing. */\n wavelengthsNm: number[];\n /** Electrons per joule of incident light at each node, per channel: EQE_c(λ)·λ/(h·c). */\n red: number[];\n green: number[];\n blue: number[];\n /** With `illuminant`: D̄_c, as `calculateChannelEffectiveDispersion` returns for these channels; else null. */\n channelDispersion: ChannelDispersion | null;\n}\n\nconst EQE_SCALE = [0.5, 0.5, 0.25] as const, CMF_KEY = [\"x\", \"y\", \"z\"] as const;\nconst PLANCK_JOULE_SECONDS = 6.62607015e-34, LIGHT_METERS_PER_SECOND = 299_792_458;\n\n/**\n * #591: the camera's channel response on its spectral grid and, with `illuminant`, each channel's effective dispersion (pass it\n * as `channelDispersion` to `calculateLateralColorFringe` or `createGenericLensCorrectionProfile`). Strict input. A labelled\n * approximation: the facade's synthetic colorimetric sensor, not a measured one.\n */\nexport function calculateCameraChannelResponse(input: CalculateCameraChannelResponseInput = {}): CalculationResult<CameraChannelResponse> {\n requireInputObject(input, \"calculateCameraChannelResponse input\");\n requireInputKeys(input, \"calculateCameraChannelResponse\", [\"sensorProfile\", \"peakQuantumEfficiency\", \"spectralStepNanometers\", \"illuminant\"]);\n const step = input.spectralStepNanometers ?? 5;\n if (typeof step !== \"number\" || !(step >= 1 && step <= 100)) throw new InvalidScientificInputError(\"spectralStepNanometers must be a number from 1 to 100.\");\n const profile = input.sensorProfile;\n if (profile !== undefined) requireInputObject(profile, \"sensorProfile\");\n let peak = profile === undefined ? 0.5 : profile.peakQuantumEfficiency;\n if (typeof peak !== \"number\" || !(peak > 0 && peak <= 1)) throw new InvalidScientificInputError(\"sensorProfile.peakQuantumEfficiency must be in (0, 1].\");\n if (input.peakQuantumEfficiency !== undefined) {\n if (typeof input.peakQuantumEfficiency !== \"number\" || !(input.peakQuantumEfficiency > 0 && input.peakQuantumEfficiency <= 1)) throw new InvalidScientificInputError(\"peakQuantumEfficiency must be in (0, 1].\");\n peak = input.peakQuantumEfficiency;\n }\n const q = peak / 0.5, { minimum, maximum } = FACADE_SPECTRAL_RANGE_NANOMETERS, count = Math.ceil((maximum - minimum) / step), spacing = (maximum - minimum) / count;\n const eqe = (c: number, nm: number): number => EQE_SCALE[c]! * estimateCie1931ColorMatchingFunctions(nm)[CMF_KEY[c]!] * 555 / nm * q;\n const wavelengthsNm: number[] = [], channels: number[][] = [[], [], []];\n for (let k = 0; k < count; k++) {\n const lo = minimum + k * spacing, hi = minimum + (k + 1) * spacing, nm = (lo + hi) / 2;\n wavelengthsNm.push(nm);\n for (let c = 0; c < 3; c++) channels[c]!.push((eqe(c, lo) + eqe(c, hi)) / 2 * nm * 1e-9 / (PLANCK_JOULE_SECONDS * LIGHT_METERS_PER_SECOND));\n }\n let channelDispersion: ChannelDispersion | null = null;\n if (input.illuminant !== undefined) {\n const shape = resolveCaptureIlluminant(input.illuminant).shape;\n const light = wavelengthsNm.map((nm) => shape.evaluate(nm));\n if (channels.some((r) => !(r.reduce((sum, v, n) => sum + v * light[n]!, 0) > 0))) throw new InvalidScientificInputError(\"Every channel needs a positive response under the illuminant.\");\n channelDispersion = channelEffectiveDispersion(wavelengthsNm, channels, light);\n }\n return approximationResult({ wavelengthsNm, red: channels[0]!, green: channels[1]!, blue: channels[2]!, channelDispersion }, \"camera-channel-response\", \"1.0.0\", [\n \"The facade's synthetic colorimetric sensor: EQE_c(λ) = a_c·cmf_c(λ)·(555 nm/λ)·(peak QE / 0.5), the capture's own channels.\",\n \"Midpoint nodes over 380–780 nm at the capture's spectral step; EQE at a node is the mean of its two breakpoints.\",\n \"Vacuum photon energy h·c/λ; the capture's air-to-vacuum factor is constant to about 1e-6 and cancels in the dispersion.\"]);\n}\n"]}
@@ -11,7 +11,7 @@ import { freezeOwnedData } from "../core/owned-data.js";
11
11
  import { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from "../core/pixel-limits.js";
12
12
  import { encodeSrgbComponent } from "../color/srgb-transfer.js";
13
13
  import { InvalidScientificResultError } from "../core/validation.js";
14
- import { parseSdrRenderingProfile, prepareSdrPixelTone } from "./sdr-rendering.js";
14
+ import { desaturateToLuminance, parseSdrRenderingProfile, prepareSdrPixelTone } from "./sdr-rendering.js";
15
15
  export const NATIVE_CAPTURE_SDR_SCHEMA_VERSION = "0.1.0";
16
16
  /** `maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`. */
17
17
  export const NATIVE_CAPTURE_SDR_LIMITS = /* @__PURE__ */ Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,
@@ -187,7 +187,7 @@ export function nativeSrgb8QuantizerForTest() {
187
187
  * values. Not exported from the package.
188
188
  */
189
189
  export function renderSdrRunInto(source, start, pixelCount, profile, tone, scale, maximum, codes, outOffset, scratch, counts) {
190
- const reject = profile.gamutHandling === "reject-out-of-range", quantizer = maximum === 255 ? srgb8Quantizer() : null;
190
+ const reject = profile.gamutHandling === "reject-out-of-range", desaturate = profile.gamutHandling === "desaturate-to-luminance", quantizer = maximum === 255 ? srgb8Quantizer() : null;
191
191
  let negative = 0, above = 0, changed = 0, low = 0, high = 0, outOfGamut = false;
192
192
  for (let p = 0, i = start, o = outOffset; p < pixelCount; p++, i += 3) {
193
193
  // The reference parser normalizes -0 to +0 before exposure.
@@ -196,6 +196,9 @@ export function renderSdrRunInto(source, start, pixelCount, profile, tone, scale
196
196
  if (!Number.isFinite(er) || !Number.isFinite(eg) || !Number.isFinite(eb))
197
197
  throw new InvalidScientificResultError("Rendering exposure produced a non-finite sample.");
198
198
  tone(er, eg, eb, scratch);
199
+ // #601: the hue-preserving desaturation, before the components are counted and clipped.
200
+ if (desaturate)
201
+ desaturateToLuminance(scratch);
199
202
  for (let c = 0; c < 3; c++, o++) {
200
203
  const exposed = c === 0 ? er : c === 1 ? eg : eb;
201
204
  if (exposed < 0)
@@ -1 +1 @@
1
- {"version":3,"file":"native-capture-sdr.js","sourceRoot":"","sources":["../../src/output/native-capture-sdr.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;GAGG;AACH,OAAO,EAAE,qBAAqB,EAAkD,MAAM,iCAAiC,CAAC;AACxH,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAwE,MAAM,oBAAoB,CAAC;AAEzJ,MAAM,CAAC,MAAM,iCAAiC,GAAG,OAAgB,CAAC;AAClE,8HAA8H;AAC9H,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,8BAA8B;IAC/I,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,yBAAyB,EAAE,MAAM,EAAE,CAAC,CAAC;AA8DjG,oHAAoH;AACpH,MAAM,UAAU,6BAA6B,CAAC,KAA4B;IACxE,MAAM,CAAC,GAAG,wBAAwB,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,oBAAoB,EAAE,SAAS,EAAE,oBAAoB,EAAE,eAAe,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAC7K,MAAM,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,aAAa,EAAE,yBAAyB,CAAC,aAAa,EAAE,yBAAyB,CAAC,aAAa,EACxI,wDAAwD,GAAG,yBAAyB,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC;IAC5G,qGAAqG;IACrG,MAAM,CAAC,GAAG,CAAC,CAAC,OAAuC,CAAC;IACpD,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI,KAAK,gBAAgB,CAAC,EAAE,CAAC;QAC7H,MAAM,IAAI,yBAAyB,CAAC,sDAAsD,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,OAAO,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,qBAAqB,CAAC,CAAC,CAAC,aAAa,EAAE,8BAA8B,CAAC,CAAC;IAC7F,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,CAAC,CAAC,kBAAkB,EAAE,qCAAqC,CAAC,CAAC;IAC9G,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,aAAa,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,wBAAwB,EAAE,CAAC,iBAAiB,CAAC;IAC3D,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,KAAK,8BAA8B;QAC7D,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,0BAA0B,CAAC,EAAE,IAAI,KAAK,CAAC,YAAY,CAAC,OAAO,KAAK,0BAA0B,CAAC,OAAO;QAC7H,KAAK,CAAC,yBAAyB,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;QAChJ,KAAK,CAAC,uBAAuB,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,uBAAuB,KAAK,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,KAAK,IAAI,CAAC;QAC9I,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,kBAAkB,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,yBAAyB,CAAC,+EAA+E,CAAC,CAAC;IACvH,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACnG,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;IAC3E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,sCAAsC,CAAC,CAAC;IACvH,IAAI,MAAM,GAAG,aAAa,IAAI,KAAK,CAAC,UAAU,GAAG,yBAAyB,CAAC,gBAAgB;QACvF,KAAK,CAAC,WAAW,GAAG,yBAAyB,CAAC,gBAAgB,IAAI,OAAO,CAAC,CAAC,kBAAkB,KAAK,QAAQ;QAC1G,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,kBAAkB,GAAG,WAAW,EAAE,CAAC;QACtF,MAAM,IAAI,yBAAyB,CAAC,6DAA6D,CAAC,CAAC;IACrG,CAAC;IACD,mHAAmH;IACnH,+HAA+H;IAC/H,MAAM,0BAA0B,GAAG,yBAAyB,CAAC,yBAAyB,GAAG,CAAC;QACxF,yBAAyB,CAAC,SAAS,GAAG,yBAAyB,CAAC,UAAU,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzF,OAAO,eAAe,CAAC,EAAE,aAAa,EAAE,iCAAiC,EAAE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,OAAO;QAC5H,WAAW,EAAE,0BAA0B;QACvC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,yBAAyB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,yBAAyB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;AAC1J,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAA0B,EAAE,OAAoC,EAC3G,KAAyB;IACzB,MAAM,CAAC,GAAG,wBAAwB,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,SAAS,CAAC,EAAE,0BAA0B,CAAC,CAAC;IACzL,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAA0C,EAAE,CAAC;QAChF,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,yBAAyB,CAAC,8CAA8C,CAAC,CAAC;IACnH,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;IAC1B,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;IAC7F,IAAI,CAAC,CAAC,OAAO,YAAY,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,YAAY,WAAW,CAAC;QAC9E,OAAO,CAAC,MAAM,CAAC,UAAU,GAAG,yBAAyB,CAAC,yBAAyB,GAAG,YAAY,CAAC,iBAAiB;QAChH,OAAO,CAAC,CAAC,gBAAgB,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,gBAAgB,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC;QAC7H,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,gBAAgB,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,yBAAyB,CAAC,yBAAyB,EAAE,CAAC;QACnI,MAAM,IAAI,yBAAyB,CAAC,oDAAoD,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,MAAM,GAAgC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,gBAAgB,EAAE,SAAS,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IAChH,4GAA4G;IAC5G,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC;gBAAE,MAAM,IAAI,yBAAyB,CAAC,sCAAsC,CAAC,CAAC;QAChH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAGD;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,IAA0B,EAAE,OAAoC,EAAE,KAAyB,EACjH,OAA4B,EAAE,IAAkB,EAAE,KAAa,EAAE,OAAe,EAAE,KAA+B,EAAE,WAAsC;IACzJ,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC9E,2FAA2F;IAC3F,MAAM,eAAe,GAAG,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,OAAO,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IACxH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,gBAAgB,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,eAAe,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAChK,CAAC;IACD,IAAI,MAAM,CAAC,UAAU;QAAE,MAAM,IAAI,yBAAyB,CAAC,mDAAmD,CAAC,CAAC;IAChH,WAAW,CAAC,4BAA4B,IAAI,MAAM,CAAC,QAAQ,CAAC;IAAC,WAAW,CAAC,kCAAkC,IAAI,MAAM,CAAC,KAAK,CAAC;IAC5H,WAAW,CAAC,sBAAsB,IAAI,MAAM,CAAC,OAAO,CAAC;IAAC,WAAW,CAAC,0BAA0B,IAAI,MAAM,CAAC,GAAG,CAAC;IAAC,WAAW,CAAC,2BAA2B,IAAI,MAAM,CAAC,IAAI,CAAC;AACrK,CAAC;AACD,gGAAgG;AAChG,SAAS,cAAc,CAAC,MAAc,IAAY,OAAO,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAQzG,IAAI,KAAwC,CAAC;AAC7C;;;;;;;;;;;GAWG;AACH,SAAS,cAAc;IACrB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,CAAC,GAAG,IAAI,YAAY,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpG,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;IACjH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAC3B,SAAS,CAAC;YACR,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,EAAE;gBAAE,MAAM;YACpC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,EAAE,GAAG,GAAG,CAAC;;gBAAM,EAAE,GAAG,GAAG,CAAC;QACxD,CAAC;QACD,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACZ,CAAC;IACD,CAAC,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IAClB,IAAI,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;IACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QAChB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC;YAAE,KAAK,GAAG,KAAK,CAAC;QAC/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9C,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAAE,KAAK,GAAG,KAAK,CAAC;QAC7G,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAAC,KAAK,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE;YAAE,KAAK,GAAG,KAAK,CAAC;IAAC,CAAC;IACxI,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,OAAO,KAAK,CAAC;AACf,CAAC;AACD,8CAA8C;AAC9C,SAAS,aAAa,CAAC,CAAiB,EAAE,CAAS;IACjD,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC;IACvB,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAE,CAAC;IACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAE;QAAE,CAAC,EAAE,CAAC;IAC3B,OAAO,CAAC,CAAC;AACX,CAAC;AACD,0FAA0F;AAC1F,MAAM,UAAU,2BAA2B;IACzC,MAAM,SAAS,GAAG,cAAc,EAAE,CAAC;IACnC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;AACrG,CAAC;AAGD;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmC,EAAE,KAAa,EAAE,UAAkB,EAAE,OAA4B,EACnI,IAAkB,EAAE,KAAa,EAAE,OAAe,EAAE,KAA8C,EAAE,SAAiB,EAAE,OAAqB,EAAE,MAAsB;IACpK,MAAM,MAAM,GAAG,OAAO,CAAC,aAAa,KAAK,qBAAqB,EAAE,SAAS,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACtH,IAAI,QAAQ,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;IAChF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,4DAA4D;QAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAE,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAE,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QACtE,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACvH,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,4BAA4B,CAAC,kDAAkD,CAAC,CAAC;QACrK,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;YAChC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,IAAI,OAAO,GAAG,CAAC;gBAAE,QAAQ,EAAE,CAAC;YAC5B,IAAI,OAAO,GAAG,CAAC;gBAAE,KAAK,EAAE,CAAC;YACzB,IAAI,MAAM,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;YACzB,IAAI,MAAM,KAAK,OAAO;gBAAE,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,KAAK,CAAC;gBAAE,MAAM,GAAG,CAAC,CAAC;YAC7B,IAAI,MAAM,GAAG,MAAM,CAAC;YACpB,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,IAAI,MAAM;oBAAE,UAAU,GAAG,IAAI,CAAC;qBACzB,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;oBAAC,GAAG,EAAE,CAAC;oBAAC,MAAM,GAAG,CAAC,CAAC;gBAAC,CAAC;qBACtC,CAAC;oBAAC,IAAI,EAAE,CAAC;oBAAC,MAAM,GAAG,CAAC,CAAC;gBAAC,CAAC;YAC9B,CAAC;YACD,yEAAyE;YACzE,2GAA2G;YAC3G,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC;QAC5J,CAAC;IACH,CAAC;IACD,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC;IAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC;IAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC;IAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC;IAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;IACtH,IAAI,UAAU;QAAE,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;AAC3C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;QACnI,YAAY,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,2BAA2B,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAC1G,CAAC;AAaD,oGAAoG;AACpG,MAAM,UAAU,0BAA0B,CAAC,KAA4B,EAAE,QAAkC;IACzG,MAAM,IAAI,GAAG,6BAA6B,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,CAAC,QAAQ,KAAK,UAAU,IAAI,OAAO,QAAQ,CAAC,YAAY,KAAK,UAAU,EAAE,CAAC;QACxG,MAAM,IAAI,yBAAyB,CAAC,yDAAyD,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,UAAU,KAAK,UAAU;QAAE,MAAM,IAAI,yBAAyB,CAAC,2CAA2C,CAAC,CAAC;IACrK,iGAAiG;IACjG,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EACpG,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,aAAa,CAAE,CAAC;IAC1E,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,gBAAgB;QAAE,MAAM,IAAI,yBAAyB,CAAC,kCAAkC,CAAC,CAAC;IACrH,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IAC7D,IAAI,KAAK,GAA8B,OAAO,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,GAAkC,IAAI,CAAC;IACxG,IAAI,KAAK,GAAoC,IAAI,CAAC;IAClD,SAAS,OAAO,KAAW,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;IACzE,SAAS,WAAW;QAClB,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,yBAAyB,CAAC,0DAA0D,CAAC,CAAC;IAC5H,CAAC;IACD,OAAO;QACL,IAAI;QACJ,IAAI,KAAK,KAAgC,OAAO,KAAK,CAAC,CAAC,CAAC;QACxD,IAAI,kBAAkB,KAAa,OAAO,KAAK,CAAC,CAAC,CAAC;QAClD,KAAK,CAAC,GAAG;YACP,IAAI,KAAK,KAAK,OAAO;gBAAE,MAAM,IAAI,yBAAyB,CAAC,gCAAgC,CAAC,CAAC;YAC7F,KAAK,GAAG,SAAS,CAAC;YAClB,IAAI,CAAC;gBACH,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;gBAC/G,MAAM,WAAW,GAAsC,EAAE,4BAA4B,EAAE,CAAC,EAAE,kCAAkC,EAAE,CAAC;oBAC7H,sBAAsB,EAAE,CAAC,EAAE,0BAA0B,EAAE,CAAC,EAAE,2BAA2B,EAAE,CAAC,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAC;gBAChI,uHAAuH;gBACvH,MAAM,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,EAAE,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,CAAC,EACvH,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,yBAAyB,CAAC,UAAU,EAAE,CAAC;oBACjF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,yBAAyB,CAAC,SAAS,EAAE,CAAC;wBAC/E,WAAW,EAAE,CAAC;wBACd,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE;4BACxF,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;4BAC5D,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;4BAC1E,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,yBAAyB,CAAC,UAAU,EAAE,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;wBACnF,CAAC;4BACC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;4BACnD,WAAW,EAAE,CAAC;4BACd,gEAAgE;4BAChE,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAM,EAAE,WAAW,CAAC,CAAC;4BAC9F,KAAK,EAAE,CAAC;4BACR,UAAU,EAAE,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;wBAC9E,CAAC;wBACD,8EAA8E;wBAC9E,MAAM,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;wBACjC,WAAW,EAAE,CAAC;oBAChB,CAAC;gBACH,CAAC;gBACD,MAAM,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,KAAM,EAAE,WAAW,EAAE,cAAc,EAAE,iBAAiB,CAAC,OAAO,CAAC;oBAC9F,iBAAiB,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,SAAS,EAAE,EAAE,CAAC;gBACrG,KAAK,GAAG,WAAW,CAAC;YACtB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAAC,KAAK,GAAG,QAAQ,CAAC;oBAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBAAC,CAAC;gBAC/D,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,MAAM;YACJ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;gBAAC,KAAK,GAAG,WAAW,CAAC;gBAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;QAC3H,CAAC;QACD,OAAO,KAAW,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;YAAC,KAAK,GAAG,UAAU,CAAC;YAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAAC,OAAO,EAAE,CAAC;QAAC,CAAC,CAAC,CAAC;QAClG,UAAU;YACR,IAAI,KAAK,KAAK,WAAW,IAAI,CAAC,MAAM;gBAAE,MAAM,IAAI,yBAAyB,CAAC,mCAAmC,CAAC,CAAC;YAC/G,MAAM,MAAM,GAAG,MAAM,CAAC;YAAC,MAAM,GAAG,IAAI,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC;YAAC,KAAK,GAAG,aAAa,CAAC;YAC1E,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Native SDR rendering of large captures, tile by tile, within fixed memory limits. The engine\n * owns the task; the caller supplies the pixel tiles. See docs/NATIVE_CAPTURE_SDR.md.\n */\nimport { parseSimulatedCapture, type SimulatedCapture, type CaptureLinearPlane } from \"../capture/simulated-capture.js\";\nimport { LINEAR_CAPTURE_RGB_PROFILE, resolveCaptureColorModel } from \"../color/capture-color.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { freezeOwnedData } from \"../core/owned-data.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\nimport { encodeSrgbComponent } from \"../color/srgb-transfer.js\";\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport { parseSdrRenderingProfile, prepareSdrPixelTone, type SdrPixelTone, type SdrRenderingProfile, type SdrRenderingResult } from \"./sdr-rendering.js\";\n\nexport const NATIVE_CAPTURE_SDR_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`. */\nexport const NATIVE_CAPTURE_SDR_LIMITS = /* @__PURE__ */ Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,\n maximumDimension: 16_384, tileWidth: 256, tileHeight: 32, maximumTileStorageSamples: 32_768 });\n/** Input. The capture holds only metadata; float samples are read from external storage. Samples must already have colour and white balance applied. */\nexport interface NativeCaptureSdrInput {\n capture: SimulatedCapture;\n sourcePlaneId: string;\n outputImageStateId: string;\n profile: SdrRenderingProfile;\n /** Limit on the size of the integer output. Checked before anything is allocated or any tile is read. */\n maximumOutputBytes: number;\n /**\n * Optional opt-in pixel limit for large captures: an integer from 1 to `NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels`.\n * Default `NATIVE_CAPTURE_SDR_LIMITS.maximumPixels` (24 000 000). `maximumOutputBytes` still applies.\n */\n maximumPixels?: number;\n}\n/** Tile request. Coordinates are absolute in the selected plane's declared pixel grid, never relative to a crop. */\nexport interface NativeCaptureSdrTileRequest {\n captureId: string;\n sourcePlaneId: string;\n artifactId: string;\n sha256: string;\n x: number; y: number; width: number; height: number;\n}\n/** A tile returned by the provider. It belongs to this call alone; do not change or reuse it. Row padding is not image data. */\nexport interface NativeCaptureSdrTile extends NativeCaptureSdrTileRequest {\n rowStrideSamples: number;\n samples: Float32Array | Float64Array;\n}\n/** Supplies tiles, one call at a time. The caller must release decoder and GPU resources when the task aborts or finishes. */\nexport interface NativeCaptureSdrProvider {\n readTile(request: Readonly<NativeCaptureSdrTileRequest>, signal: AbortSignal): Promise<NativeCaptureSdrTile>;\n /** Must yield to the host event loop, so cancellation and input events can run between tiles. */\n yieldControl(signal: AbortSignal): Promise<void>;\n /**\n * Optional progress observer, called synchronously after each committed tile. It cannot change\n * output; an exception it throws fails the task like any other error.\n */\n onProgress?(progress: NativeTaskProgress): void;\n}\n/** Tile progress of a native task: `completedTileCount` of `totalTileCount` tiles are committed. */\nexport interface NativeTaskProgress { readonly completedTileCount: number; readonly totalTileCount: number }\nexport interface NativeCaptureSdrPlan {\n schemaVersion: typeof NATIVE_CAPTURE_SDR_SCHEMA_VERSION;\n capture: SimulatedCapture;\n sourcePlaneId: string;\n outputImageStateId: string;\n profile: SdrRenderingProfile;\n outputBytes: number;\n /** Upper bound on the bytes held in working arrays. It is not peak JavaScript heap, GPU or process memory, and does not show that a device can run the task. */\n maximumScratchPayloadBytes: number;\n tileCount: number;\n}\nexport interface NativeCaptureSdrOutput {\n plan: NativeCaptureSdrPlan;\n /** Packed RGB codes in row-major order, owned by the caller. 16-bit codes are numbers, not bytes in a particular byte order. */\n integerSamples: Uint8Array | Uint16Array;\n diagnostics: SdrRenderingResult[\"diagnostics\"];\n outputEncoding: SdrRenderingResult[\"outputEncoding\"];\n displayAdaptation: SdrRenderingResult[\"displayAdaptation\"];\n}\nexport type NativeCaptureSdrTaskState = \"ready\" | \"running\" | \"completed\" | \"cancelled\" | \"failed\" | \"disposed\" | \"transferred\";\n\n/** Validates the input and plans the task, including its limits, without reading samples or allocating an image. */\nexport function calculateNativeCaptureSdrPlan(input: NativeCaptureSdrInput): NativeCaptureSdrPlan {\n const r = requireAllowlistedRecord(input, [\"capture\", \"sourcePlaneId\", \"outputImageStateId\", \"profile\", \"maximumOutputBytes\", \"maximumPixels\"], \"Invalid native SDR input.\");\n const maximumPixels = resolveRaisedLimit(r.maximumPixels, NATIVE_CAPTURE_SDR_LIMITS.maximumPixels, NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels,\n \"Native SDR maximumPixels must be an integer from 1 to \" + NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels + \".\");\n // Only external-storage manifests are accepted, so unrelated inline sample buffers are never copied.\n const c = r.capture as SimulatedCapture | undefined;\n if (!c || !Array.isArray(c.planes) || c.planes.length > 32 || c.planes.some(p => !p || p.storage?.kind === \"inline-float64\")) {\n throw new InvalidConfigurationError(\"Native SDR requires external-only capture manifests.\");\n }\n const capture = parseSimulatedCapture(c);\n const sourcePlaneId = requirePublicOpaqueId(r.sourcePlaneId, \"Invalid native SDR plane ID.\");\n const outputImageStateId = requirePublicOpaqueId(r.outputImageStateId, \"Invalid native SDR output state ID.\");\n const plane = capture.planes.find(p => p.id === sourcePlaneId);\n const white = resolveCaptureColorModel().referenceWhiteXyz;\n if (!plane || plane.imageState !== \"color-transformed-linear-rgb\" ||\n plane.colorProfile?.id !== LINEAR_CAPTURE_RGB_PROFILE.id || plane.colorProfile.version !== LINEAR_CAPTURE_RGB_PROFILE.version ||\n plane.encodingReferenceWhiteXyz?.x !== white.x || plane.encodingReferenceWhiteXyz.y !== white.y || plane.encodingReferenceWhiteXyz.z !== white.z ||\n plane.whiteBalanceApplication === \"intent-only\" || (plane.whiteBalanceApplication === \"not-applicable\" && capture.whiteBalanceIntent !== null) ||\n capture.planes.some(p => p.imageStateId === outputImageStateId)) {\n throw new InvalidConfigurationError(\"Native SDR requires exact resolved linear-sRGB/D65 and a new output state ID.\");\n }\n const pixels = plane.pixelWidth * plane.pixelHeight, profile = parseSdrRenderingProfile(r.profile);\n const outputBytes = pixels * 3 * (profile.bitDepth / 8);\n const scale = 2 ** profile.renderingExposureEv / plane.referenceWhiteValue;\n if (!Number.isFinite(scale) || scale <= 0) throw new InvalidConfigurationError(\"Native SDR scale is unrepresentable.\");\n if (pixels > maximumPixels || plane.pixelWidth > NATIVE_CAPTURE_SDR_LIMITS.maximumDimension ||\n plane.pixelHeight > NATIVE_CAPTURE_SDR_LIMITS.maximumDimension || typeof r.maximumOutputBytes !== \"number\" ||\n !Number.isSafeInteger(r.maximumOutputBytes) || r.maximumOutputBytes < outputBytes) {\n throw new InvalidConfigurationError(\"Native SDR raster/output exceeds declared admission limits.\");\n }\n // A cautious declared bound, kept from the earlier executor that called the reference renderer: provider payload +\n // packed copy + reference parser copy + four reference output arrays. The typed kernel itself keeps only the provider payload.\n const maximumScratchPayloadBytes = NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples * 8 +\n NATIVE_CAPTURE_SDR_LIMITS.tileWidth * NATIVE_CAPTURE_SDR_LIMITS.tileHeight * 3 * 8 * 6;\n return freezeOwnedData({ schemaVersion: NATIVE_CAPTURE_SDR_SCHEMA_VERSION, capture, sourcePlaneId, outputImageStateId, profile,\n outputBytes, maximumScratchPayloadBytes,\n tileCount: Math.ceil(plane.pixelWidth / NATIVE_CAPTURE_SDR_LIMITS.tileWidth) * Math.ceil(plane.pixelHeight / NATIVE_CAPTURE_SDR_LIMITS.tileHeight) });\n}\n\n/**\n * Tile checks shared by every native SDR backend: identity, precision, row stride, storage and valid\n * input values. Returns a view of the provider's samples (not a copy). Padding is not checked for\n * finite values. Not exported from the package.\n */\nexport function validateNativeCaptureSdrTile(tile: NativeCaptureSdrTile, request: NativeCaptureSdrTileRequest,\n plane: CaptureLinearPlane): { source: Float32Array | Float64Array; stride: number } {\n const r = requireAllowlistedRecord(tile, [\"captureId\", \"sourcePlaneId\", \"artifactId\", \"sha256\", \"x\", \"y\", \"width\", \"height\", \"rowStrideSamples\", \"samples\"], \"Invalid native SDR tile.\");\n for (const key of Object.keys(request) as (keyof NativeCaptureSdrTileRequest)[]) {\n if (r[key] !== request[key]) throw new InvalidConfigurationError(\"Native SDR tile identity/rectangle mismatch.\");\n }\n const samples = r.samples;\n const expectedType = plane.storage.kind === \"external-float32\" ? Float32Array : Float64Array;\n if (!(samples instanceof expectedType) || !(samples.buffer instanceof ArrayBuffer) ||\n samples.buffer.byteLength > NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples * expectedType.BYTES_PER_ELEMENT ||\n typeof r.rowStrideSamples !== \"number\" || !Number.isSafeInteger(r.rowStrideSamples) || r.rowStrideSamples < request.width * 3 ||\n samples.length !== r.rowStrideSamples * request.height || samples.length > NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples) {\n throw new InvalidConfigurationError(\"Native SDR tile precision/stride/storage mismatch.\");\n }\n const source: Float32Array | Float64Array = samples, stride = r.rowStrideSamples, rowLength = request.width * 3;\n // Check every image sample (not padding) against the reference renderer's input rules before any rendering.\n for (let y = 0; y < request.height; y++) {\n for (let i = y * stride, end = i + rowLength; i < end; i++) {\n if (!Number.isFinite(source[i]!)) throw new InvalidConfigurationError(\"Unsupported SDR input domain/raster.\");\n }\n }\n return { source, stride };\n}\n\ntype NativeSdrDiagnosticCounts = Omit<SdrRenderingResult[\"diagnostics\"], \"captureSaturation\">;\n/**\n * Fast tile kernel on typed arrays. It applies the same equations as the SDR reference renderer\n * (`calculateSdrRendering`), with the same floating-point operation order per sample and the same\n * error precedence, and writes integer codes straight into the packed output.\n *\n * Steps: input check → rendering exposure → tone → gamut → sRGB transfer → quantization (round to\n * nearest, ties up). `test/native-capture-sdr-kernel-parity.test.ts` shows that codes and diagnostics\n * match the unchanged reference exactly.\n */\nfunction renderTileInto(tile: NativeCaptureSdrTile, request: NativeCaptureSdrTileRequest, plane: CaptureLinearPlane,\n profile: SdrRenderingProfile, tone: SdrPixelTone, scale: number, maximum: number, codes: Uint8Array | Uint16Array, diagnostics: NativeSdrDiagnosticCounts): void {\n const { source, stride } = validateNativeCaptureSdrTile(tile, request, plane);\n // Tiling is safe because every supported rendering operation works on one pixel at a time.\n const outputRowStride = plane.pixelWidth * 3;\n const counts = { negative: 0, above: 0, changed: 0, low: 0, high: 0, outOfGamut: false }, scratch = new Float64Array(3);\n for (let y = 0; y < request.height; y++) {\n renderSdrRunInto(source, y * stride, request.width, profile, tone, scale, maximum, codes, (request.y + y) * outputRowStride + request.x * 3, scratch, counts);\n }\n if (counts.outOfGamut) throw new InvalidConfigurationError(\"Tone output exceeds the declared SDR gamut range.\");\n diagnostics.renderingNegativeSampleCount += counts.negative; diagnostics.renderingAboveReferenceSampleCount += counts.above;\n diagnostics.toneChangedSampleCount += counts.changed; diagnostics.gamutClippedLowSampleCount += counts.low; diagnostics.gamutClippedHighSampleCount += counts.high;\n}\n/** Reference 8-bit code of a linear sample in [0,1]: Math.round(encodeSrgbComponent(x)·255). */\nfunction referenceSrgb8(linear: number): number { return Math.round(encodeSrgbComponent(linear) * 255); }\n/** Exact 8-bit sRGB quantizer tables; see {@link srgb8Quantizer}. */\nexport interface Srgb8Quantizer {\n /** t[k]: smallest binary64 x in [0,1] with referenceSrgb8(x) ≥ k; t[256] = +∞ sentinel. */\n readonly thresholds: Float64Array;\n /** start[b]: reference code of b/4096, the first code of bucket [b/4096, (b+1)/4096). */\n readonly start: Uint8Array;\n}\nlet srgb8: Srgb8Quantizer | null | undefined;\n/**\n * Exact 8-bit sRGB quantization without evaluating the transfer curve for each sample.\n *\n * The code thresholds are found by bisection on this engine's own `encodeSrgbComponent`. Comparing\n * against them gives Math.round(encodeSrgbComponent(x)·255) for every x, provided that function never\n * decreases (true when pow is monotone). Construction checks every threshold and the 16 neighbouring\n * doubles on each side. If any check fails, the direct transfer is used instead, so output never\n * silently depends on that assumption.\n *\n * A 4097-entry bucket index (x·4096 is exact) gives the starting code. Even the steepest part of the\n * curve (slope 12.92·255) covers less than one code per bucket, so one or two comparisons finish.\n */\nfunction srgb8Quantizer(): Srgb8Quantizer | null {\n if (srgb8 !== undefined) return srgb8;\n const t = new Float64Array(257), bits = new Float64Array(1), word = new BigUint64Array(bits.buffer);\n const step = (x: number, n: number): number => { bits[0] = x; word[0] = word[0]! + BigInt(n); return bits[0]!; };\n for (let k = 1; k < 256; k++) {\n let lo = t[k - 1]!, hi = 1;\n for (;;) {\n const mid = lo + (hi - lo) / 2;\n if (mid === lo || mid === hi) break;\n if (referenceSrgb8(mid) >= k) hi = mid; else lo = mid;\n }\n t[k] = hi;\n }\n t[256] = Infinity;\n let valid = referenceSrgb8(0) === 0 && referenceSrgb8(1) === 255;\n for (let k = 1; valid && k < 256; k++) {\n const x = t[k]!;\n if (!(x > t[k - 1]!) || referenceSrgb8(x) !== k) valid = false;\n for (let n = 1; valid && n <= 16; n++) {\n const below = step(x, -n), above = step(x, n);\n if ((below >= 0 && referenceSrgb8(below) >= k) || (above <= 1 && referenceSrgb8(above) < k)) valid = false;\n }\n }\n const start = new Uint8Array(4097);\n for (let i = 0; valid && i <= 4096; i++) { start[i] = referenceSrgb8(i / 4096); if (i > 0 && start[i]! < start[i - 1]!) valid = false; }\n srgb8 = valid ? { thresholds: t, start } : null;\n return srgb8;\n}\n/** Largest k with t[k] ≤ x for x in [0,1]. */\nfunction quantizeSrgb8(q: Srgb8Quantizer, x: number): number {\n const t = q.thresholds;\n let k = q.start[(x * 4096) | 0]!;\n while (x >= t[k + 1]!) k++;\n return k;\n}\n/** Internal, for tests: the exact quantizer (null when its construction check failed). */\nexport function nativeSrgb8QuantizerForTest(): { quantizer: Srgb8Quantizer | null; quantize(x: number): number } {\n const quantizer = srgb8Quantizer();\n return { quantizer, quantize: (x) => quantizer ? quantizeSrgb8(quantizer, x) : referenceSrgb8(x) };\n}\n/** Per-tile counters for {@link renderSdrRunInto}. `outOfGamut` delays the rejection, as the reference does. */\nexport interface SdrPixelCounts { negative: number; above: number; changed: number; low: number; high: number; outOfGamut: boolean }\n/**\n * Internal SDR kernel on typed arrays, shared by native SDR and RAW development.\n *\n * For `pixelCount` interleaved linear-sRGB pixels starting at `source[start]`, it runs exactly the\n * reference (`calculateSdrRendering`) operations in the same order per pixel: -0 → +0 → rendering\n * exposure → shared tone operator → gamut → sRGB transfer → quantization (nearest, ties up) into\n * `codes[outOffset]` (with `maximum` 0, the encoded value unquantized). A gamut rejection is delayed via `counts.outOfGamut`, because the reference\n * finishes the whole tone pass (and its exposure errors) first. `scratch` must hold at least 3\n * values. Not exported from the package.\n */\nexport function renderSdrRunInto(source: Float32Array | Float64Array, start: number, pixelCount: number, profile: SdrRenderingProfile,\n tone: SdrPixelTone, scale: number, maximum: number, codes: Uint8Array | Uint16Array | Float64Array, outOffset: number, scratch: Float64Array, counts: SdrPixelCounts): void {\n const reject = profile.gamutHandling === \"reject-out-of-range\", quantizer = maximum === 255 ? srgb8Quantizer() : null;\n let negative = 0, above = 0, changed = 0, low = 0, high = 0, outOfGamut = false;\n for (let p = 0, i = start, o = outOffset; p < pixelCount; p++, i += 3) {\n // The reference parser normalizes -0 to +0 before exposure.\n const red = source[i]!, green = source[i + 1]!, blue = source[i + 2]!;\n const er = (red === 0 ? 0 : red) * scale, eg = (green === 0 ? 0 : green) * scale, eb = (blue === 0 ? 0 : blue) * scale;\n if (!Number.isFinite(er) || !Number.isFinite(eg) || !Number.isFinite(eb)) throw new InvalidScientificResultError(\"Rendering exposure produced a non-finite sample.\");\n tone(er, eg, eb, scratch);\n for (let c = 0; c < 3; c++, o++) {\n const exposed = c === 0 ? er : c === 1 ? eg : eb;\n if (exposed < 0) negative++;\n if (exposed > 1) above++;\n let mapped = scratch[c]!;\n if (mapped !== exposed) changed++;\n if (mapped === 0) mapped = 0;\n let linear = mapped;\n if (mapped < 0 || mapped > 1) {\n if (reject) outOfGamut = true;\n else if (mapped < 0) { low++; linear = 0; }\n else { high++; linear = 1; }\n }\n // Encoded values are in [0,1]: Math.round is exact nearest-ties-up here.\n // maximum 0 (#571): the encoded value itself, unquantized, into a Float64Array (the unsharp mask's input).\n codes[o] = quantizer ? quantizeSrgb8(quantizer, linear) : maximum === 0 ? encodeSrgbComponent(linear) : Math.round(encodeSrgbComponent(linear) * maximum);\n }\n }\n counts.negative += negative; counts.above += above; counts.changed += changed; counts.low += low; counts.high += high;\n if (outOfGamut) counts.outOfGamut = true;\n}\n\n/** The reference's output-encoding declaration for the given code maximum. */\nexport function sdrOutputEncoding(maximum: number): SdrRenderingResult[\"outputEncoding\"] {\n return { imageState: \"output-referred-sdr\", colorSpace: \"srgb\", primariesXy: { red: [.64, .33], green: [.30, .60], blue: [.15, .06] },\n whitePointXy: [.3127, .3290], referenceWhiteLuminanceCdM2: 80, codeMinimum: 0, codeMaximum: maximum };\n}\n\n/** A single-use task. No partial output is ever returned, and cancel or dispose stops a late result from being published. */\nexport interface NativeCaptureSdrTask {\n readonly plan: NativeCaptureSdrPlan;\n readonly state: NativeCaptureSdrTaskState;\n readonly completedTileCount: number;\n run(): Promise<void>;\n cancel(): void;\n dispose(): void;\n /** Hands over the only copy of the output buffer, once. Dispose cannot take back a buffer already handed over. */\n takeOutput(): NativeCaptureSdrOutput;\n}\n/** Creates a single-use task. Limits are checked before any buffer is allocated or tile is read. */\nexport function createNativeCaptureSdrTask(input: NativeCaptureSdrInput, provider: NativeCaptureSdrProvider): NativeCaptureSdrTask {\n const plan = calculateNativeCaptureSdrPlan(input);\n if (!provider || typeof provider.readTile !== \"function\" || typeof provider.yieldControl !== \"function\") {\n throw new InvalidConfigurationError(\"Native SDR requires a tile reader and event-loop yield.\");\n }\n if (provider.onProgress !== undefined && typeof provider.onProgress !== \"function\") throw new InvalidConfigurationError(\"Native SDR onProgress must be a function.\");\n // Also capture the callback functions now, so later app changes cannot swap the reader mid-task.\n const readTile = provider.readTile.bind(provider), yieldControl = provider.yieldControl.bind(provider),\n onProgress = provider.onProgress?.bind(provider);\n const plane = plan.capture.planes.find(p => p.id === plan.sourcePlaneId)!;\n if (plane.storage.kind === \"inline-float64\") throw new InvalidConfigurationError(\"External native source required.\");\n const storage = plane.storage, abort = new AbortController();\n let state: NativeCaptureSdrTaskState = \"ready\", count = 0, output: NativeCaptureSdrOutput | null = null;\n let codes: Uint8Array | Uint16Array | null = null;\n function release(): void { codes?.fill(0); codes = null; output = null; }\n function checkActive(): void {\n if (abort.signal.aborted) throw new InvalidConfigurationError(\"Native SDR task cancelled/disposed; no output available.\");\n }\n return {\n plan,\n get state(): NativeCaptureSdrTaskState { return state; },\n get completedTileCount(): number { return count; },\n async run(): Promise<void> {\n if (state !== \"ready\") throw new InvalidConfigurationError(\"Native SDR task is single-use.\");\n state = \"running\";\n try {\n codes = plan.profile.bitDepth === 8 ? new Uint8Array(plan.outputBytes) : new Uint16Array(plan.outputBytes / 2);\n const diagnostics: SdrRenderingResult[\"diagnostics\"] = { renderingNegativeSampleCount: 0, renderingAboveReferenceSampleCount: 0,\n toneChangedSampleCount: 0, gamutClippedLowSampleCount: 0, gamutClippedHighSampleCount: 0, captureSaturation: \"not-consumed\" };\n // Same scale expression and code range as the reference. The plan already rejected a scale that cannot be represented.\n const scale = 2 ** plan.profile.renderingExposureEv / plane.referenceWhiteValue, maximum = 2 ** plan.profile.bitDepth - 1,\n tone = prepareSdrPixelTone(plan.profile);\n for (let y = 0; y < plane.pixelHeight; y += NATIVE_CAPTURE_SDR_LIMITS.tileHeight) {\n for (let x = 0; x < plane.pixelWidth; x += NATIVE_CAPTURE_SDR_LIMITS.tileWidth) {\n checkActive();\n const request = Object.freeze({ captureId: plan.capture.captureId, sourcePlaneId: plane.id,\n artifactId: storage.artifactId, sha256: storage.sha256, x, y,\n width: Math.min(NATIVE_CAPTURE_SDR_LIMITS.tileWidth, plane.pixelWidth - x),\n height: Math.min(NATIVE_CAPTURE_SDR_LIMITS.tileHeight, plane.pixelHeight - y) });\n {\n const tile = await readTile(request, abort.signal);\n checkActive();\n // Diagnostics are aggregated only after the whole tile renders.\n renderTileInto(tile, request, plane, plan.profile, tone, scale, maximum, codes!, diagnostics);\n count++;\n onProgress?.({ completedTileCount: count, totalTileCount: plan.tileCount });\n }\n // Do not keep tile or reference arrays past the next tile read or host yield.\n await yieldControl(abort.signal);\n checkActive();\n }\n }\n output = { plan, integerSamples: codes!, diagnostics, outputEncoding: sdrOutputEncoding(maximum),\n displayAdaptation: { kind: \"external-platform\", applied: false, displayCapabilities: \"unknown\" } };\n state = \"completed\";\n } catch (error) {\n release();\n if (!abort.signal.aborted) { state = \"failed\"; abort.abort(); }\n throw error;\n }\n },\n cancel(): void {\n if (state === \"ready\" || state === \"running\" || state === \"completed\") { state = \"cancelled\"; abort.abort(); release(); }\n },\n dispose(): void { if (state !== \"transferred\") { state = \"disposed\"; abort.abort(); release(); } },\n takeOutput(): NativeCaptureSdrOutput {\n if (state !== \"completed\" || !output) throw new InvalidConfigurationError(\"Native SDR output is unavailable.\");\n const result = output; output = null; codes = null; state = \"transferred\";\n return result;\n }\n };\n}\n"]}
1
+ {"version":3,"file":"native-capture-sdr.js","sourceRoot":"","sources":["../../src/output/native-capture-sdr.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Native SDR rendering of large captures, tile by tile, within fixed memory limits. The engine\n * owns the task; the caller supplies the pixel tiles. See docs/NATIVE_CAPTURE_SDR.md.\n */\nimport { parseSimulatedCapture, type SimulatedCapture, type CaptureLinearPlane } from \"../capture/simulated-capture.js\";\nimport { LINEAR_CAPTURE_RGB_PROFILE, resolveCaptureColorModel } from \"../color/capture-color.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { freezeOwnedData } from \"../core/owned-data.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\nimport { encodeSrgbComponent } from \"../color/srgb-transfer.js\";\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport { desaturateToLuminance, parseSdrRenderingProfile, prepareSdrPixelTone, type SdrPixelTone, type SdrRenderingProfile, type SdrRenderingResult } from \"./sdr-rendering.js\";\n\nexport const NATIVE_CAPTURE_SDR_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`. */\nexport const NATIVE_CAPTURE_SDR_LIMITS = /* @__PURE__ */ Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,\n maximumDimension: 16_384, tileWidth: 256, tileHeight: 32, maximumTileStorageSamples: 32_768 });\n/** Input. The capture holds only metadata; float samples are read from external storage. Samples must already have colour and white balance applied. */\nexport interface NativeCaptureSdrInput {\n capture: SimulatedCapture;\n sourcePlaneId: string;\n outputImageStateId: string;\n profile: SdrRenderingProfile;\n /** Limit on the size of the integer output. Checked before anything is allocated or any tile is read. */\n maximumOutputBytes: number;\n /**\n * Optional opt-in pixel limit for large captures: an integer from 1 to `NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels`.\n * Default `NATIVE_CAPTURE_SDR_LIMITS.maximumPixels` (24 000 000). `maximumOutputBytes` still applies.\n */\n maximumPixels?: number;\n}\n/** Tile request. Coordinates are absolute in the selected plane's declared pixel grid, never relative to a crop. */\nexport interface NativeCaptureSdrTileRequest {\n captureId: string;\n sourcePlaneId: string;\n artifactId: string;\n sha256: string;\n x: number; y: number; width: number; height: number;\n}\n/** A tile returned by the provider. It belongs to this call alone; do not change or reuse it. Row padding is not image data. */\nexport interface NativeCaptureSdrTile extends NativeCaptureSdrTileRequest {\n rowStrideSamples: number;\n samples: Float32Array | Float64Array;\n}\n/** Supplies tiles, one call at a time. The caller must release decoder and GPU resources when the task aborts or finishes. */\nexport interface NativeCaptureSdrProvider {\n readTile(request: Readonly<NativeCaptureSdrTileRequest>, signal: AbortSignal): Promise<NativeCaptureSdrTile>;\n /** Must yield to the host event loop, so cancellation and input events can run between tiles. */\n yieldControl(signal: AbortSignal): Promise<void>;\n /**\n * Optional progress observer, called synchronously after each committed tile. It cannot change\n * output; an exception it throws fails the task like any other error.\n */\n onProgress?(progress: NativeTaskProgress): void;\n}\n/** Tile progress of a native task: `completedTileCount` of `totalTileCount` tiles are committed. */\nexport interface NativeTaskProgress { readonly completedTileCount: number; readonly totalTileCount: number }\nexport interface NativeCaptureSdrPlan {\n schemaVersion: typeof NATIVE_CAPTURE_SDR_SCHEMA_VERSION;\n capture: SimulatedCapture;\n sourcePlaneId: string;\n outputImageStateId: string;\n profile: SdrRenderingProfile;\n outputBytes: number;\n /** Upper bound on the bytes held in working arrays. It is not peak JavaScript heap, GPU or process memory, and does not show that a device can run the task. */\n maximumScratchPayloadBytes: number;\n tileCount: number;\n}\nexport interface NativeCaptureSdrOutput {\n plan: NativeCaptureSdrPlan;\n /** Packed RGB codes in row-major order, owned by the caller. 16-bit codes are numbers, not bytes in a particular byte order. */\n integerSamples: Uint8Array | Uint16Array;\n diagnostics: SdrRenderingResult[\"diagnostics\"];\n outputEncoding: SdrRenderingResult[\"outputEncoding\"];\n displayAdaptation: SdrRenderingResult[\"displayAdaptation\"];\n}\nexport type NativeCaptureSdrTaskState = \"ready\" | \"running\" | \"completed\" | \"cancelled\" | \"failed\" | \"disposed\" | \"transferred\";\n\n/** Validates the input and plans the task, including its limits, without reading samples or allocating an image. */\nexport function calculateNativeCaptureSdrPlan(input: NativeCaptureSdrInput): NativeCaptureSdrPlan {\n const r = requireAllowlistedRecord(input, [\"capture\", \"sourcePlaneId\", \"outputImageStateId\", \"profile\", \"maximumOutputBytes\", \"maximumPixels\"], \"Invalid native SDR input.\");\n const maximumPixels = resolveRaisedLimit(r.maximumPixels, NATIVE_CAPTURE_SDR_LIMITS.maximumPixels, NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels,\n \"Native SDR maximumPixels must be an integer from 1 to \" + NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels + \".\");\n // Only external-storage manifests are accepted, so unrelated inline sample buffers are never copied.\n const c = r.capture as SimulatedCapture | undefined;\n if (!c || !Array.isArray(c.planes) || c.planes.length > 32 || c.planes.some(p => !p || p.storage?.kind === \"inline-float64\")) {\n throw new InvalidConfigurationError(\"Native SDR requires external-only capture manifests.\");\n }\n const capture = parseSimulatedCapture(c);\n const sourcePlaneId = requirePublicOpaqueId(r.sourcePlaneId, \"Invalid native SDR plane ID.\");\n const outputImageStateId = requirePublicOpaqueId(r.outputImageStateId, \"Invalid native SDR output state ID.\");\n const plane = capture.planes.find(p => p.id === sourcePlaneId);\n const white = resolveCaptureColorModel().referenceWhiteXyz;\n if (!plane || plane.imageState !== \"color-transformed-linear-rgb\" ||\n plane.colorProfile?.id !== LINEAR_CAPTURE_RGB_PROFILE.id || plane.colorProfile.version !== LINEAR_CAPTURE_RGB_PROFILE.version ||\n plane.encodingReferenceWhiteXyz?.x !== white.x || plane.encodingReferenceWhiteXyz.y !== white.y || plane.encodingReferenceWhiteXyz.z !== white.z ||\n plane.whiteBalanceApplication === \"intent-only\" || (plane.whiteBalanceApplication === \"not-applicable\" && capture.whiteBalanceIntent !== null) ||\n capture.planes.some(p => p.imageStateId === outputImageStateId)) {\n throw new InvalidConfigurationError(\"Native SDR requires exact resolved linear-sRGB/D65 and a new output state ID.\");\n }\n const pixels = plane.pixelWidth * plane.pixelHeight, profile = parseSdrRenderingProfile(r.profile);\n const outputBytes = pixels * 3 * (profile.bitDepth / 8);\n const scale = 2 ** profile.renderingExposureEv / plane.referenceWhiteValue;\n if (!Number.isFinite(scale) || scale <= 0) throw new InvalidConfigurationError(\"Native SDR scale is unrepresentable.\");\n if (pixels > maximumPixels || plane.pixelWidth > NATIVE_CAPTURE_SDR_LIMITS.maximumDimension ||\n plane.pixelHeight > NATIVE_CAPTURE_SDR_LIMITS.maximumDimension || typeof r.maximumOutputBytes !== \"number\" ||\n !Number.isSafeInteger(r.maximumOutputBytes) || r.maximumOutputBytes < outputBytes) {\n throw new InvalidConfigurationError(\"Native SDR raster/output exceeds declared admission limits.\");\n }\n // A cautious declared bound, kept from the earlier executor that called the reference renderer: provider payload +\n // packed copy + reference parser copy + four reference output arrays. The typed kernel itself keeps only the provider payload.\n const maximumScratchPayloadBytes = NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples * 8 +\n NATIVE_CAPTURE_SDR_LIMITS.tileWidth * NATIVE_CAPTURE_SDR_LIMITS.tileHeight * 3 * 8 * 6;\n return freezeOwnedData({ schemaVersion: NATIVE_CAPTURE_SDR_SCHEMA_VERSION, capture, sourcePlaneId, outputImageStateId, profile,\n outputBytes, maximumScratchPayloadBytes,\n tileCount: Math.ceil(plane.pixelWidth / NATIVE_CAPTURE_SDR_LIMITS.tileWidth) * Math.ceil(plane.pixelHeight / NATIVE_CAPTURE_SDR_LIMITS.tileHeight) });\n}\n\n/**\n * Tile checks shared by every native SDR backend: identity, precision, row stride, storage and valid\n * input values. Returns a view of the provider's samples (not a copy). Padding is not checked for\n * finite values. Not exported from the package.\n */\nexport function validateNativeCaptureSdrTile(tile: NativeCaptureSdrTile, request: NativeCaptureSdrTileRequest,\n plane: CaptureLinearPlane): { source: Float32Array | Float64Array; stride: number } {\n const r = requireAllowlistedRecord(tile, [\"captureId\", \"sourcePlaneId\", \"artifactId\", \"sha256\", \"x\", \"y\", \"width\", \"height\", \"rowStrideSamples\", \"samples\"], \"Invalid native SDR tile.\");\n for (const key of Object.keys(request) as (keyof NativeCaptureSdrTileRequest)[]) {\n if (r[key] !== request[key]) throw new InvalidConfigurationError(\"Native SDR tile identity/rectangle mismatch.\");\n }\n const samples = r.samples;\n const expectedType = plane.storage.kind === \"external-float32\" ? Float32Array : Float64Array;\n if (!(samples instanceof expectedType) || !(samples.buffer instanceof ArrayBuffer) ||\n samples.buffer.byteLength > NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples * expectedType.BYTES_PER_ELEMENT ||\n typeof r.rowStrideSamples !== \"number\" || !Number.isSafeInteger(r.rowStrideSamples) || r.rowStrideSamples < request.width * 3 ||\n samples.length !== r.rowStrideSamples * request.height || samples.length > NATIVE_CAPTURE_SDR_LIMITS.maximumTileStorageSamples) {\n throw new InvalidConfigurationError(\"Native SDR tile precision/stride/storage mismatch.\");\n }\n const source: Float32Array | Float64Array = samples, stride = r.rowStrideSamples, rowLength = request.width * 3;\n // Check every image sample (not padding) against the reference renderer's input rules before any rendering.\n for (let y = 0; y < request.height; y++) {\n for (let i = y * stride, end = i + rowLength; i < end; i++) {\n if (!Number.isFinite(source[i]!)) throw new InvalidConfigurationError(\"Unsupported SDR input domain/raster.\");\n }\n }\n return { source, stride };\n}\n\ntype NativeSdrDiagnosticCounts = Omit<SdrRenderingResult[\"diagnostics\"], \"captureSaturation\">;\n/**\n * Fast tile kernel on typed arrays. It applies the same equations as the SDR reference renderer\n * (`calculateSdrRendering`), with the same floating-point operation order per sample and the same\n * error precedence, and writes integer codes straight into the packed output.\n *\n * Steps: input check → rendering exposure → tone → gamut → sRGB transfer → quantization (round to\n * nearest, ties up). `test/native-capture-sdr-kernel-parity.test.ts` shows that codes and diagnostics\n * match the unchanged reference exactly.\n */\nfunction renderTileInto(tile: NativeCaptureSdrTile, request: NativeCaptureSdrTileRequest, plane: CaptureLinearPlane,\n profile: SdrRenderingProfile, tone: SdrPixelTone, scale: number, maximum: number, codes: Uint8Array | Uint16Array, diagnostics: NativeSdrDiagnosticCounts): void {\n const { source, stride } = validateNativeCaptureSdrTile(tile, request, plane);\n // Tiling is safe because every supported rendering operation works on one pixel at a time.\n const outputRowStride = plane.pixelWidth * 3;\n const counts = { negative: 0, above: 0, changed: 0, low: 0, high: 0, outOfGamut: false }, scratch = new Float64Array(3);\n for (let y = 0; y < request.height; y++) {\n renderSdrRunInto(source, y * stride, request.width, profile, tone, scale, maximum, codes, (request.y + y) * outputRowStride + request.x * 3, scratch, counts);\n }\n if (counts.outOfGamut) throw new InvalidConfigurationError(\"Tone output exceeds the declared SDR gamut range.\");\n diagnostics.renderingNegativeSampleCount += counts.negative; diagnostics.renderingAboveReferenceSampleCount += counts.above;\n diagnostics.toneChangedSampleCount += counts.changed; diagnostics.gamutClippedLowSampleCount += counts.low; diagnostics.gamutClippedHighSampleCount += counts.high;\n}\n/** Reference 8-bit code of a linear sample in [0,1]: Math.round(encodeSrgbComponent(x)·255). */\nfunction referenceSrgb8(linear: number): number { return Math.round(encodeSrgbComponent(linear) * 255); }\n/** Exact 8-bit sRGB quantizer tables; see {@link srgb8Quantizer}. */\nexport interface Srgb8Quantizer {\n /** t[k]: smallest binary64 x in [0,1] with referenceSrgb8(x) ≥ k; t[256] = +∞ sentinel. */\n readonly thresholds: Float64Array;\n /** start[b]: reference code of b/4096, the first code of bucket [b/4096, (b+1)/4096). */\n readonly start: Uint8Array;\n}\nlet srgb8: Srgb8Quantizer | null | undefined;\n/**\n * Exact 8-bit sRGB quantization without evaluating the transfer curve for each sample.\n *\n * The code thresholds are found by bisection on this engine's own `encodeSrgbComponent`. Comparing\n * against them gives Math.round(encodeSrgbComponent(x)·255) for every x, provided that function never\n * decreases (true when pow is monotone). Construction checks every threshold and the 16 neighbouring\n * doubles on each side. If any check fails, the direct transfer is used instead, so output never\n * silently depends on that assumption.\n *\n * A 4097-entry bucket index (x·4096 is exact) gives the starting code. Even the steepest part of the\n * curve (slope 12.92·255) covers less than one code per bucket, so one or two comparisons finish.\n */\nfunction srgb8Quantizer(): Srgb8Quantizer | null {\n if (srgb8 !== undefined) return srgb8;\n const t = new Float64Array(257), bits = new Float64Array(1), word = new BigUint64Array(bits.buffer);\n const step = (x: number, n: number): number => { bits[0] = x; word[0] = word[0]! + BigInt(n); return bits[0]!; };\n for (let k = 1; k < 256; k++) {\n let lo = t[k - 1]!, hi = 1;\n for (;;) {\n const mid = lo + (hi - lo) / 2;\n if (mid === lo || mid === hi) break;\n if (referenceSrgb8(mid) >= k) hi = mid; else lo = mid;\n }\n t[k] = hi;\n }\n t[256] = Infinity;\n let valid = referenceSrgb8(0) === 0 && referenceSrgb8(1) === 255;\n for (let k = 1; valid && k < 256; k++) {\n const x = t[k]!;\n if (!(x > t[k - 1]!) || referenceSrgb8(x) !== k) valid = false;\n for (let n = 1; valid && n <= 16; n++) {\n const below = step(x, -n), above = step(x, n);\n if ((below >= 0 && referenceSrgb8(below) >= k) || (above <= 1 && referenceSrgb8(above) < k)) valid = false;\n }\n }\n const start = new Uint8Array(4097);\n for (let i = 0; valid && i <= 4096; i++) { start[i] = referenceSrgb8(i / 4096); if (i > 0 && start[i]! < start[i - 1]!) valid = false; }\n srgb8 = valid ? { thresholds: t, start } : null;\n return srgb8;\n}\n/** Largest k with t[k] ≤ x for x in [0,1]. */\nfunction quantizeSrgb8(q: Srgb8Quantizer, x: number): number {\n const t = q.thresholds;\n let k = q.start[(x * 4096) | 0]!;\n while (x >= t[k + 1]!) k++;\n return k;\n}\n/** Internal, for tests: the exact quantizer (null when its construction check failed). */\nexport function nativeSrgb8QuantizerForTest(): { quantizer: Srgb8Quantizer | null; quantize(x: number): number } {\n const quantizer = srgb8Quantizer();\n return { quantizer, quantize: (x) => quantizer ? quantizeSrgb8(quantizer, x) : referenceSrgb8(x) };\n}\n/** Per-tile counters for {@link renderSdrRunInto}. `outOfGamut` delays the rejection, as the reference does. */\nexport interface SdrPixelCounts { negative: number; above: number; changed: number; low: number; high: number; outOfGamut: boolean }\n/**\n * Internal SDR kernel on typed arrays, shared by native SDR and RAW development.\n *\n * For `pixelCount` interleaved linear-sRGB pixels starting at `source[start]`, it runs exactly the\n * reference (`calculateSdrRendering`) operations in the same order per pixel: -0 → +0 → rendering\n * exposure → shared tone operator → gamut → sRGB transfer → quantization (nearest, ties up) into\n * `codes[outOffset]` (with `maximum` 0, the encoded value unquantized). A gamut rejection is delayed via `counts.outOfGamut`, because the reference\n * finishes the whole tone pass (and its exposure errors) first. `scratch` must hold at least 3\n * values. Not exported from the package.\n */\nexport function renderSdrRunInto(source: Float32Array | Float64Array, start: number, pixelCount: number, profile: SdrRenderingProfile,\n tone: SdrPixelTone, scale: number, maximum: number, codes: Uint8Array | Uint16Array | Float64Array, outOffset: number, scratch: Float64Array, counts: SdrPixelCounts): void {\n const reject = profile.gamutHandling === \"reject-out-of-range\", desaturate = profile.gamutHandling === \"desaturate-to-luminance\", quantizer = maximum === 255 ? srgb8Quantizer() : null;\n let negative = 0, above = 0, changed = 0, low = 0, high = 0, outOfGamut = false;\n for (let p = 0, i = start, o = outOffset; p < pixelCount; p++, i += 3) {\n // The reference parser normalizes -0 to +0 before exposure.\n const red = source[i]!, green = source[i + 1]!, blue = source[i + 2]!;\n const er = (red === 0 ? 0 : red) * scale, eg = (green === 0 ? 0 : green) * scale, eb = (blue === 0 ? 0 : blue) * scale;\n if (!Number.isFinite(er) || !Number.isFinite(eg) || !Number.isFinite(eb)) throw new InvalidScientificResultError(\"Rendering exposure produced a non-finite sample.\");\n tone(er, eg, eb, scratch);\n // #601: the hue-preserving desaturation, before the components are counted and clipped.\n if (desaturate) desaturateToLuminance(scratch);\n for (let c = 0; c < 3; c++, o++) {\n const exposed = c === 0 ? er : c === 1 ? eg : eb;\n if (exposed < 0) negative++;\n if (exposed > 1) above++;\n let mapped = scratch[c]!;\n if (mapped !== exposed) changed++;\n if (mapped === 0) mapped = 0;\n let linear = mapped;\n if (mapped < 0 || mapped > 1) {\n if (reject) outOfGamut = true;\n else if (mapped < 0) { low++; linear = 0; }\n else { high++; linear = 1; }\n }\n // Encoded values are in [0,1]: Math.round is exact nearest-ties-up here.\n // maximum 0 (#571): the encoded value itself, unquantized, into a Float64Array (the unsharp mask's input).\n codes[o] = quantizer ? quantizeSrgb8(quantizer, linear) : maximum === 0 ? encodeSrgbComponent(linear) : Math.round(encodeSrgbComponent(linear) * maximum);\n }\n }\n counts.negative += negative; counts.above += above; counts.changed += changed; counts.low += low; counts.high += high;\n if (outOfGamut) counts.outOfGamut = true;\n}\n\n/** The reference's output-encoding declaration for the given code maximum. */\nexport function sdrOutputEncoding(maximum: number): SdrRenderingResult[\"outputEncoding\"] {\n return { imageState: \"output-referred-sdr\", colorSpace: \"srgb\", primariesXy: { red: [.64, .33], green: [.30, .60], blue: [.15, .06] },\n whitePointXy: [.3127, .3290], referenceWhiteLuminanceCdM2: 80, codeMinimum: 0, codeMaximum: maximum };\n}\n\n/** A single-use task. No partial output is ever returned, and cancel or dispose stops a late result from being published. */\nexport interface NativeCaptureSdrTask {\n readonly plan: NativeCaptureSdrPlan;\n readonly state: NativeCaptureSdrTaskState;\n readonly completedTileCount: number;\n run(): Promise<void>;\n cancel(): void;\n dispose(): void;\n /** Hands over the only copy of the output buffer, once. Dispose cannot take back a buffer already handed over. */\n takeOutput(): NativeCaptureSdrOutput;\n}\n/** Creates a single-use task. Limits are checked before any buffer is allocated or tile is read. */\nexport function createNativeCaptureSdrTask(input: NativeCaptureSdrInput, provider: NativeCaptureSdrProvider): NativeCaptureSdrTask {\n const plan = calculateNativeCaptureSdrPlan(input);\n if (!provider || typeof provider.readTile !== \"function\" || typeof provider.yieldControl !== \"function\") {\n throw new InvalidConfigurationError(\"Native SDR requires a tile reader and event-loop yield.\");\n }\n if (provider.onProgress !== undefined && typeof provider.onProgress !== \"function\") throw new InvalidConfigurationError(\"Native SDR onProgress must be a function.\");\n // Also capture the callback functions now, so later app changes cannot swap the reader mid-task.\n const readTile = provider.readTile.bind(provider), yieldControl = provider.yieldControl.bind(provider),\n onProgress = provider.onProgress?.bind(provider);\n const plane = plan.capture.planes.find(p => p.id === plan.sourcePlaneId)!;\n if (plane.storage.kind === \"inline-float64\") throw new InvalidConfigurationError(\"External native source required.\");\n const storage = plane.storage, abort = new AbortController();\n let state: NativeCaptureSdrTaskState = \"ready\", count = 0, output: NativeCaptureSdrOutput | null = null;\n let codes: Uint8Array | Uint16Array | null = null;\n function release(): void { codes?.fill(0); codes = null; output = null; }\n function checkActive(): void {\n if (abort.signal.aborted) throw new InvalidConfigurationError(\"Native SDR task cancelled/disposed; no output available.\");\n }\n return {\n plan,\n get state(): NativeCaptureSdrTaskState { return state; },\n get completedTileCount(): number { return count; },\n async run(): Promise<void> {\n if (state !== \"ready\") throw new InvalidConfigurationError(\"Native SDR task is single-use.\");\n state = \"running\";\n try {\n codes = plan.profile.bitDepth === 8 ? new Uint8Array(plan.outputBytes) : new Uint16Array(plan.outputBytes / 2);\n const diagnostics: SdrRenderingResult[\"diagnostics\"] = { renderingNegativeSampleCount: 0, renderingAboveReferenceSampleCount: 0,\n toneChangedSampleCount: 0, gamutClippedLowSampleCount: 0, gamutClippedHighSampleCount: 0, captureSaturation: \"not-consumed\" };\n // Same scale expression and code range as the reference. The plan already rejected a scale that cannot be represented.\n const scale = 2 ** plan.profile.renderingExposureEv / plane.referenceWhiteValue, maximum = 2 ** plan.profile.bitDepth - 1,\n tone = prepareSdrPixelTone(plan.profile);\n for (let y = 0; y < plane.pixelHeight; y += NATIVE_CAPTURE_SDR_LIMITS.tileHeight) {\n for (let x = 0; x < plane.pixelWidth; x += NATIVE_CAPTURE_SDR_LIMITS.tileWidth) {\n checkActive();\n const request = Object.freeze({ captureId: plan.capture.captureId, sourcePlaneId: plane.id,\n artifactId: storage.artifactId, sha256: storage.sha256, x, y,\n width: Math.min(NATIVE_CAPTURE_SDR_LIMITS.tileWidth, plane.pixelWidth - x),\n height: Math.min(NATIVE_CAPTURE_SDR_LIMITS.tileHeight, plane.pixelHeight - y) });\n {\n const tile = await readTile(request, abort.signal);\n checkActive();\n // Diagnostics are aggregated only after the whole tile renders.\n renderTileInto(tile, request, plane, plan.profile, tone, scale, maximum, codes!, diagnostics);\n count++;\n onProgress?.({ completedTileCount: count, totalTileCount: plan.tileCount });\n }\n // Do not keep tile or reference arrays past the next tile read or host yield.\n await yieldControl(abort.signal);\n checkActive();\n }\n }\n output = { plan, integerSamples: codes!, diagnostics, outputEncoding: sdrOutputEncoding(maximum),\n displayAdaptation: { kind: \"external-platform\", applied: false, displayCapabilities: \"unknown\" } };\n state = \"completed\";\n } catch (error) {\n release();\n if (!abort.signal.aborted) { state = \"failed\"; abort.abort(); }\n throw error;\n }\n },\n cancel(): void {\n if (state === \"ready\" || state === \"running\" || state === \"completed\") { state = \"cancelled\"; abort.abort(); release(); }\n },\n dispose(): void { if (state !== \"transferred\") { state = \"disposed\"; abort.abort(); release(); } },\n takeOutput(): NativeCaptureSdrOutput {\n if (state !== \"completed\" || !output) throw new InvalidConfigurationError(\"Native SDR output is unavailable.\");\n const result = output; output = null; codes = null; state = \"transferred\";\n return result;\n }\n };\n}\n"]}
@@ -33,7 +33,12 @@ export interface SdrRenderingProfile {
33
33
  toneCurve: SdrToneCurve;
34
34
  /** Present exactly for `extended-reinhard-luminance` ({whiteLuminance}) and `naka-rushton-luminance`. */
35
35
  toneCurveParameters?: SdrToneCurveParameters;
36
- gamutHandling: "clip-components" | "reject-out-of-range";
36
+ /**
37
+ * After the tone curve: clip each component to [0, 1], reject an out-of-range pixel, or (#601, 2.22.0) desaturate a pixel whose
38
+ * largest component exceeds 1 toward the gray of its own luminance until that component is 1 (`desaturateToLuminance`), then
39
+ * clip what remains below 0.
40
+ */
41
+ gamutHandling: "clip-components" | "reject-out-of-range" | "desaturate-to-luminance";
37
42
  outputDynamicRange: "sdr";
38
43
  transferFunction: "srgb";
39
44
  bitDepth: 8 | 16;
@@ -53,6 +58,18 @@ export interface SdrRenderingInput {
53
58
  samples: readonly number[];
54
59
  profile: SdrRenderingProfile;
55
60
  }
61
+ /** Internal: the linear-sRGB/D65 relative-luminance weights the luminance curves and the desaturation use. */
62
+ export declare const SDR_LUMINANCE_WEIGHTS: readonly number[];
63
+ /**
64
+ * Internal (#601): the `desaturate-to-luminance` gamut handling of one tone-mapped pixel, in place. Where its largest component t
65
+ * exceeds 1, the pixel is moved toward the gray of its own luminance Y = wᵀp, p' = Y + a·(p − Y) with a = (1 − Y)/(t − Y), so
66
+ * the largest component becomes 1. Y is linear in the components and the weights sum to 1, so p' keeps the luminance, and as an
67
+ * additive mixture with that gray it stays on the line from the white point through p's chromaticity (its dominant wavelength,
68
+ * the colorimetric hue) instead of the per-component clip's shift toward the secondaries. Where Y ≥ 1 the pixel is white. The
69
+ * same principle as W3C CSS Color 4 §13 (reduce chroma at constant lightness and hue) and as darktable's sigmoid "RGB ratio"
70
+ * mode (desaturate bright saturated colors to stay in gamut); this linear form is the engine's own. Other pixels are unchanged.
71
+ */
72
+ export declare function desaturateToLuminance(out: Float64Array | number[]): void;
56
73
  /** Writes the three tone-mapped samples of one exposed pixel. Exposed values are finite. */
57
74
  export type SdrPixelTone = (red: number, green: number, blue: number, out: Float64Array | number[]) => void;
58
75
  /**
@@ -52,6 +52,33 @@ function parseToneCurveParameters(curve, value) {
52
52
  }
53
53
  /** Linear-sRGB/D65 relative-luminance weights: the Y row of the engine's sRGB to XYZ matrix (W3C CSS Color 4 sRGB; also IEC 61966-2-1). */
54
54
  const LUMINANCE = resolveCaptureColorModel().cameraRgbToXyz[1];
55
+ /** Internal: the linear-sRGB/D65 relative-luminance weights the luminance curves and the desaturation use. */
56
+ export const SDR_LUMINANCE_WEIGHTS = LUMINANCE;
57
+ /**
58
+ * Internal (#601): the `desaturate-to-luminance` gamut handling of one tone-mapped pixel, in place. Where its largest component t
59
+ * exceeds 1, the pixel is moved toward the gray of its own luminance Y = wᵀp, p' = Y + a·(p − Y) with a = (1 − Y)/(t − Y), so
60
+ * the largest component becomes 1. Y is linear in the components and the weights sum to 1, so p' keeps the luminance, and as an
61
+ * additive mixture with that gray it stays on the line from the white point through p's chromaticity (its dominant wavelength,
62
+ * the colorimetric hue) instead of the per-component clip's shift toward the secondaries. Where Y ≥ 1 the pixel is white. The
63
+ * same principle as W3C CSS Color 4 §13 (reduce chroma at constant lightness and hue) and as darktable's sigmoid "RGB ratio"
64
+ * mode (desaturate bright saturated colors to stay in gamut); this linear form is the engine's own. Other pixels are unchanged.
65
+ */
66
+ export function desaturateToLuminance(out) {
67
+ const r = out[0], g = out[1], b = out[2], top = r > g ? (r > b ? r : b) : (g > b ? g : b);
68
+ if (!(top > 1))
69
+ return;
70
+ const l = LUMINANCE[0] * r + LUMINANCE[1] * g + LUMINANCE[2] * b;
71
+ if (l >= 1) {
72
+ out[0] = 1;
73
+ out[1] = 1;
74
+ out[2] = 1;
75
+ return;
76
+ }
77
+ const a = (1 - l) / (top - l);
78
+ out[0] = l + a * (r - l);
79
+ out[1] = l + a * (g - l);
80
+ out[2] = l + a * (b - l);
81
+ }
55
82
  /**
56
83
  * Internal shared tone operator. The reference renderer, the native SDR kernel and RAW development all
57
84
  * call this one function, so every path uses the same per-pixel operation order. Not exported from the package.
@@ -104,7 +131,7 @@ export function parseSdrRenderingProfile(value) {
104
131
  const toneCurveParameters = parseToneCurveParameters(r.toneCurve, r.toneCurveParameters);
105
132
  if (r.schemaVersion !== SDR_RENDERING_SCHEMA_VERSION || typeof r.renderingExposureEv !== "number" ||
106
133
  !Number.isFinite(r.renderingExposureEv) || Math.abs(r.renderingExposureEv) > 32 ||
107
- (r.gamutHandling !== "clip-components" && r.gamutHandling !== "reject-out-of-range") ||
134
+ (r.gamutHandling !== "clip-components" && r.gamutHandling !== "reject-out-of-range" && r.gamutHandling !== "desaturate-to-luminance") ||
108
135
  r.outputDynamicRange !== "sdr" || r.transferFunction !== "srgb" || (r.bitDepth !== 8 && r.bitDepth !== 16) ||
109
136
  r.rounding !== "nearest-ties-up" || r.dither !== "none")
110
137
  throw new InvalidConfigurationError("Unsupported SDR rendering profile.");
@@ -163,7 +190,17 @@ export function calculateSdrRendering(input) {
163
190
  tone.push(m === 0 ? 0 : m);
164
191
  }
165
192
  }
166
- const linear = tone.map((sample) => {
193
+ // #601: the desaturation acts on whole pixels before the remaining components are clipped (tone samples are kept as mapped).
194
+ const gamut = p.gamutHandling === "desaturate-to-luminance" ? tone.slice() : tone;
195
+ if (gamut !== tone)
196
+ for (let i = 0; i < gamut.length; i += 3) {
197
+ const px = gamut.slice(i, i + 3);
198
+ desaturateToLuminance(px);
199
+ gamut[i] = px[0];
200
+ gamut[i + 1] = px[1];
201
+ gamut[i + 2] = px[2];
202
+ }
203
+ const linear = gamut.map((sample) => {
167
204
  if (sample < 0 || sample > 1) {
168
205
  if (p.gamutHandling === "reject-out-of-range")
169
206
  throw new InvalidConfigurationError("Tone output exceeds the declared SDR gamut range.");
@@ -1 +1 @@
1
- {"version":3,"file":"sdr-rendering.js","sourceRoot":"","sources":["../../src/output/sdr-rendering.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The SDR (standard dynamic range) reference renderer: linear sRGB → rendering exposure → tone\n * curve → gamut handling → sRGB transfer → integer codes.\n *\n * It takes linear RGB that already has colour and white balance (WB) applied. Unknown HDR or\n * display modes are rejected.\n * @see docs/SDR_RENDERING.md for equations, coordinate/unit conventions, blockers and support limits.\n */\n\nimport { encodeSrgbComponent } from \"../color/srgb-transfer.js\";\nimport { resolveCaptureColorModel } from \"../color/capture-color.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { InvalidScientificResultError } from \"../core/validation.js\";\n\nexport const SDR_RENDERING_SCHEMA_VERSION = \"0.1.0\" as const;\n/**\n * Version of the `generic-sdr-rendering-reference` model. 0.2.0 added luminance tone curves and fixed\n * round-to-nearest, ties up (`Math.round`; the old `Math.floor(x+.5)` rounded 0.49999999999999994 up).\n */\nexport const SDR_RENDERING_MODEL_VERSION = \"0.2.0\" as const;\n/**\n * Tone curves. A per-channel curve acts on each exposed component separately. A luminance curve\n * scales all three components of a pixel by f(L)/L, where L is the linear-sRGB/D65 relative\n * luminance, so the colour (chromaticity) is kept before gamut handling.\n * - `identity`: f(x)=x.\n * - `positive-reinhard-per-channel`: f(x)=x/(1+x) for x>0 (Reinhard et al. 2002, eq. 3), per channel.\n * - `extended-reinhard-luminance`: f(L)=L(1+L/Lw²)/(1+L) (Reinhard et al. 2002, eq. 4); L=Lw maps to 1.\n * - `naka-rushton-luminance`: f(L)=g(L)/g(Lw), g(L)=Lⁿ/(Lⁿ+σⁿ) (Naka & Rushton 1966; Reinhard & Devlin\n * 2005): a sigmoid in log luminance with contrast n and mid-response σ, i.e. a film-like S-curve.\n */\nexport type SdrToneCurve = \"identity\" | \"positive-reinhard-per-channel\" | \"extended-reinhard-luminance\" | \"naka-rushton-luminance\";\n/** Required only by luminance curves; all values are relative linear luminance after rendering exposure. */\nexport type SdrToneCurveParameters =\n | { whiteLuminance: number }\n | { exponent: number; semiSaturation: number; whiteLuminance: number };\n/** Rendering choices made by the engine user. They are independent of physical exposure and display calibration. */\nexport interface SdrRenderingProfile {\n schemaVersion: typeof SDR_RENDERING_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n renderingExposureEv: number;\n toneCurve: SdrToneCurve;\n /** Present exactly for `extended-reinhard-luminance` ({whiteLuminance}) and `naka-rushton-luminance`. */\n toneCurveParameters?: SdrToneCurveParameters;\n gamutHandling: \"clip-components\" | \"reject-out-of-range\";\n outputDynamicRange: \"sdr\";\n transferFunction: \"srgb\";\n bitDepth: 8 | 16;\n rounding: \"nearest-ties-up\";\n dither: \"none\";\n}\n/** Only linear-sRGB/D65 data that has been explicitly colour-transformed, with all three channels at each pixel, is accepted. */\nexport interface SdrRenderingInput {\n sourceImageStateId: string;\n inputImageState: \"color-transformed-linear-rgb\";\n inputColorSpace: \"linear-srgb-d65\";\n whiteBalanceHandling: \"already-applied-upstream\" | \"not-required\";\n pixelWidth: number;\n pixelHeight: number;\n referenceWhiteValue: number;\n /** Relative linear RGB, row-major tightly interleaved red/green/blue. */\n samples: readonly number[];\n profile: SdrRenderingProfile;\n}\nfunction object(v: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(v, keys, \"Invalid SDR fields.\");\n}\nfunction publicId(v: unknown): string {\n return requirePublicOpaqueId(v, \"Invalid public SDR identity.\");\n}\nfunction bounded(value: unknown, minimumExclusive: number, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= minimumExclusive || value > maximum) {\n throw new InvalidConfigurationError(\"Unsupported SDR tone-curve parameters.\");\n }\n return value;\n}\nfunction parseToneCurveParameters(curve: unknown, value: unknown): SdrToneCurveParameters | undefined {\n if (curve === \"identity\" || curve === \"positive-reinhard-per-channel\") {\n if (value !== undefined) throw new InvalidConfigurationError(\"Per-channel tone curves take no parameters.\");\n return undefined;\n }\n if (curve === \"extended-reinhard-luminance\") {\n const p = requireAllowlistedRecord(value, [\"whiteLuminance\"], \"Invalid SDR tone-curve parameters.\");\n return { whiteLuminance: bounded(p.whiteLuminance, 0, 65536) };\n }\n if (curve === \"naka-rushton-luminance\") {\n const p = requireAllowlistedRecord(value, [\"exponent\", \"semiSaturation\", \"whiteLuminance\"], \"Invalid SDR tone-curve parameters.\");\n const exponent = bounded(p.exponent, 0, 4);\n if (exponent < .25) throw new InvalidConfigurationError(\"Unsupported SDR tone-curve parameters.\");\n return { exponent, semiSaturation: bounded(p.semiSaturation, 0, 64), whiteLuminance: bounded(p.whiteLuminance, 0, 65536) };\n }\n throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n}\n/** Linear-sRGB/D65 relative-luminance weights: the Y row of the engine's sRGB to XYZ matrix (W3C CSS Color 4 sRGB; also IEC 61966-2-1). */\nconst LUMINANCE = resolveCaptureColorModel().cameraRgbToXyz[1]! as readonly number[];\n/** Writes the three tone-mapped samples of one exposed pixel. Exposed values are finite. */\nexport type SdrPixelTone = (red: number, green: number, blue: number, out: Float64Array | number[]) => void;\n/**\n * Internal shared tone operator. The reference renderer, the native SDR kernel and RAW development all\n * call this one function, so every path uses the same per-pixel operation order. Not exported from the package.\n */\nexport function prepareSdrPixelTone(profile: Pick<SdrRenderingProfile, \"toneCurve\" | \"toneCurveParameters\">): SdrPixelTone {\n const curve = profile.toneCurve, p = profile.toneCurveParameters;\n if (curve === \"identity\") return (r, g, b, out) => { out[0] = r; out[1] = g; out[2] = b; };\n if (curve === \"positive-reinhard-per-channel\") return (r, g, b, out) => {\n out[0] = r > 0 ? r/(1+r) : r; out[1] = g > 0 ? g/(1+g) : g; out[2] = b > 0 ? b/(1+b) : b;\n };\n let map: (l: number) => number;\n if (curve === \"extended-reinhard-luminance\" && p && !(\"exponent\" in p)) {\n const white2 = p.whiteLuminance*p.whiteLuminance;\n // Written as L/(1+L)·(1+L/Lw²) so a very large finite L cannot overflow an intermediate product.\n map = (l: number): number => l/(1+l)*(1+l/white2);\n } else if (curve === \"naka-rushton-luminance\" && p && \"exponent\" in p) {\n // g(L) = Lⁿ/(Lⁿ+σⁿ) = 1/(1+(σ/L)ⁿ): no overflow for large L.\n const q = p as Extract<SdrToneCurveParameters, { exponent: number }>;\n const n = q.exponent, sigma = q.semiSaturation, g = (l: number): number => 1/(1+(sigma/l)**n), white = g(q.whiteLuminance);\n map = (l: number): number => g(l)/white;\n } else throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n const kr = LUMINANCE[0]!, kg = LUMINANCE[1]!, kb = LUMINANCE[2]!;\n return (r, g, b, out) => {\n const l = kr*r+kg*g+kb*b;\n if (!(l > 0)) { out[0] = r; out[1] = g; out[2] = b; return; }\n const ratio = map(l)/l, mr = r*ratio, mg = g*ratio, mb = b*ratio;\n if (!Number.isFinite(mr) || !Number.isFinite(mg) || !Number.isFinite(mb)) throw new InvalidScientificResultError(\"Tone mapping produced a non-finite sample.\");\n out[0] = mr; out[1] = mg; out[2] = mb;\n };\n}\n/** Validates a rendering and encoding profile. Unknown HDR or display modes are rejected. */\nexport function parseSdrRenderingProfile(value: unknown): SdrRenderingProfile {\n const r = object(value, [\"schemaVersion\", \"profileId\", \"profileVersion\", \"renderingExposureEv\", \"toneCurve\", \"toneCurveParameters\", \"gamutHandling\",\n \"outputDynamicRange\", \"transferFunction\", \"bitDepth\", \"rounding\", \"dither\"]);\n const toneCurveParameters = parseToneCurveParameters(r.toneCurve, r.toneCurveParameters);\n if (r.schemaVersion !== SDR_RENDERING_SCHEMA_VERSION || typeof r.renderingExposureEv !== \"number\" ||\n !Number.isFinite(r.renderingExposureEv) || Math.abs(r.renderingExposureEv) > 32 ||\n (r.gamutHandling !== \"clip-components\" && r.gamutHandling !== \"reject-out-of-range\") ||\n r.outputDynamicRange !== \"sdr\" || r.transferFunction !== \"srgb\" || (r.bitDepth !== 8 && r.bitDepth !== 16) ||\n r.rounding !== \"nearest-ties-up\" || r.dither !== \"none\") throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n return { schemaVersion: SDR_RENDERING_SCHEMA_VERSION, profileId: publicId(r.profileId), profileVersion: publicId(r.profileVersion),\n renderingExposureEv: Object.is(r.renderingExposureEv, -0) ? 0 : r.renderingExposureEv, toneCurve: r.toneCurve as SdrToneCurve,\n ...(toneCurveParameters === undefined ? {} : { toneCurveParameters }), gamutHandling: r.gamutHandling, outputDynamicRange: r.outputDynamicRange, transferFunction: r.transferFunction,\n bitDepth: r.bitDepth, rounding: r.rounding, dither: r.dither };\n}\n/** Validates the declared image state and a bounded, finite, inline image. It never applies white balance or colour conversion. */\nexport function parseSdrRenderingInput(value: unknown): SdrRenderingInput {\n const r = object(value, [\"sourceImageStateId\", \"inputImageState\", \"inputColorSpace\", \"whiteBalanceHandling\",\n \"pixelWidth\", \"pixelHeight\", \"referenceWhiteValue\", \"samples\", \"profile\"]);\n const samples = r.samples;\n if (r.inputImageState !== \"color-transformed-linear-rgb\" || r.inputColorSpace !== \"linear-srgb-d65\" ||\n (r.whiteBalanceHandling !== \"already-applied-upstream\" && r.whiteBalanceHandling !== \"not-required\") ||\n typeof r.pixelWidth !== \"number\" || !Number.isSafeInteger(r.pixelWidth) || r.pixelWidth <= 0 ||\n typeof r.pixelHeight !== \"number\" || !Number.isSafeInteger(r.pixelHeight) || r.pixelHeight <= 0 ||\n typeof r.referenceWhiteValue !== \"number\" || !Number.isFinite(r.referenceWhiteValue) || r.referenceWhiteValue <= 0 ||\n !Array.isArray(samples) || samples.length !== r.pixelWidth*r.pixelHeight*3 || samples.length > 262144 ||\n Array.from({ length: samples.length }, (_, i) => i in samples).includes(false) ||\n samples.some((v) => typeof v !== \"number\" || !Number.isFinite(v))) throw new InvalidConfigurationError(\"Unsupported SDR input domain/raster.\");\n return { sourceImageStateId: publicId(r.sourceImageStateId), inputImageState: r.inputImageState, inputColorSpace: r.inputColorSpace,\n whiteBalanceHandling: r.whiteBalanceHandling, pixelWidth: r.pixelWidth, pixelHeight: r.pixelHeight,\n referenceWhiteValue: r.referenceWhiteValue, samples: samples.map((v: number) => Object.is(v, -0) ? 0 : v),\n profile: parseSdrRenderingProfile(r.profile) };\n}\nexport interface SdrRenderingResult {\n schemaVersion: typeof SDR_RENDERING_SCHEMA_VERSION;\n sourceImageStateId: string;\n profile: SdrRenderingProfile;\n pixelWidth: number;\n pixelHeight: number;\n channelOrder: readonly [\"red\", \"green\", \"blue\"];\n whiteBalanceHandling: SdrRenderingInput[\"whiteBalanceHandling\"];\n /** Relative normalized linear samples after exposure/tone, before gamut handling. */\n toneMappedLinearSamples: readonly number[];\n outputLinearSamples: readonly number[];\n outputEncodedSamples: readonly number[];\n integerSamples: readonly number[];\n outputEncoding: {\n imageState: \"output-referred-sdr\";\n colorSpace: \"srgb\";\n primariesXy: { red: readonly [number, number]; green: readonly [number, number]; blue: readonly [number, number] };\n whitePointXy: readonly [number, number];\n referenceWhiteLuminanceCdM2: 80;\n codeMinimum: 0;\n codeMaximum: number;\n };\n diagnostics: {\n renderingNegativeSampleCount: number;\n renderingAboveReferenceSampleCount: number;\n toneChangedSampleCount: number;\n gamutClippedLowSampleCount: number;\n gamutClippedHighSampleCount: number;\n /**\n * `not-consumed`: no upstream saturation data (full well, ADC or RAW) is available, and it is never\n * guessed from the output.\n * `consumed-in-raw-development`: RAW development already clipped or reconstructed flagged and\n * white-level samples before white balance (see its highlight diagnostics).\n */\n captureSaturation: \"not-consumed\" | \"consumed-in-raw-development\";\n };\n /** Describes the encoded output for the platform's colour management. No real display is assessed. */\n displayAdaptation: { kind: \"external-platform\"; applied: false; displayCapabilities: \"unknown\" };\n}\n/**\n * Standalone SDR rendering: rendering exposure → tone → gamut → sRGB transfer → quantization.\n * No physical capture, metering, white balance, colour conversion, lens correction or display\n * adaptation runs.\n */\nexport function calculateSdrRendering(input: SdrRenderingInput): CalculationResult<SdrRenderingResult> {\n const value = parseSdrRenderingInput(input), p = value.profile;\n const scale = 2**p.renderingExposureEv/value.referenceWhiteValue;\n if (!Number.isFinite(scale) || scale <= 0) throw new InvalidScientificResultError(\"Rendering scale is non-finite/unrepresentable.\");\n let negative = 0, above = 0, changed = 0, low = 0, high = 0;\n const toneOf = prepareSdrPixelTone(p), exposed = [0, 0, 0], mapped = [0, 0, 0], tone: number[] = [];\n for (let i = 0; i < value.samples.length; i += 3) {\n for (let c = 0; c < 3; c++) {\n const e = value.samples[i+c]!*scale;\n if (!Number.isFinite(e)) throw new InvalidScientificResultError(\"Rendering exposure produced a non-finite sample.\");\n if (e < 0) negative++;\n if (e > 1) above++;\n exposed[c] = e;\n }\n toneOf(exposed[0]!, exposed[1]!, exposed[2]!, mapped);\n for (let c = 0; c < 3; c++) {\n const m = mapped[c]!;\n if (m !== exposed[c]) changed++;\n tone.push(m === 0 ? 0 : m);\n }\n }\n const linear = tone.map((sample) => {\n if (sample < 0 || sample > 1) {\n if (p.gamutHandling === \"reject-out-of-range\") throw new InvalidConfigurationError(\"Tone output exceeds the declared SDR gamut range.\");\n if (sample < 0) { low++; return 0; }\n high++; return 1;\n }\n return sample;\n });\n const encoded = linear.map(encodeSrgbComponent);\n const maximum = 2**p.bitDepth-1;\n // Nearest, ties up: encoded samples are in [0,1], where Math.round is exact ties-up rounding.\n const codes = encoded.map((sample) => Math.round(sample*maximum));\n return calculatedResult({ schemaVersion: SDR_RENDERING_SCHEMA_VERSION, sourceImageStateId: value.sourceImageStateId,\n profile: p, pixelWidth: value.pixelWidth, pixelHeight: value.pixelHeight, channelOrder: [\"red\", \"green\", \"blue\"],\n whiteBalanceHandling: value.whiteBalanceHandling, toneMappedLinearSamples: tone, outputLinearSamples: linear,\n outputEncodedSamples: encoded, integerSamples: codes,\n outputEncoding: { imageState: \"output-referred-sdr\", colorSpace: \"srgb\", primariesXy: { red: [.64, .33], green: [.30, .60], blue: [.15, .06] },\n whitePointXy: [.3127, .3290], referenceWhiteLuminanceCdM2: 80, codeMinimum: 0, codeMaximum: maximum },\n diagnostics: { renderingNegativeSampleCount: negative, renderingAboveReferenceSampleCount: above,\n toneChangedSampleCount: changed, gamutClippedLowSampleCount: low, gamutClippedHighSampleCount: high, captureSaturation: \"not-consumed\" },\n displayAdaptation: { kind: \"external-platform\", applied: false, displayCapabilities: \"unknown\" } },\n \"generic-sdr-rendering-reference\", SDR_RENDERING_MODEL_VERSION,\n [\"Deterministic first-party rendering choice, not a calibrated camera look or scene-light model\",\n \"Per-channel positive Reinhard can change chromaticity; luminance curves preserve chromaticity until component clipping, which is not perceptual gamut mapping\",\n \"Input WB/color completion is a producer declaration; capture/correction adapter and production activation remain pending\",\n \"sRGB reference encoding assumptions do not establish actual display luminance, saturation or viewing conditions\"]);\n}\n"]}
1
+ {"version":3,"file":"sdr-rendering.js","sourceRoot":"","sources":["../../src/output/sdr-rendering.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The SDR (standard dynamic range) reference renderer: linear sRGB → rendering exposure → tone\n * curve → gamut handling → sRGB transfer → integer codes.\n *\n * It takes linear RGB that already has colour and white balance (WB) applied. Unknown HDR or\n * display modes are rejected.\n * @see docs/SDR_RENDERING.md for equations, coordinate/unit conventions, blockers and support limits.\n */\n\nimport { encodeSrgbComponent } from \"../color/srgb-transfer.js\";\nimport { resolveCaptureColorModel } from \"../color/capture-color.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { InvalidScientificResultError } from \"../core/validation.js\";\n\nexport const SDR_RENDERING_SCHEMA_VERSION = \"0.1.0\" as const;\n/**\n * Version of the `generic-sdr-rendering-reference` model. 0.2.0 added luminance tone curves and fixed\n * round-to-nearest, ties up (`Math.round`; the old `Math.floor(x+.5)` rounded 0.49999999999999994 up).\n */\nexport const SDR_RENDERING_MODEL_VERSION = \"0.2.0\" as const;\n/**\n * Tone curves. A per-channel curve acts on each exposed component separately. A luminance curve\n * scales all three components of a pixel by f(L)/L, where L is the linear-sRGB/D65 relative\n * luminance, so the colour (chromaticity) is kept before gamut handling.\n * - `identity`: f(x)=x.\n * - `positive-reinhard-per-channel`: f(x)=x/(1+x) for x>0 (Reinhard et al. 2002, eq. 3), per channel.\n * - `extended-reinhard-luminance`: f(L)=L(1+L/Lw²)/(1+L) (Reinhard et al. 2002, eq. 4); L=Lw maps to 1.\n * - `naka-rushton-luminance`: f(L)=g(L)/g(Lw), g(L)=Lⁿ/(Lⁿ+σⁿ) (Naka & Rushton 1966; Reinhard & Devlin\n * 2005): a sigmoid in log luminance with contrast n and mid-response σ, i.e. a film-like S-curve.\n */\nexport type SdrToneCurve = \"identity\" | \"positive-reinhard-per-channel\" | \"extended-reinhard-luminance\" | \"naka-rushton-luminance\";\n/** Required only by luminance curves; all values are relative linear luminance after rendering exposure. */\nexport type SdrToneCurveParameters =\n | { whiteLuminance: number }\n | { exponent: number; semiSaturation: number; whiteLuminance: number };\n/** Rendering choices made by the engine user. They are independent of physical exposure and display calibration. */\nexport interface SdrRenderingProfile {\n schemaVersion: typeof SDR_RENDERING_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n renderingExposureEv: number;\n toneCurve: SdrToneCurve;\n /** Present exactly for `extended-reinhard-luminance` ({whiteLuminance}) and `naka-rushton-luminance`. */\n toneCurveParameters?: SdrToneCurveParameters;\n /**\n * After the tone curve: clip each component to [0, 1], reject an out-of-range pixel, or (#601, 2.22.0) desaturate a pixel whose\n * largest component exceeds 1 toward the gray of its own luminance until that component is 1 (`desaturateToLuminance`), then\n * clip what remains below 0.\n */\n gamutHandling: \"clip-components\" | \"reject-out-of-range\" | \"desaturate-to-luminance\";\n outputDynamicRange: \"sdr\";\n transferFunction: \"srgb\";\n bitDepth: 8 | 16;\n rounding: \"nearest-ties-up\";\n dither: \"none\";\n}\n/** Only linear-sRGB/D65 data that has been explicitly colour-transformed, with all three channels at each pixel, is accepted. */\nexport interface SdrRenderingInput {\n sourceImageStateId: string;\n inputImageState: \"color-transformed-linear-rgb\";\n inputColorSpace: \"linear-srgb-d65\";\n whiteBalanceHandling: \"already-applied-upstream\" | \"not-required\";\n pixelWidth: number;\n pixelHeight: number;\n referenceWhiteValue: number;\n /** Relative linear RGB, row-major tightly interleaved red/green/blue. */\n samples: readonly number[];\n profile: SdrRenderingProfile;\n}\nfunction object(v: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(v, keys, \"Invalid SDR fields.\");\n}\nfunction publicId(v: unknown): string {\n return requirePublicOpaqueId(v, \"Invalid public SDR identity.\");\n}\nfunction bounded(value: unknown, minimumExclusive: number, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= minimumExclusive || value > maximum) {\n throw new InvalidConfigurationError(\"Unsupported SDR tone-curve parameters.\");\n }\n return value;\n}\nfunction parseToneCurveParameters(curve: unknown, value: unknown): SdrToneCurveParameters | undefined {\n if (curve === \"identity\" || curve === \"positive-reinhard-per-channel\") {\n if (value !== undefined) throw new InvalidConfigurationError(\"Per-channel tone curves take no parameters.\");\n return undefined;\n }\n if (curve === \"extended-reinhard-luminance\") {\n const p = requireAllowlistedRecord(value, [\"whiteLuminance\"], \"Invalid SDR tone-curve parameters.\");\n return { whiteLuminance: bounded(p.whiteLuminance, 0, 65536) };\n }\n if (curve === \"naka-rushton-luminance\") {\n const p = requireAllowlistedRecord(value, [\"exponent\", \"semiSaturation\", \"whiteLuminance\"], \"Invalid SDR tone-curve parameters.\");\n const exponent = bounded(p.exponent, 0, 4);\n if (exponent < .25) throw new InvalidConfigurationError(\"Unsupported SDR tone-curve parameters.\");\n return { exponent, semiSaturation: bounded(p.semiSaturation, 0, 64), whiteLuminance: bounded(p.whiteLuminance, 0, 65536) };\n }\n throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n}\n/** Linear-sRGB/D65 relative-luminance weights: the Y row of the engine's sRGB to XYZ matrix (W3C CSS Color 4 sRGB; also IEC 61966-2-1). */\nconst LUMINANCE = resolveCaptureColorModel().cameraRgbToXyz[1]! as readonly number[];\n/** Internal: the linear-sRGB/D65 relative-luminance weights the luminance curves and the desaturation use. */\nexport const SDR_LUMINANCE_WEIGHTS: readonly number[] = LUMINANCE;\n/**\n * Internal (#601): the `desaturate-to-luminance` gamut handling of one tone-mapped pixel, in place. Where its largest component t\n * exceeds 1, the pixel is moved toward the gray of its own luminance Y = wᵀp, p' = Y + a·(p − Y) with a = (1 − Y)/(t − Y), so\n * the largest component becomes 1. Y is linear in the components and the weights sum to 1, so p' keeps the luminance, and as an\n * additive mixture with that gray it stays on the line from the white point through p's chromaticity (its dominant wavelength,\n * the colorimetric hue) instead of the per-component clip's shift toward the secondaries. Where Y ≥ 1 the pixel is white. The\n * same principle as W3C CSS Color 4 §13 (reduce chroma at constant lightness and hue) and as darktable's sigmoid \"RGB ratio\"\n * mode (desaturate bright saturated colors to stay in gamut); this linear form is the engine's own. Other pixels are unchanged.\n */\nexport function desaturateToLuminance(out: Float64Array | number[]): void {\n const r = out[0]!, g = out[1]!, b = out[2]!, top = r > g ? (r > b ? r : b) : (g > b ? g : b);\n if (!(top > 1)) return;\n const l = LUMINANCE[0]!*r+LUMINANCE[1]!*g+LUMINANCE[2]!*b;\n if (l >= 1) { out[0] = 1; out[1] = 1; out[2] = 1; return; }\n const a = (1-l)/(top-l);\n out[0] = l+a*(r-l); out[1] = l+a*(g-l); out[2] = l+a*(b-l);\n}\n/** Writes the three tone-mapped samples of one exposed pixel. Exposed values are finite. */\nexport type SdrPixelTone = (red: number, green: number, blue: number, out: Float64Array | number[]) => void;\n/**\n * Internal shared tone operator. The reference renderer, the native SDR kernel and RAW development all\n * call this one function, so every path uses the same per-pixel operation order. Not exported from the package.\n */\nexport function prepareSdrPixelTone(profile: Pick<SdrRenderingProfile, \"toneCurve\" | \"toneCurveParameters\">): SdrPixelTone {\n const curve = profile.toneCurve, p = profile.toneCurveParameters;\n if (curve === \"identity\") return (r, g, b, out) => { out[0] = r; out[1] = g; out[2] = b; };\n if (curve === \"positive-reinhard-per-channel\") return (r, g, b, out) => {\n out[0] = r > 0 ? r/(1+r) : r; out[1] = g > 0 ? g/(1+g) : g; out[2] = b > 0 ? b/(1+b) : b;\n };\n let map: (l: number) => number;\n if (curve === \"extended-reinhard-luminance\" && p && !(\"exponent\" in p)) {\n const white2 = p.whiteLuminance*p.whiteLuminance;\n // Written as L/(1+L)·(1+L/Lw²) so a very large finite L cannot overflow an intermediate product.\n map = (l: number): number => l/(1+l)*(1+l/white2);\n } else if (curve === \"naka-rushton-luminance\" && p && \"exponent\" in p) {\n // g(L) = Lⁿ/(Lⁿ+σⁿ) = 1/(1+(σ/L)ⁿ): no overflow for large L.\n const q = p as Extract<SdrToneCurveParameters, { exponent: number }>;\n const n = q.exponent, sigma = q.semiSaturation, g = (l: number): number => 1/(1+(sigma/l)**n), white = g(q.whiteLuminance);\n map = (l: number): number => g(l)/white;\n } else throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n const kr = LUMINANCE[0]!, kg = LUMINANCE[1]!, kb = LUMINANCE[2]!;\n return (r, g, b, out) => {\n const l = kr*r+kg*g+kb*b;\n if (!(l > 0)) { out[0] = r; out[1] = g; out[2] = b; return; }\n const ratio = map(l)/l, mr = r*ratio, mg = g*ratio, mb = b*ratio;\n if (!Number.isFinite(mr) || !Number.isFinite(mg) || !Number.isFinite(mb)) throw new InvalidScientificResultError(\"Tone mapping produced a non-finite sample.\");\n out[0] = mr; out[1] = mg; out[2] = mb;\n };\n}\n/** Validates a rendering and encoding profile. Unknown HDR or display modes are rejected. */\nexport function parseSdrRenderingProfile(value: unknown): SdrRenderingProfile {\n const r = object(value, [\"schemaVersion\", \"profileId\", \"profileVersion\", \"renderingExposureEv\", \"toneCurve\", \"toneCurveParameters\", \"gamutHandling\",\n \"outputDynamicRange\", \"transferFunction\", \"bitDepth\", \"rounding\", \"dither\"]);\n const toneCurveParameters = parseToneCurveParameters(r.toneCurve, r.toneCurveParameters);\n if (r.schemaVersion !== SDR_RENDERING_SCHEMA_VERSION || typeof r.renderingExposureEv !== \"number\" ||\n !Number.isFinite(r.renderingExposureEv) || Math.abs(r.renderingExposureEv) > 32 ||\n (r.gamutHandling !== \"clip-components\" && r.gamutHandling !== \"reject-out-of-range\" && r.gamutHandling !== \"desaturate-to-luminance\") ||\n r.outputDynamicRange !== \"sdr\" || r.transferFunction !== \"srgb\" || (r.bitDepth !== 8 && r.bitDepth !== 16) ||\n r.rounding !== \"nearest-ties-up\" || r.dither !== \"none\") throw new InvalidConfigurationError(\"Unsupported SDR rendering profile.\");\n return { schemaVersion: SDR_RENDERING_SCHEMA_VERSION, profileId: publicId(r.profileId), profileVersion: publicId(r.profileVersion),\n renderingExposureEv: Object.is(r.renderingExposureEv, -0) ? 0 : r.renderingExposureEv, toneCurve: r.toneCurve as SdrToneCurve,\n ...(toneCurveParameters === undefined ? {} : { toneCurveParameters }), gamutHandling: r.gamutHandling, outputDynamicRange: r.outputDynamicRange, transferFunction: r.transferFunction,\n bitDepth: r.bitDepth, rounding: r.rounding, dither: r.dither };\n}\n/** Validates the declared image state and a bounded, finite, inline image. It never applies white balance or colour conversion. */\nexport function parseSdrRenderingInput(value: unknown): SdrRenderingInput {\n const r = object(value, [\"sourceImageStateId\", \"inputImageState\", \"inputColorSpace\", \"whiteBalanceHandling\",\n \"pixelWidth\", \"pixelHeight\", \"referenceWhiteValue\", \"samples\", \"profile\"]);\n const samples = r.samples;\n if (r.inputImageState !== \"color-transformed-linear-rgb\" || r.inputColorSpace !== \"linear-srgb-d65\" ||\n (r.whiteBalanceHandling !== \"already-applied-upstream\" && r.whiteBalanceHandling !== \"not-required\") ||\n typeof r.pixelWidth !== \"number\" || !Number.isSafeInteger(r.pixelWidth) || r.pixelWidth <= 0 ||\n typeof r.pixelHeight !== \"number\" || !Number.isSafeInteger(r.pixelHeight) || r.pixelHeight <= 0 ||\n typeof r.referenceWhiteValue !== \"number\" || !Number.isFinite(r.referenceWhiteValue) || r.referenceWhiteValue <= 0 ||\n !Array.isArray(samples) || samples.length !== r.pixelWidth*r.pixelHeight*3 || samples.length > 262144 ||\n Array.from({ length: samples.length }, (_, i) => i in samples).includes(false) ||\n samples.some((v) => typeof v !== \"number\" || !Number.isFinite(v))) throw new InvalidConfigurationError(\"Unsupported SDR input domain/raster.\");\n return { sourceImageStateId: publicId(r.sourceImageStateId), inputImageState: r.inputImageState, inputColorSpace: r.inputColorSpace,\n whiteBalanceHandling: r.whiteBalanceHandling, pixelWidth: r.pixelWidth, pixelHeight: r.pixelHeight,\n referenceWhiteValue: r.referenceWhiteValue, samples: samples.map((v: number) => Object.is(v, -0) ? 0 : v),\n profile: parseSdrRenderingProfile(r.profile) };\n}\nexport interface SdrRenderingResult {\n schemaVersion: typeof SDR_RENDERING_SCHEMA_VERSION;\n sourceImageStateId: string;\n profile: SdrRenderingProfile;\n pixelWidth: number;\n pixelHeight: number;\n channelOrder: readonly [\"red\", \"green\", \"blue\"];\n whiteBalanceHandling: SdrRenderingInput[\"whiteBalanceHandling\"];\n /** Relative normalized linear samples after exposure/tone, before gamut handling. */\n toneMappedLinearSamples: readonly number[];\n outputLinearSamples: readonly number[];\n outputEncodedSamples: readonly number[];\n integerSamples: readonly number[];\n outputEncoding: {\n imageState: \"output-referred-sdr\";\n colorSpace: \"srgb\";\n primariesXy: { red: readonly [number, number]; green: readonly [number, number]; blue: readonly [number, number] };\n whitePointXy: readonly [number, number];\n referenceWhiteLuminanceCdM2: 80;\n codeMinimum: 0;\n codeMaximum: number;\n };\n diagnostics: {\n renderingNegativeSampleCount: number;\n renderingAboveReferenceSampleCount: number;\n toneChangedSampleCount: number;\n gamutClippedLowSampleCount: number;\n gamutClippedHighSampleCount: number;\n /**\n * `not-consumed`: no upstream saturation data (full well, ADC or RAW) is available, and it is never\n * guessed from the output.\n * `consumed-in-raw-development`: RAW development already clipped or reconstructed flagged and\n * white-level samples before white balance (see its highlight diagnostics).\n */\n captureSaturation: \"not-consumed\" | \"consumed-in-raw-development\";\n };\n /** Describes the encoded output for the platform's colour management. No real display is assessed. */\n displayAdaptation: { kind: \"external-platform\"; applied: false; displayCapabilities: \"unknown\" };\n}\n/**\n * Standalone SDR rendering: rendering exposure → tone → gamut → sRGB transfer → quantization.\n * No physical capture, metering, white balance, colour conversion, lens correction or display\n * adaptation runs.\n */\nexport function calculateSdrRendering(input: SdrRenderingInput): CalculationResult<SdrRenderingResult> {\n const value = parseSdrRenderingInput(input), p = value.profile;\n const scale = 2**p.renderingExposureEv/value.referenceWhiteValue;\n if (!Number.isFinite(scale) || scale <= 0) throw new InvalidScientificResultError(\"Rendering scale is non-finite/unrepresentable.\");\n let negative = 0, above = 0, changed = 0, low = 0, high = 0;\n const toneOf = prepareSdrPixelTone(p), exposed = [0, 0, 0], mapped = [0, 0, 0], tone: number[] = [];\n for (let i = 0; i < value.samples.length; i += 3) {\n for (let c = 0; c < 3; c++) {\n const e = value.samples[i+c]!*scale;\n if (!Number.isFinite(e)) throw new InvalidScientificResultError(\"Rendering exposure produced a non-finite sample.\");\n if (e < 0) negative++;\n if (e > 1) above++;\n exposed[c] = e;\n }\n toneOf(exposed[0]!, exposed[1]!, exposed[2]!, mapped);\n for (let c = 0; c < 3; c++) {\n const m = mapped[c]!;\n if (m !== exposed[c]) changed++;\n tone.push(m === 0 ? 0 : m);\n }\n }\n // #601: the desaturation acts on whole pixels before the remaining components are clipped (tone samples are kept as mapped).\n const gamut = p.gamutHandling === \"desaturate-to-luminance\" ? tone.slice() : tone;\n if (gamut !== tone) for (let i = 0; i < gamut.length; i += 3) { const px = gamut.slice(i, i+3); desaturateToLuminance(px); gamut[i] = px[0]!; gamut[i+1] = px[1]!; gamut[i+2] = px[2]!; }\n const linear = gamut.map((sample) => {\n if (sample < 0 || sample > 1) {\n if (p.gamutHandling === \"reject-out-of-range\") throw new InvalidConfigurationError(\"Tone output exceeds the declared SDR gamut range.\");\n if (sample < 0) { low++; return 0; }\n high++; return 1;\n }\n return sample;\n });\n const encoded = linear.map(encodeSrgbComponent);\n const maximum = 2**p.bitDepth-1;\n // Nearest, ties up: encoded samples are in [0,1], where Math.round is exact ties-up rounding.\n const codes = encoded.map((sample) => Math.round(sample*maximum));\n return calculatedResult({ schemaVersion: SDR_RENDERING_SCHEMA_VERSION, sourceImageStateId: value.sourceImageStateId,\n profile: p, pixelWidth: value.pixelWidth, pixelHeight: value.pixelHeight, channelOrder: [\"red\", \"green\", \"blue\"],\n whiteBalanceHandling: value.whiteBalanceHandling, toneMappedLinearSamples: tone, outputLinearSamples: linear,\n outputEncodedSamples: encoded, integerSamples: codes,\n outputEncoding: { imageState: \"output-referred-sdr\", colorSpace: \"srgb\", primariesXy: { red: [.64, .33], green: [.30, .60], blue: [.15, .06] },\n whitePointXy: [.3127, .3290], referenceWhiteLuminanceCdM2: 80, codeMinimum: 0, codeMaximum: maximum },\n diagnostics: { renderingNegativeSampleCount: negative, renderingAboveReferenceSampleCount: above,\n toneChangedSampleCount: changed, gamutClippedLowSampleCount: low, gamutClippedHighSampleCount: high, captureSaturation: \"not-consumed\" },\n displayAdaptation: { kind: \"external-platform\", applied: false, displayCapabilities: \"unknown\" } },\n \"generic-sdr-rendering-reference\", SDR_RENDERING_MODEL_VERSION,\n [\"Deterministic first-party rendering choice, not a calibrated camera look or scene-light model\",\n \"Per-channel positive Reinhard can change chromaticity; luminance curves preserve chromaticity until component clipping, which is not perceptual gamut mapping\",\n \"Input WB/color completion is a producer declaration; capture/correction adapter and production activation remain pending\",\n \"sRGB reference encoding assumptions do not establish actual display luminance, saturation or viewing conditions\"]);\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * #595 (2.22.0): `result.sensor.native`, the sensor figures of one native photosite, in a module of its own so that it can be loaded
3
+ * apart from the capture's default path: the same report at the native pitch, the EMVA 1288 Release 4.0 dynamic range (§2.7,
4
+ * Eq. 26–28) and the 8 MP-normalized one (a convention). Model figures, exact arithmetic under the engine's noise model.
5
+ * @see docs/QUICK_START.md, "Native-pixel dynamic range (#595)".
6
+ */
7
+ import type { SensorFixedPatternNoiseProfile } from "./fixed-pattern-noise.js";
8
+ import { type NativeSensorInput, type SimulateCaptureNativeSensorReport, type SimulateCaptureSensorReport } from "./sensor-characteristics.js";
9
+ /** The native figures for a capture's report `rendered` (its regime, grey charge and dark charge). */
10
+ export declare function nativeSensorReport(rendered: SimulateCaptureSensorReport, regimeId: string, greyElectrons: number | undefined, darkElectrons: number, native: NativeSensorInput, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureNativeSensorReport;
@@ -0,0 +1,16 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { regimeRange, sensorReport } from "./sensor-characteristics.js";
3
+ /** The native figures for a capture's report `rendered` (its regime, grey charge and dark charge). */
4
+ export function nativeSensorReport(rendered, regimeId, greyElectrons, darkElectrons, native, pattern) {
5
+ const base = { pixelPitchMicrometers: rendered.pixelPitchMicrometers, peakQuantumEfficiency: rendered.peakQuantumEfficiency, readoutSeconds: rendered.readoutSeconds };
6
+ // #595: the same report at the native pitch; EMVA 1288 R4.0 Eq. (27) μ_e.min = √(σ_d² + σ_q²/K² + 1/4) + 1/2 and Eq. (28) DR = μ_e.sat / μ_e.min.
7
+ const p0 = native.pixelPitchMicrometers, area = (p0 / base.pixelPitchMicrometers) ** 2, own = sensorReport(native.profile, regimeId, native.capacityElectrons, greyElectrons && greyElectrons * area, darkElectrons, { ...base, pixelPitchMicrometers: p0 }, pattern);
8
+ const q = regimeRange(native.profile, native.profile.regimes.find((r) => r.regimeId === regimeId), native.capacityElectrons);
9
+ const threshold = Math.sqrt(q.read ** 2 + q.quantization ** 2 + 0.25) + 0.5, emva = Math.log2(q.saturation / threshold), normalization = 0.5 * Math.log2(native.framePhotosites / 8e6);
10
+ return { pixelPitchMicrometers: p0, photositesPerSite: 1 / area, fullWellElectrons: native.capacityElectrons, readNoiseElectrons: q.read, dynamicRangeStops: own.dynamicRangeStops,
11
+ dynamicRangeByIso: own.dynamicRangeByIso, emva1288: { absoluteSensitivityThresholdElectrons: threshold, dynamicRangeStops: emva, dynamicRangeDecibels: 20 * Math.log10(2) * emva,
12
+ saturationCapacityElectronsPerSquareMicrometer: q.saturation / (p0 * p0) },
13
+ normalized8Megapixel: { referencePixels: 8e6, framePhotosites: native.framePhotosites, dynamicRangeStops: own.dynamicRangeStops + normalization, emva1288DynamicRangeStops: emva + normalization },
14
+ ...(own.midGrey ? { midGrey: own.midGrey } : {}) };
15
+ }
16
+ //# sourceMappingURL=native-sensor-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-sensor-report.js","sourceRoot":"","sources":["../../src/sensor/native-sensor-report.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAUtC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAoG,MAAM,6BAA6B,CAAC;AAE1K,sGAAsG;AACtG,MAAM,UAAU,kBAAkB,CAAC,QAAqC,EAAE,QAAgB,EAAE,aAAiC,EAAE,aAAqB,EAAE,MAAyB,EAC7K,OAAwC;IACxC,MAAM,IAAI,GAAG,EAAE,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB,EAAE,cAAc,EAAE,QAAQ,CAAC,cAAc,EAAE,CAAC;IACvK,kJAAkJ;IAClJ,MAAM,EAAE,GAAG,MAAM,CAAC,qBAAqB,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,iBAAiB,EACzJ,aAAa,IAAI,aAAa,GAAG,IAAI,EAAE,aAAa,EAAE,EAAE,GAAG,IAAI,EAAE,qBAAqB,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;IAC3G,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAE,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC9H,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC,EAAE,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC;IACvL,OAAO,EAAE,qBAAqB,EAAE,EAAE,EAAE,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,CAAC,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;QAChL,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,EAAE,QAAQ,EAAE,EAAE,qCAAqC,EAAE,SAAS,EAAE,iBAAiB,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;YAC9K,8CAA8C,EAAE,CAAC,CAAC,UAAU,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;QAC5E,oBAAoB,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,GAAG,aAAa,EAAE,yBAAyB,EAAE,IAAI,GAAG,aAAa,EAAE;QAClM,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACvD,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #595 (2.22.0): `result.sensor.native`, the sensor figures of one native photosite, in a module of its own so that it can be loaded\n * apart from the capture's default path: the same report at the native pitch, the EMVA 1288 Release 4.0 dynamic range (§2.7,\n * Eq. 26–28) and the 8 MP-normalized one (a convention). Model figures, exact arithmetic under the engine's noise model.\n * @see docs/QUICK_START.md, \"Native-pixel dynamic range (#595)\".\n */\n\nimport type { SensorFixedPatternNoiseProfile } from \"./fixed-pattern-noise.js\";\nimport { regimeRange, sensorReport, type NativeSensorInput, type SimulateCaptureNativeSensorReport, type SimulateCaptureSensorReport } from \"./sensor-characteristics.js\";\n\n/** The native figures for a capture's report `rendered` (its regime, grey charge and dark charge). */\nexport function nativeSensorReport(rendered: SimulateCaptureSensorReport, regimeId: string, greyElectrons: number | undefined, darkElectrons: number, native: NativeSensorInput,\n pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureNativeSensorReport {\n const base = { pixelPitchMicrometers: rendered.pixelPitchMicrometers, peakQuantumEfficiency: rendered.peakQuantumEfficiency, readoutSeconds: rendered.readoutSeconds };\n // #595: the same report at the native pitch; EMVA 1288 R4.0 Eq. (27) μ_e.min = √(σ_d² + σ_q²/K² + 1/4) + 1/2 and Eq. (28) DR = μ_e.sat / μ_e.min.\n const p0 = native.pixelPitchMicrometers, area = (p0 / base.pixelPitchMicrometers) ** 2, own = sensorReport(native.profile, regimeId, native.capacityElectrons,\n greyElectrons && greyElectrons * area, darkElectrons, { ...base, pixelPitchMicrometers: p0 }, pattern);\n const q = regimeRange(native.profile, native.profile.regimes.find((r) => r.regimeId === regimeId)!, native.capacityElectrons);\n const threshold = Math.sqrt(q.read ** 2 + q.quantization ** 2 + 0.25) + 0.5, emva = Math.log2(q.saturation / threshold), normalization = 0.5 * Math.log2(native.framePhotosites / 8e6);\n return { pixelPitchMicrometers: p0, photositesPerSite: 1 / area, fullWellElectrons: native.capacityElectrons, readNoiseElectrons: q.read, dynamicRangeStops: own.dynamicRangeStops,\n dynamicRangeByIso: own.dynamicRangeByIso, emva1288: { absoluteSensitivityThresholdElectrons: threshold, dynamicRangeStops: emva, dynamicRangeDecibels: 20 * Math.log10(2) * emva,\n saturationCapacityElectronsPerSquareMicrometer: q.saturation / (p0 * p0) },\n normalized8Megapixel: { referencePixels: 8e6, framePhotosites: native.framePhotosites, dynamicRangeStops: own.dynamicRangeStops + normalization, emva1288DynamicRangeStops: emva + normalization },\n ...(own.midGrey ? { midGrey: own.midGrey } : {}) };\n}\n"]}