@photivra/engine 2.20.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 (275) hide show
  1. package/CHANGELOG.md +295 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +18 -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-lens-correction-opcodes.d.ts +2 -2
  13. package/dist/capture/dng-lens-correction-opcodes.js +2 -2
  14. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
  15. package/dist/capture/dng-opcodes.d.ts +96 -0
  16. package/dist/capture/dng-opcodes.js +391 -0
  17. package/dist/capture/dng-opcodes.js.map +1 -0
  18. package/dist/capture/dng-profile-maps.d.ts +99 -0
  19. package/dist/capture/dng-profile-maps.js +381 -0
  20. package/dist/capture/dng-profile-maps.js.map +1 -0
  21. package/dist/capture/dng-stage-image.d.ts +23 -0
  22. package/dist/capture/dng-stage-image.js +65 -0
  23. package/dist/capture/dng-stage-image.js.map +1 -0
  24. package/dist/capture/exposure-build-up-stage.d.ts +16 -0
  25. package/dist/capture/exposure-build-up-stage.js +4 -0
  26. package/dist/capture/exposure-build-up-stage.js.map +1 -0
  27. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  28. package/dist/capture/jpeg-xl-tiles.js +151 -0
  29. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  30. package/dist/capture/lossy-jpeg.d.ts +17 -0
  31. package/dist/capture/lossy-jpeg.js +369 -0
  32. package/dist/capture/lossy-jpeg.js.map +1 -0
  33. package/dist/capture/native-environment-lean.d.ts +2 -1
  34. package/dist/capture/native-environment-lean.js +41 -73
  35. package/dist/capture/native-environment-lean.js.map +1 -1
  36. package/dist/capture/native-environment-tabulated.js +4 -3
  37. package/dist/capture/native-environment-tabulated.js.map +1 -1
  38. package/dist/capture/native-raw-development.d.ts +2 -1
  39. package/dist/capture/native-raw-development.js.map +1 -1
  40. package/dist/capture/native-raw.d.ts +3 -2
  41. package/dist/capture/native-raw.js +5 -4
  42. package/dist/capture/native-raw.js.map +1 -1
  43. package/dist/capture/sensor-raw-producer.d.ts +4 -1
  44. package/dist/capture/sensor-raw-producer.js +12 -3
  45. package/dist/capture/sensor-raw-producer.js.map +1 -1
  46. package/dist/color/neighbor-highlight-reconstruction.d.ts +66 -0
  47. package/dist/color/neighbor-highlight-reconstruction.js +234 -0
  48. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  49. package/dist/color/sensor-color-development.d.ts +5 -0
  50. package/dist/color/sensor-color-development.js +7 -1
  51. package/dist/color/sensor-color-development.js.map +1 -1
  52. package/dist/color/spectral-spikiness.d.ts +11 -0
  53. package/dist/color/spectral-spikiness.js +23 -0
  54. package/dist/color/spectral-spikiness.js.map +1 -0
  55. package/dist/core/execution-error.d.ts +21 -0
  56. package/dist/core/execution-error.js +12 -0
  57. package/dist/core/execution-error.js.map +1 -0
  58. package/dist/core/version.d.ts +1 -1
  59. package/dist/core/version.js +1 -1
  60. package/dist/core/version.js.map +1 -1
  61. package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
  62. package/dist/equipment/generic-lens-catalogue.js +18 -5
  63. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  64. package/dist/equipment/generic-tier-assets.d.ts +32 -0
  65. package/dist/equipment/generic-tier-assets.js +16 -11
  66. package/dist/equipment/generic-tier-assets.js.map +1 -1
  67. package/dist/equipment/sensor-profile-body.d.ts +78 -0
  68. package/dist/equipment/sensor-profile-body.js +77 -0
  69. package/dist/equipment/sensor-profile-body.js.map +1 -0
  70. package/dist/equipment/sensor-profiles.d.ts +8 -0
  71. package/dist/equipment/sensor-profiles.js +82 -0
  72. package/dist/equipment/sensor-profiles.js.map +1 -0
  73. package/dist/facade/catalogue-lens-signal.d.ts +28 -3
  74. package/dist/facade/catalogue-lens-signal.js +83 -78
  75. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  76. package/dist/facade/chart-radiance-batch.js +2 -1
  77. package/dist/facade/chart-radiance-batch.js.map +1 -1
  78. package/dist/facade/defocus-image-space-provider.d.ts +7 -0
  79. package/dist/facade/defocus-image-space-provider.js +64 -76
  80. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  81. package/dist/facade/diffraction-provider.d.ts +1 -1
  82. package/dist/facade/diffraction-provider.js +4 -4
  83. package/dist/facade/diffraction-provider.js.map +1 -1
  84. package/dist/facade/display-view-gpu-session.js +39 -5
  85. package/dist/facade/display-view-gpu-session.js.map +1 -1
  86. package/dist/facade/display-view-gpu.d.ts +19 -6
  87. package/dist/facade/display-view-gpu.js.map +1 -1
  88. package/dist/facade/display-view-lens-signal.js +25 -23
  89. package/dist/facade/display-view-lens-signal.js.map +1 -1
  90. package/dist/facade/display-view-normal.d.ts +15 -0
  91. package/dist/facade/display-view-normal.js +44 -0
  92. package/dist/facade/display-view-normal.js.map +1 -0
  93. package/dist/facade/display-view-rendering.d.ts +97 -0
  94. package/dist/facade/display-view-rendering.js +598 -0
  95. package/dist/facade/display-view-rendering.js.map +1 -0
  96. package/dist/facade/display-view.d.ts +37 -65
  97. package/dist/facade/display-view.js +152 -499
  98. package/dist/facade/display-view.js.map +1 -1
  99. package/dist/facade/expected-signal-setup.d.ts +3 -1
  100. package/dist/facade/expected-signal-setup.js +3 -3
  101. package/dist/facade/expected-signal-setup.js.map +1 -1
  102. package/dist/facade/expected-signal.d.ts +47 -14
  103. package/dist/facade/expected-signal.js +117 -112
  104. package/dist/facade/expected-signal.js.map +1 -1
  105. package/dist/facade/exposure-build-up.d.ts +70 -0
  106. package/dist/facade/exposure-build-up.js +164 -0
  107. package/dist/facade/exposure-build-up.js.map +1 -0
  108. package/dist/facade/fast-methods.d.ts +1 -1
  109. package/dist/facade/fast-methods.js +46 -9
  110. package/dist/facade/fast-methods.js.map +1 -1
  111. package/dist/facade/generic-lens-capture.d.ts +3 -3
  112. package/dist/facade/generic-lens-capture.js +3 -3
  113. package/dist/facade/generic-lens-capture.js.map +1 -1
  114. package/dist/facade/image-plane-enable.d.ts +7 -0
  115. package/dist/facade/image-plane-enable.js +17 -0
  116. package/dist/facade/image-plane-enable.js.map +1 -0
  117. package/dist/facade/image-plane-lattice-data.d.ts +6 -0
  118. package/dist/facade/image-plane-lattice-data.js +10 -0
  119. package/dist/facade/image-plane-lattice-data.js.map +1 -0
  120. package/dist/facade/image-plane-support.d.ts +33 -0
  121. package/dist/facade/image-plane-support.js +11 -0
  122. package/dist/facade/image-plane-support.js.map +1 -0
  123. package/dist/facade/image-plane.d.ts +36 -0
  124. package/dist/facade/image-plane.js +384 -0
  125. package/dist/facade/image-plane.js.map +1 -0
  126. package/dist/facade/mixed-illumination.d.ts +108 -0
  127. package/dist/facade/mixed-illumination.js +174 -0
  128. package/dist/facade/mixed-illumination.js.map +1 -0
  129. package/dist/facade/on-demand-kits.d.ts +14 -0
  130. package/dist/facade/on-demand-kits.js +36 -0
  131. package/dist/facade/on-demand-kits.js.map +1 -0
  132. package/dist/facade/opt-in-kit.d.ts +9 -0
  133. package/dist/facade/opt-in-kit.js +11 -0
  134. package/dist/facade/opt-in-kit.js.map +1 -0
  135. package/dist/facade/quality-kit.d.ts +21 -0
  136. package/dist/facade/quality-kit.js +23 -0
  137. package/dist/facade/quality-kit.js.map +1 -0
  138. package/dist/facade/quality-paths.d.ts +22 -0
  139. package/dist/facade/quality-paths.js +44 -0
  140. package/dist/facade/quality-paths.js.map +1 -0
  141. package/dist/facade/representative-light-sources.d.ts +72 -0
  142. package/dist/facade/representative-light-sources.js +124 -0
  143. package/dist/facade/representative-light-sources.js.map +1 -0
  144. package/dist/facade/scene-radiance.d.ts +145 -4
  145. package/dist/facade/scene-radiance.js +50 -13
  146. package/dist/facade/scene-radiance.js.map +1 -1
  147. package/dist/facade/simulate-capture.d.ts +68 -20
  148. package/dist/facade/simulate-capture.js +399 -146
  149. package/dist/facade/simulate-capture.js.map +1 -1
  150. package/dist/facade/simulate-display-view.d.ts +10 -12
  151. package/dist/facade/simulate-display-view.js +70 -53
  152. package/dist/facade/simulate-display-view.js.map +1 -1
  153. package/dist/gpu/chart-composite-gpu.d.ts +13 -1
  154. package/dist/gpu/chart-composite-gpu.js +48 -9
  155. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  156. package/dist/gpu/chart-radiance-gpu.js +4 -30
  157. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  158. package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
  159. package/dist/gpu/display-view-composite-material-module.js +39 -13
  160. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  161. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  162. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  163. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  164. package/dist/gpu/display-view-image-material-module.d.ts +87 -0
  165. package/dist/gpu/display-view-image-material-module.js +361 -0
  166. package/dist/gpu/display-view-image-material-module.js.map +1 -0
  167. package/dist/gpu/display-view-image-quality.d.ts +87 -0
  168. package/dist/gpu/display-view-image-quality.js +625 -0
  169. package/dist/gpu/display-view-image-quality.js.map +1 -0
  170. package/dist/gpu/display-view-quality-gpu.d.ts +80 -65
  171. package/dist/gpu/display-view-quality-gpu.js +187 -445
  172. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  173. package/dist/gpu/display-view-speed-gpu.d.ts +67 -19
  174. package/dist/gpu/display-view-speed-gpu.js +181 -105
  175. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  176. package/dist/gpu/image-kernel-accelerator.js +3 -0
  177. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  178. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  179. package/dist/gpu/image-kernel-execution.js +2 -10
  180. package/dist/gpu/image-kernel-execution.js.map +1 -1
  181. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +8 -0
  182. package/dist/gpu/wgsl-display-view-coupled-stub.js +21 -0
  183. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  184. package/dist/gpu/wgsl-display-view-coupled.d.ts +88 -0
  185. package/dist/gpu/wgsl-display-view-coupled.js +353 -0
  186. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  187. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +33 -24
  188. package/dist/gpu/wgsl-display-view-quality-kernel.js +166 -146
  189. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  190. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +19 -44
  191. package/dist/gpu/wgsl-display-view-speed-kernel.js +116 -18
  192. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  193. package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
  194. package/dist/gpu/wgsl-exact-sum.js +42 -0
  195. package/dist/gpu/wgsl-exact-sum.js.map +1 -0
  196. package/dist/index.d.ts +22 -6
  197. package/dist/index.js +16 -2
  198. package/dist/index.js.map +1 -1
  199. package/dist/motion/subject-motion.d.ts +66 -0
  200. package/dist/motion/subject-motion.js +52 -0
  201. package/dist/motion/subject-motion.js.map +1 -0
  202. package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
  203. package/dist/optics/generic-lens-lateral-color.js +77 -0
  204. package/dist/optics/generic-lens-lateral-color.js.map +1 -0
  205. package/dist/optics/generic-lens-psf.js +54 -46
  206. package/dist/optics/generic-lens-psf.js.map +1 -1
  207. package/dist/optics/lens-psf-profile.d.ts +7 -0
  208. package/dist/optics/lens-psf-profile.js +2 -1
  209. package/dist/optics/lens-psf-profile.js.map +1 -1
  210. package/dist/output/camera-channel-response.d.ts +50 -0
  211. package/dist/output/camera-channel-response.js +69 -0
  212. package/dist/output/camera-channel-response.js.map +1 -0
  213. package/dist/output/developed-lens-corrections.d.ts +19 -5
  214. package/dist/output/developed-lens-corrections.js +28 -9
  215. package/dist/output/developed-lens-corrections.js.map +1 -1
  216. package/dist/output/generic-lens-corrections.d.ts +41 -5
  217. package/dist/output/generic-lens-corrections.js +52 -8
  218. package/dist/output/generic-lens-corrections.js.map +1 -1
  219. package/dist/output/native-capture-sdr.js +5 -2
  220. package/dist/output/native-capture-sdr.js.map +1 -1
  221. package/dist/output/print-plan.d.ts +41 -5
  222. package/dist/output/print-plan.js +59 -14
  223. package/dist/output/print-plan.js.map +1 -1
  224. package/dist/output/sdr-rendering.d.ts +18 -1
  225. package/dist/output/sdr-rendering.js +39 -2
  226. package/dist/output/sdr-rendering.js.map +1 -1
  227. package/dist/sensor/binomial-sample.d.ts +3 -0
  228. package/dist/sensor/binomial-sample.js +86 -0
  229. package/dist/sensor/binomial-sample.js.map +1 -0
  230. package/dist/sensor/native-sensor-report.d.ts +10 -0
  231. package/dist/sensor/native-sensor-report.js +16 -0
  232. package/dist/sensor/native-sensor-report.js.map +1 -0
  233. package/dist/sensor/sensor-characteristics.d.ts +91 -13
  234. package/dist/sensor/sensor-characteristics.js +30 -15
  235. package/dist/sensor/sensor-characteristics.js.map +1 -1
  236. package/docs/AGENT_GUIDE.md +1 -0
  237. package/docs/API_REFERENCE.md +214 -4
  238. package/docs/QUICK_START.md +162 -8
  239. package/docs/USAGE.md +195 -22
  240. package/docs/api/capture-c2pa-embed.md +177 -0
  241. package/docs/api/capture-dng-import.md +86 -10
  242. package/docs/api/capture-dng-opcodes.md +93 -0
  243. package/docs/api/capture-dng-profile-maps.md +141 -0
  244. package/docs/api/capture-dng-stage-image.md +43 -0
  245. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  246. package/docs/api/capture-lossy-jpeg.md +41 -0
  247. package/docs/api/capture-native-raw-development.md +2 -1
  248. package/docs/api/core-execution-error.md +43 -0
  249. package/docs/api/core-version.md +1 -1
  250. package/docs/api/equipment-generic-lens-catalogue.md +10 -5
  251. package/docs/api/equipment-sensor-profile-body.md +64 -0
  252. package/docs/api/equipment-sensor-profiles.md +86 -0
  253. package/docs/api/exports.json +460 -5
  254. package/docs/api/facade-display-view-gpu.md +5 -2
  255. package/docs/api/facade-display-view.md +11 -15
  256. package/docs/api/facade-exposure-build-up.md +105 -0
  257. package/docs/api/facade-fast-methods.md +47 -10
  258. package/docs/api/facade-image-plane-enable.md +16 -0
  259. package/docs/api/facade-mixed-illumination.md +67 -0
  260. package/docs/api/facade-quality-paths.md +24 -0
  261. package/docs/api/facade-representative-light-sources.md +131 -0
  262. package/docs/api/facade-scene-radiance.md +54 -3
  263. package/docs/api/facade-simulate-capture.md +48 -11
  264. package/docs/api/facade-simulate-display-view.md +10 -12
  265. package/docs/api/gpu-image-kernel-execution.md +0 -36
  266. package/docs/api/motion-subject-motion.md +23 -0
  267. package/docs/api/optics-generic-lens-lateral-color.md +79 -0
  268. package/docs/api/output-camera-channel-response.md +55 -0
  269. package/docs/api/output-developed-lens-corrections.md +10 -1
  270. package/docs/api/output-generic-lens-corrections.md +40 -4
  271. package/docs/api/output-print-plan.md +35 -1
  272. package/docs/api/output-sdr-rendering.md +6 -1
  273. package/docs/api/sensor-sensor-characteristics.md +54 -2
  274. package/llms.txt +3 -0
  275. package/package.json +6 -5
@@ -70,10 +70,14 @@ export interface DisplayViewResult {
70
70
  sensor: SimulateCaptureSensorReport;
71
71
  /**
72
72
  * Work done: real pixels sampled, real-site expectations evaluated, scene queries, display pixels sampled in full (detail) or with
73
- * a quarter of the samples per axis (uniform), display pixels whose value includes the AHD low-signal correction, and wall-clock
74
- * milliseconds (diagnostic only; never affects the samples).
73
+ * a quarter of the samples per axis (uniform), and wall-clock milliseconds (diagnostic only; never affects the samples).
75
74
  */
76
- diagnostics: { sampleCount: number; siteEvaluations: number; sceneLookups: number; detailPixelCount: number; uniformPixelCount: number; ahdCorrectedPixelCount: number;
75
+ diagnostics: { sampleCount: number; siteEvaluations: number; sceneLookups: number; detailPixelCount: number; uniformPixelCount: number;
76
+ /**
77
+ * Display pixels whose value included the AHD low-signal correction (2.10–2.21).
78
+ * @deprecated Since 2.22.0 the view has no such correction, and this is always 0. Removed in 3.0.0.
79
+ */
80
+ ahdCorrectedPixelCount: number;
77
81
  timingsMs: { view: number; total: number };
78
82
  /** #395: exact or fast, as requested and as run; 2.11 (#481): plus `execution`, where the render ran (see {@link DisplayViewFastReport}). */
79
83
  fast: DisplayViewFastReport;
@@ -144,7 +148,7 @@ a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.tempor
144
148
 
145
149
  ```ts
146
150
  SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
147
- "lens.diffraction", "lens.defocus", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
151
+ "lens.diffraction", "lens.defocus", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "sensor.spectralStepNanometers", "motion.rotation", "motion.subject", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
148
152
  "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual",
149
153
  "output.diffractionCorrection", "output.sharpening", "output.lensCorrections", "output.format.jpeg", "output.format.png", "output.format.dng"] as const)
150
154
  ```
@@ -175,7 +179,8 @@ the record form with `fast` and is reported in `diagnostics.fast.applied` when i
175
179
  whether or not it has a fast method yet; one without runs exact and reports `false`.
176
180
 
177
181
  ```ts
178
- SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"] as const)
182
+ SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd", "motion.subject",
183
+ "sensor.spectralStepNanometers"] as const)
179
184
  ```
180
185
 
181
186
  ## SIMULATE_CAPTURE_METERING_MODES
@@ -473,8 +478,9 @@ export interface SimulateCaptureFastReport {
473
478
 
474
479
  `lens.catalogue` as a record (#568): the lens id as `kind` and the `fast` override (see {@link SimulateCaptureOptions.fast}).
475
480
  Fast: the tabulated fast method (#568), and with `lens.defocus` the lens's bokeh without its cat's eye (#567; fast wides and
476
- normals only), whose field-varying PSF needs a finer fast grid (measured 2.2–3.4× slower). Exact: the per-site PSF with the
477
- cat's eye. Blades and spherical aberration always apply (they cost nothing).
481
+ normals only), whose field-varying PSF needs a finer fast grid (measured 2.2–3.4× slower); with camera rotation under
482
+ `motion.shutter: "rolling"`, the per-tap PSF points instead of the lattice reading (#588). Exact: the per-site PSF with the
483
+ cat's eye, read on the lattice. Blades and spherical aberration always apply (they cost nothing).
478
484
 
479
485
  ```ts
480
486
  export interface SimulateCaptureLensCatalogueRecord { kind: string; fast?: boolean }
@@ -529,6 +535,11 @@ export interface SimulateCaptureMotion {
529
535
  shutter?: "global" | "rolling";
530
536
  /** Rolling shutter only: time from the first to the last line over the simulated frame, in s. Default: the body's declared readout skew. */
531
537
  rollingShutterSkewSeconds?: number;
538
+ /**
539
+ * #551: a moving subject: the whole scene, or one rectangle of it over a still background, translating on the scene plane
540
+ * (and optionally rotating about the optical axis) during the exposure. Off by default (a still scene).
541
+ */
542
+ subject?: SimulateCaptureSubjectMotion;
532
543
  }
533
544
  ```
534
545
 
@@ -672,9 +683,10 @@ export interface SimulateCaptureOptions {
672
683
  /**
673
684
  * Required: an engine chart (for example `{ preset: "uniform" }`, an 18 % grey card), a declared `material-layers` scene (#491:
674
685
  * a chart under shape-mask layers, as `resolveChartMaterialLayers` takes it; the capture is that of its `callerScene`, byte for
675
- * byte, and the display view can render it on the GPU), or a caller reflectance, radiance or material function.
686
+ * byte, and the display view can render it on the GPU), a caller reflectance, radiance or material function, or (2.21, #581) an
687
+ * `image-plane` scene: a photo as a flat print (docs/IMAGE_PLANE.md).
676
688
  */
677
- scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene;
689
+ scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene | ImagePlaneScene;
678
690
  /** Distance from the camera to the scene plane in metres. Default 5. */
679
691
  subjectDistanceM?: number;
680
692
  /**
@@ -689,6 +701,12 @@ export interface SimulateCaptureOptions {
689
701
  illuminant?: CaptureIlluminant;
690
702
  /** Illuminance on the scene plane, lux. Default about 21 481 lux (see `SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX` and the assumptions). */
691
703
  illuminanceLux?: number;
704
+ /**
705
+ * 2.22 (#550), opt-in mixed lighting: two or more sources blended across the chart (docs/MIXED_ILLUMINATION.md), instead of
706
+ * `illuminant` and `illuminanceLux`. The frame center's light sets the grey-card meter and the standard-exposure figures, and
707
+ * its strongest source the "scene-illuminant" white balance; both are reported in `assumptions`.
708
+ */
709
+ illumination?: MixedIllumination;
692
710
  output?: {
693
711
  /** Output pixels; the native sensor raster adds a one-site demosaic border on each side. Defaults 64 × 64. */
694
712
  width?: number;
@@ -699,8 +717,12 @@ export interface SimulateCaptureOptions {
699
717
  toneCurve?: SdrToneCurve;
700
718
  /** Required by the luminance tone curves (see `SdrToneCurveParameters`). The facade does not make one up. */
701
719
  toneCurveParameters?: SdrToneCurveParameters;
702
- /** Default "clip". */
703
- highlightMode?: RawHighlightMode;
720
+ /**
721
+ * Default "clip". 2.22.0 (#602, opt-in): "reconstruct-neighbors" rebuilds clipped channels from color ratios of the unclipped
722
+ * pixels around each clipped region (an estimate; `src/color/neighbor-highlight-reconstruction.ts`): the ring's mean ratios
723
+ * (fast) or their harmonic interpolation inward (full). Whole-image, so not with `output.region`.
724
+ */
725
+ highlightMode?: SimulateCaptureHighlightMode;
704
726
  /**
705
727
  * Opt-in (#556) development exposure push in stops, −2 to +5 (`NATIVE_RAW_DEVELOPMENT_EXPOSURE_RANGE_STOPS`); default 0.
706
728
  * A linear gain 2^stops on the RAW after black subtraction and white normalization, before demosaic, white balance,
@@ -711,6 +733,14 @@ export interface SimulateCaptureOptions {
711
733
  * honour it. Reported in the assumption `output.exposureCompensationStops`. 0 or omitted: byte-identical to before.
712
734
  */
713
735
  exposureCompensationStops?: number;
736
+ /**
737
+ * #601 (2.22.0, opt-in): the sRGB rendering's gamut handling after the tone curve. "clip-components" (the default) clips
738
+ * each component to [0, 1]; "desaturate-to-luminance" moves a pixel whose largest component exceeds 1 toward the gray of its
739
+ * own luminance until that component is 1, keeping its luminance and hue (`desaturateToLuminance`, src/output/sdr-rendering.ts),
740
+ * instead of shifting bright saturated colors toward the secondaries. sRGB output only (not with a wide-gamut `colorEncoding`
741
+ * or `gamutMapping`). Reported in the assumption `output.gamutHandling`. Omitted: byte-identical to before.
742
+ */
743
+ gamutHandling?: SdrRenderingProfile["gamutHandling"] & ("clip-components" | "desaturate-to-luminance");
714
744
  /** Default "scene-illuminant". */
715
745
  whiteBalance?: SimulateCaptureWhiteBalance;
716
746
  /** JPEG constant quantization step 1-255 (not a manufacturer 'quality'). Default 1 (2.20.0; 2 before), the full-quality export. */
@@ -788,6 +818,13 @@ export interface SimulateCaptureOptions {
788
818
  motion?: SimulateCaptureMotion;
789
819
  /** #555: plus the sensor characteristics ({@link SimulateCaptureSensorCharacteristics}), each off by default. */
790
820
  sensor?: SimulateCaptureSensorCharacteristics & {
821
+ /**
822
+ * #554: a sensor profile record (`SENSOR_PROFILES`, or `resolveSensorProfile(id)`), checked strictly. It replaces the body's ISO range, readout regimes
823
+ * (full well, read noise of each conversion gain, the dual-gain switch, high-gain storage, ADC bit depth), readout time and
824
+ * fixed-pattern noise, and sets the default pixel pitch and peak QE; explicit fields here still override it one at a time.
825
+ * Default: none (the body's own sensor). See docs/SENSOR_PROFILES.md.
826
+ */
827
+ profile?: SensorProfile;
791
828
  /**
792
829
  * Pixel pitch in micrometres, 0.5–1000. Default 6. The simulated sensor is output width × pitch wide. 2.5: above 100 µm the
793
830
  * capture is a downsized view of a whole frame (for example 36 mm / 48 pixels = 750 µm), recorded in the assumption
@@ -12,8 +12,7 @@ setup: use it to render one view in many regions, for example on each worker of
12
12
  ```ts
13
13
  export interface PreparedDisplayView {
14
14
  /**
15
- * Renders a view of the prepared request. Renders may run concurrently; they share no state except read-only setup and the
16
- * realm's AHD correction states, which never change a byte (docs/DISPLAY_VIEW.md, "AHD at low signal"). With `gpu: "auto"`
15
+ * Renders a view of the prepared request. Renders may run concurrently; they share no state except read-only setup. With `gpu: "auto"`
17
16
  * or `"require"` (#481) GPU renders of one prepared view run one at a time on its device, and `diagnostics.fast.execution`
18
17
  * says whether the GPU or the CPU reference rendered the view, and why.
19
18
  */
@@ -25,22 +24,21 @@ export interface PreparedDisplayView {
25
24
  */
26
25
  dispose(): void;
27
26
  /**
28
- * 2.10 (#447): the key of this request's AHD low-signal correction states, or null when the request has no AHD
29
- * (`output.demosaic` other than "ahd"). Every input of the key is fixed when the request is prepared; two prepared views
30
- * with the same key read the same states.
27
+ * 2.10 (#447), deprecated in 2.22.0: was the key of this request's AHD low-signal correction states. The view has no such
28
+ * states since 2.22.0 (docs/DISPLAY_VIEW.md, "AHD at low signal"), so this returns null for every request.
29
+ * @deprecated Since 2.22.0: always null. Removed in 3.0.0.
31
30
  */
32
31
  correctionKey(): string | null;
33
32
  /**
34
- * 2.10 (#447): the grid keys of every correction state a render of `view` reads (ascending, whether or not this realm holds
35
- * them), found by a render of the view's region that records the states instead of computing them. No image is returned;
36
- * it costs about one render whose states are all held. Fails with an InvalidConfigurationError when the request has no AHD.
33
+ * 2.10 (#447), deprecated in 2.22.0: was the grid keys of the correction states a render of `view` read. Since 2.22.0 it checks
34
+ * `view` as `render` does and returns `{ key: "", gridKeys }` with no grid keys, for every request; it renders nothing.
35
+ * @deprecated Since 2.22.0: returns no grid keys. Removed in 3.0.0.
37
36
  */
38
37
  collectCorrectionStates(view: SimulateDisplayViewOptions, renderOptions?: PreparedDisplayViewRenderOptions): Promise<DisplayViewCorrectionStateKeys>;
39
38
  /**
40
- * 2.10 (#447): computes the correction states of `gridKeys` (from `collectCorrectionStates`, or a share of them) as a render
41
- * would, stores them in this realm's cache and returns their values, for {@link bindDisplayViewCorrectionStates} in
42
- * another realm. States this realm holds are read, not computed. Yields and honours `signal` like `render`. Fails with an
43
- * InvalidConfigurationError when the request has no AHD or a grid key is out of range.
39
+ * 2.10 (#447), deprecated in 2.22.0: computed correction states. Since 2.22.0 it checks that `gridKeys` is an Int32Array of
40
+ * grid keys (integers in [0, 97³)) and returns `{ key: "", gridKeys, values }` with no states, for every request; it computes nothing.
41
+ * @deprecated Since 2.22.0: returns no states. Removed in 3.0.0.
44
42
  */
45
43
  computeCorrectionStates(gridKeys: Int32Array, renderOptions?: PreparedDisplayViewRenderOptions): Promise<DisplayViewCorrectionStateSet>;
46
44
  }
@@ -139,42 +139,6 @@ export interface ImageKernelExecution {
139
139
  }
140
140
  ```
141
141
 
142
- ## ImageKernelExecutionError
143
-
144
- Domain error for getting and running a backend. Input validation still throws the engine's existing errors.
145
-
146
- ```ts
147
- export class ImageKernelExecutionError extends Error {
148
- readonly code = "IMAGE_KERNEL_EXECUTION";
149
- readonly reason: ImageKernelFailureReason;
150
-
151
- constructor(reason: ImageKernelFailureReason, message: string) {
152
- super(message);
153
- this.name = "ImageKernelExecutionError";
154
- this.reason = reason;
155
- }
156
- }
157
- ```
158
-
159
- ## ImageKernelFailureReason
160
-
161
- Failure classes reported by {@link ImageKernelExecutionError}. `conformance-failed` (#481): a device failed the display view's
162
- run-time self-check with `gpu: "require"`.
163
-
164
- ```ts
165
- export type ImageKernelFailureReason = ImageKernelFallbackReason | "device-lost" | "out-of-memory" | "gpu-validation" |
166
- "aborted" | "disposed" | "busy" | "unsupported-on-backend" | "unsupported-float32-domain" | "indeterminate-float32-classification" | "conformance-failed";
167
- ```
168
-
169
- ## ImageKernelFallbackReason
170
-
171
- Why a requested `webgpu-f32` backend ran the CPU reference instead. `webgpu-disabled`: `setWebGpuEnabled(false)` in this realm.
172
-
173
- ```ts
174
- export type ImageKernelFallbackReason = "webgpu-unavailable" | "webgpu-disabled" | "adapter-unavailable" | "device-request-failed" |
175
- "insufficient-limits" | "unsupported-platform-endianness";
176
- ```
177
-
178
142
  ## ImageKernelRelativeErrorBound
179
143
 
180
144
  Non-negative linear sums: |v_webgpu − v_reference| ≤ relative·v_reference + absolute for every sample.
@@ -0,0 +1,23 @@
1
+ # motion/subject-motion.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## SimulateCaptureSubjectMotion
6
+
7
+ `motion.subject` of `simulateCapture`: a moving subject on the scene plane (#551).
8
+
9
+ ```ts
10
+ export interface SimulateCaptureSubjectMotion {
11
+ /** Velocity on the scene plane, m/s: +x right, +y up as seen from the camera. Missing components are 0. */
12
+ velocityMPerSec?: { x?: number; y?: number };
13
+ /** Rotation about the optical axis direction, rad/s, counter-clockwise as seen from the camera (+x toward +y). Default 0. */
14
+ angularVelocityRadPerSec?: number;
15
+ /**
16
+ * The moving part: a rectangle on the scene plane at rest (at the first opening reference), its centre and size in metres
17
+ * (+x right, +y up from the optical axis). Omitted: the whole scene moves.
18
+ */
19
+ region?: { centerXM: number; centerYM: number; widthM: number; heightM: number };
20
+ /** Exact or fast (2.20.0 convention): fast takes fewer temporal midpoints when the subject's streak is short (see the docs). */
21
+ fast?: boolean;
22
+ }
23
+ ```
@@ -0,0 +1,79 @@
1
+ # optics/generic-lens-lateral-color.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## calculateLateralColorFringe
6
+
7
+ The red–blue image displacement of a catalogue lens at a field position on a sensor, in micrometres and pixels, from the
8
+ lens's own lateral-colour model Δr(λ) = L·R·H·D(λ) at the lens corrections' channel wavelengths (610 and 465 nm), or at the
9
+ given `channelDispersion`. Exact for the model (no fast mode); an approximation of a generic lens, not a measured product.
10
+
11
+ ```ts
12
+ export function calculateLateralColorFringe(input: CalculateLateralColorFringeInput): CalculationResult<LateralColorFringe>;
13
+ ```
14
+
15
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
16
+
17
+ ## CalculateLateralColorFringeInput
18
+
19
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
20
+
21
+ ```ts
22
+ export interface CalculateLateralColorFringeInput {
23
+ /** A catalogue id (`GENERIC_LENS_IDS`). */
24
+ lensId: string;
25
+ /** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */
26
+ activeImagingArea: { widthMm: number; heightMm: number };
27
+ /** The sensor's pixel pitch, µm. */
28
+ pixelPitchMicrometers: number;
29
+ /**
30
+ * The field position as fractions of the frame's half-width (x, +right) and half-height (y, +up) from the optical axis, each
31
+ * in [−1, 1]. Default `{ x: 1, y: 1 }`: the top-right corner.
32
+ */
33
+ fieldPosition?: { x: number; y: number };
34
+ /**
35
+ * 2.21.0: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light;
36
+ * only `red` and `blue` are read). Default: D at the single wavelengths 610 and 465 nm.
37
+ */
38
+ channelDispersion?: ChannelDispersion;
39
+ }
40
+ ```
41
+
42
+ ## LateralColorDisplacement
43
+
44
+ A signed displacement of the blue image from the red one: radial (positive: blue farther from the axis) and its x and y parts.
45
+
46
+ ```ts
47
+ export interface LateralColorDisplacement {
48
+ radial: number;
49
+ x: number;
50
+ y: number;
51
+ }
52
+ ```
53
+
54
+ ## LateralColorFringe
55
+
56
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
57
+
58
+ ```ts
59
+ export interface LateralColorFringe {
60
+ lensId: string;
61
+ /** The image-plane point, mm from the optical axis (+x right, +y up). */
62
+ imagePointMm: { x: number; y: number };
63
+ /** Field height H: the point's distance from the axis as a fraction of the half-diagonal (1 at the corner). */
64
+ fieldHeight: number;
65
+ /** The channel wavelengths the displacement is taken at, nm; null with a given `channelDispersion`. */
66
+ channelWavelengthsNm: { red: number; blue: number } | null;
67
+ /** The red and blue dispersions D used (normalized: 0 at the d line, F minus C = 1). */
68
+ channelDispersion: { red: number; blue: number };
69
+ /** Blue-minus-red image displacement, µm. */
70
+ blueMinusRedMicrometers: LateralColorDisplacement;
71
+ /** The same in pixels of the given pitch. */
72
+ blueMinusRedPixels: LateralColorDisplacement;
73
+ /** The fringe width: |radial| displacement, µm and pixels. */
74
+ fringeWidthMicrometers: number;
75
+ fringeWidthPixels: number;
76
+ /** The lens's own F-to-C (486 to 656 nm) displacement at this point, L·R·H, µm. */
77
+ fToCMicrometers: number;
78
+ }
79
+ ```
@@ -0,0 +1,55 @@
1
+ # output/camera-channel-response.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## calculateCameraChannelResponse
6
+
7
+ #591: the camera's channel response on its spectral grid and, with `illuminant`, each channel's effective dispersion (pass it
8
+ as `channelDispersion` to `calculateLateralColorFringe` or `createGenericLensCorrectionProfile`). Strict input. A labelled
9
+ approximation: the facade's synthetic colorimetric sensor, not a measured one.
10
+
11
+ ```ts
12
+ export function calculateCameraChannelResponse(input: CalculateCameraChannelResponseInput = {}): CalculationResult<CameraChannelResponse>;
13
+ ```
14
+
15
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
16
+
17
+ ## CalculateCameraChannelResponseInput
18
+
19
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
20
+
21
+ ```ts
22
+ export interface CalculateCameraChannelResponseInput {
23
+ /**
24
+ * The capture's `sensor.profile` record (a `SensorProfile`, for example `resolveSensorProfile(id)`): only its
25
+ * `peakQuantumEfficiency` is read, the one field that changes the channels. Omitted: a tier body (peak QE 0.5).
26
+ */
27
+ sensorProfile?: Pick<SensorProfile, "peakQuantumEfficiency">;
28
+ /** Peak quantum efficiency in (0, 1] (as `sensor.peakQuantumEfficiency`); wins over the profile's. */
29
+ peakQuantumEfficiency?: number;
30
+ /**
31
+ * The capture's `sensor.spectralStepNanometers`, 1–100. Default 5: the calculator runs at full quality, and a spiky light
32
+ * needs the fine grid. Pass the capture's step to reproduce a capture's channels.
33
+ */
34
+ spectralStepNanometers?: number;
35
+ /** A `simulateCapture` illuminant: when given, the result also carries each channel's effective dispersion under it. */
36
+ illuminant?: CaptureIlluminant;
37
+ }
38
+ ```
39
+
40
+ ## CameraChannelResponse
41
+
42
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
43
+
44
+ ```ts
45
+ export interface CameraChannelResponse {
46
+ /** The capture's midpoint nodes, nm, increasing. */
47
+ wavelengthsNm: number[];
48
+ /** Electrons per joule of incident light at each node, per channel: EQE_c(λ)·λ/(h·c). */
49
+ red: number[];
50
+ green: number[];
51
+ blue: number[];
52
+ /** With `illuminant`: D̄_c, as `calculateChannelEffectiveDispersion` returns for these channels; else null. */
53
+ channelDispersion: ChannelDispersion | null;
54
+ }
55
+ ```
@@ -2,10 +2,19 @@
2
2
 
3
3
  The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
4
 
5
+ ## ChannelDispersion
6
+
7
+ A normalized dispersion per channel (`genericLensDispersion` units: 0 at the d line, F minus C = 1).
8
+
9
+ ```ts
10
+ export type ChannelDispersion = Readonly<Record<LensCorrectionChannel, number>>;
11
+ ```
12
+
5
13
  ## GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM
6
14
 
7
15
  Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where
8
- a camera's channel responses peak, not a measured camera.
16
+ a camera's channel responses peak, not a measured camera. The default of `createGenericLensCorrectionProfile` without a
17
+ `channelDispersion`; `simulateCapture`'s `output.lensCorrections` uses the capture's own channels instead (#589).
9
18
 
10
19
  ```ts
11
20
  GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = Object.freeze({ red: 610, green: 540, blue: 465 })
@@ -2,13 +2,44 @@
2
2
 
3
3
  The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
4
 
5
+ ## calculateChannelEffectiveDispersion
6
+
7
+ #589: each channel's effective dispersion for the catalogue lenses' lateral colour, D̄_c = Σ w·D(λ) / Σ w with
8
+ w = response_c(λ)·illuminant(λ) at the nodes and D the normalised Cauchy dispersion (`genericLensDispersion`: 0 at the
9
+ Fraunhofer d line, F minus C = 1). Under lateral colour L·r·D(λ) a neutral surface's image in channel c moves by L·r·D̄_c, so
10
+ blue minus red is L·r·(D̄_blue − D̄_red). Pass the result as `channelDispersion` to `createGenericLensCorrectionProfile`.
11
+ `simulateCapture`'s `output.lensCorrections` computes it from the capture's own channels and reports it in its assumption.
12
+ An approximation: a weighted mean over a neutral surface; a colored surface weights the band differently.
13
+
14
+ ```ts
15
+ export function calculateChannelEffectiveDispersion(input: CalculateChannelEffectiveDispersionInput): CalculationResult<ChannelDispersion>;
16
+ ```
17
+
18
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
19
+
20
+ ## CalculateChannelEffectiveDispersionInput
21
+
22
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
23
+
24
+ ```ts
25
+ export interface CalculateChannelEffectiveDispersionInput {
26
+ /** Spectral nodes, nm, strictly increasing, within 300–1100 nm. */
27
+ wavelengthsNm: readonly number[];
28
+ /** Each channel's response at the nodes (any common linear unit, ≥ 0; for example electrons per unit spectral radiance). */
29
+ channelResponse: { red: readonly number[]; green: readonly number[]; blue: readonly number[] };
30
+ /** The illuminant's relative spectral power at the nodes (≥ 0; the scale cancels). */
31
+ illuminant: readonly number[];
32
+ }
33
+ ```
34
+
5
35
  ## createGenericLensCorrectionProfile
6
36
 
7
37
  Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
8
- r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
9
- relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
10
- (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
11
- sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
38
+ r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D̄_channel (`channelDispersion`,
39
+ by default D at the generic channel wavelengths), and the relative-illumination gain at the state's aperture (strength 1). From
40
+ profile version 1.1.0 (#584) the vignetting component (version 1.1.0) carries the distortion's area factor
41
+ (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's sensor point, so it flattens a flat-field capture. The
42
+ result passes `parseGenericLensCorrectionProfile`.
12
43
 
13
44
  ```ts
14
45
  export function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -28,5 +59,10 @@ export interface CreateGenericLensCorrectionProfileInput {
28
59
  activeImagingArea: { widthMm: number; heightMm: number };
29
60
  /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */
30
61
  state: OpticalProfileState;
62
+ /**
63
+ * #589: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light).
64
+ * Default: D at {@link GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM} (610, 540 and 465 nm), a generic approximation.
65
+ */
66
+ channelDispersion?: ChannelDispersion;
31
67
  }
32
68
  ```
@@ -42,14 +42,44 @@ export function parsePrintPlanInput(value: unknown): PrintPlanInput;
42
42
 
43
43
  This call is synchronous; any supplied provider must follow its explicit synchronous contract.
44
44
 
45
+ ## PrintContainLayout
46
+
47
+ The layout of a `contain` fit, in millimetres. The printed image itself is the plan's `printedImageMm`.
48
+
49
+ ```ts
50
+ export interface PrintContainLayout {
51
+ /** The paper as placed: after the quarter turn when `paperRotated`. */
52
+ paperMm: { width: number; height: number };
53
+ paperRotated: boolean;
54
+ /** Unprinted border on each side of the centered image; zero when the paper has the image's aspect ratio. */
55
+ bordersMm: { left: number; right: number; top: number; bottom: number };
56
+ }
57
+ ```
58
+
45
59
  ## PrintedImageSize
46
60
 
47
61
  The flat printed image area. It excludes paper margins, borders and canvas wraps.
62
+ With a `contain` fit it is instead the paper, or the printable area of it, that the image is fitted inside.
48
63
 
49
64
  ```ts
50
65
  export interface PrintedImageSize { width: number; height: number; unit: PrintLength["unit"] }
51
66
  ```
52
67
 
68
+ ## PrintFit
69
+
70
+ How the printed image relates to `printedImage`.
71
+ - `confirmed-native-aspect`: `printedImage` is the printed image itself and must already have the native aspect ratio,
72
+ within the stated relative error.
73
+ - `contain`: `printedImage` is the paper. The image keeps the native aspect ratio and is scaled to the largest size that
74
+ fits inside it, centered; the rest is unprinted border. `match-image` turns the paper a quarter turn when its
75
+ orientation (portrait or landscape) differs from the image's; `as-given` keeps it. Layout only: nothing is cropped or resampled.
76
+
77
+ ```ts
78
+ export type PrintFit =
79
+ | { kind: "confirmed-native-aspect"; maximumRelativeAspectError: number }
80
+ | { kind: "contain"; paperOrientation: "as-given" | "match-image" };
81
+ ```
82
+
53
83
  ## PrintLength
54
84
 
55
85
  A physical length. Not to be confused with image pixels or printer dots.
@@ -90,6 +120,8 @@ export interface PrintPlan {
90
120
  alternatives: { maximumImageMmAtCurrentDistance: { width: number; height: number }; minimumViewingDistanceMmAtCurrentSize: number | null } | null;
91
121
  capturedDetailAssessment: "unassessed";
92
122
  deliveryAssessment: "unassessed";
123
+ /** Present only with a `contain` fit; null when the native raster is unknown. */
124
+ contain?: PrintContainLayout | null;
93
125
  }
94
126
  ```
95
127
 
@@ -103,7 +135,7 @@ export interface PrintPlanInput {
103
135
  printedImage: PrintedImageSize;
104
136
  viewingDistance: PrintLength;
105
137
  sampling: PrintSamplingCriterion;
106
- fit: { kind: "confirmed-native-aspect"; maximumRelativeAspectError: number };
138
+ fit: PrintFit;
107
139
  provider?: PrintProviderConstraints;
108
140
  }
109
141
  ```
@@ -156,6 +188,8 @@ export interface PrintSizeLimit {
156
188
  maximumImageMm: { width: number; height: number } | null;
157
189
  reasons: string[];
158
190
  referencePlan: PrintPlan;
191
+ /** Present only with a `contain` fit: the paper that holds `maximumImageMm` with the same layout (borders scale with it). */
192
+ maximumPaperMm?: { width: number; height: number } | null;
159
193
  }
160
194
  ```
161
195
 
@@ -83,7 +83,12 @@ export interface SdrRenderingProfile {
83
83
  toneCurve: SdrToneCurve;
84
84
  /** Present exactly for `extended-reinhard-luminance` ({whiteLuminance}) and `naka-rushton-luminance`. */
85
85
  toneCurveParameters?: SdrToneCurveParameters;
86
- gamutHandling: "clip-components" | "reject-out-of-range";
86
+ /**
87
+ * 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
88
+ * largest component exceeds 1 toward the gray of its own luminance until that component is 1 (`desaturateToLuminance`), then
89
+ * clip what remains below 0.
90
+ */
91
+ gamutHandling: "clip-components" | "reject-out-of-range" | "desaturate-to-luminance";
87
92
  outputDynamicRange: "sdr";
88
93
  transferFunction: "srgb";
89
94
  bitDepth: 8 | 16;
@@ -2,6 +2,49 @@
2
2
 
3
3
  The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
4
 
5
+ ## SimulateCaptureMidGreyFigure
6
+
7
+ #535: the mid-grey noise figure of {@link SimulateCaptureSensorReport.midGrey}.
8
+
9
+ ```ts
10
+ export interface SimulateCaptureMidGreyFigure { reflectance: 0.18; channel: "green"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean }
11
+ ```
12
+
13
+ ## SimulateCaptureNativeSensorReport
14
+
15
+ #595: {@link SimulateCaptureSensorReport.native}, the figures of one native photosite.
16
+
17
+ ```ts
18
+ export interface SimulateCaptureNativeSensorReport {
19
+ /** The native photosite pitch, µm (tiers: 6; a profile: its own). */
20
+ pixelPitchMicrometers: number;
21
+ /** N = (rendered pitch / native pitch)²: the photosites one rendered site stands for (below 1, a site smaller than a photosite). */
22
+ photositesPerSite: number;
23
+ /** Full-well capacity of one photosite, e⁻. */
24
+ fullWellElectrons: number;
25
+ /** Read noise of one photosite at the capture's ISO, RMS e⁻ (the capture's readout regime). */
26
+ readNoiseElectrons: number;
27
+ /** Engineering dynamic range of one photosite at the capture's ISO, stops (the definition of `dynamicRangeStops`). */
28
+ dynamicRangeStops: number;
29
+ /** The same per ISO setting of the body. */
30
+ dynamicRangeByIso: readonly { iso: number; stops: number }[];
31
+ /**
32
+ * EMVA 1288 Release 4.0 (§2.7, Eq. 26–28) at the capture's ISO: the absolute sensitivity threshold μ_e.min = √(σ_d² + σ_q² + 1/4) + 1/2
33
+ * (σ_d the read noise, dark current excluded; σ_q the quantization noise in e⁻), DR = μ_e.sat / μ_e.min in stops and dB, and the
34
+ * saturation capacity per area μ_e.sat / p² (Table 2).
35
+ */
36
+ emva1288: { absoluteSensitivityThresholdElectrons: number; dynamicRangeStops: number; dynamicRangeDecibels: number; saturationCapacityElectronsPerSquareMicrometer: number };
37
+ /**
38
+ * A convention, not part of EMVA 1288: the dynamic range of the whole frame's native photosites resampled to 8 million output
39
+ * pixels, each summing framePhotosites / 8e6 photosites with their read noise (EMVA 1288 R4.0 §6.4, binning):
40
+ * DR + ½·log₂(framePhotosites / 8e6), for the engineering and the EMVA figure. No ADC limit is applied to the sum.
41
+ */
42
+ normalized8Megapixel: { referencePixels: 8_000_000; framePhotosites: number; dynamicRangeStops: number; emva1288DynamicRangeStops: number };
43
+ /** The mid-grey figure for one photosite (its charge (native/rendered pitch)² of the site's), present when `midGrey` is. */
44
+ midGrey?: SimulateCaptureMidGreyFigure;
45
+ }
46
+ ```
47
+
5
48
  ## SimulateCaptureNoiseComponents
6
49
 
7
50
  One RMS noise component of the mid-grey figure, e⁻ (input-referred).
@@ -45,7 +88,9 @@ export interface SimulateCaptureSensorCharacteristics {
45
88
  */
46
89
  fullWell?: { electrons: number; link?: "pixel-area" | "none"; referencePixelPitchMicrometers?: number };
47
90
  /**
48
- * Read noise, RMS e⁻ (0–100), of the low and the high conversion gain, and the dual-gain switch: the ISO setting from
91
+ * Read noise, RMS e⁻ (0–100), of one physical photosite, as a sensor datasheet gives it (2.22.0: on a tier too; a rendered site that
92
+ * sums N photosites, above the tier's 6 µm or a profile's own pitch, takes it ×√N), of the low and the high conversion gain, and
93
+ * the dual-gain switch: the ISO setting from
49
94
  * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).
50
95
  * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).
51
96
  */
@@ -100,6 +145,13 @@ export interface SimulateCaptureSensorReport {
100
145
  * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for
101
146
  * DNG, JPEG and PNG exports; omitted for a fast request).
102
147
  */
103
- midGrey?: { reflectance: 0.18; channel: "green"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean };
148
+ midGrey?: SimulateCaptureMidGreyFigure;
149
+ /**
150
+ * #595 (2.22.0): the same figures for one of the body's native photosites, whatever pitch the capture renders (the fields above
151
+ * are the as-rendered site's). The native pitch is a generic tier's (or a caller preset's) declared 6 µm reference, or a sensor
152
+ * profile's own photosite; the request's sensor characteristics apply as they would at that pitch. Exact arithmetic under the
153
+ * engine's model (model figures, not measurements); see docs/QUICK_START.md, "Native-pixel dynamic range (#595)".
154
+ */
155
+ native: SimulateCaptureNativeSensorReport;
104
156
  }
105
157
  ```
package/llms.txt CHANGED
@@ -7,6 +7,9 @@
7
7
  - [Agent guide](https://github.com/Photivra/photivra/blob/main/docs/AGENT_GUIDE.md): which function to call, units, reading results, errors and limits
8
8
  - [Quick start](https://github.com/Photivra/photivra/blob/main/docs/QUICK_START.md): `simulateCapture()` inputs, defaults, chart presets and output formats
9
9
  - [Color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md): the built-in color chart patches (`color-patches` preset, `{ kind: "color-patch", patchId }`), their spectra and CIELAB
10
+ - [Image-plane scene](https://github.com/Photivra/photivra/blob/main/docs/IMAGE_PLANE.md): a photo as a flat print in front of the camera (`{ kind: "image-plane" }`), its input, placement, print reflectance lattice and accuracy
11
+ - [Light sources](https://github.com/Photivra/photivra/blob/main/docs/LIGHT_SOURCES.md): representative fluorescent and LED spectra designed by Photivra (`createRepresentativeLightSourceIlluminant`), their CCT and color rendering
12
+ - [Content Credentials](https://github.com/Photivra/photivra/blob/main/docs/CONTENT_CREDENTIALS.md): embed a caller-built C2PA manifest store in a JPEG, PNG or DNG export (`bindC2paManifestStore`), hash the bound bytes (`calculateC2paDataHash`), write the signed store at the same length (`setC2paManifestStore`) and find an existing store (`parseC2paManifestStore`); no keys, signature checks or network
10
13
  - [Usage guide](https://github.com/Photivra/photivra/blob/main/docs/USAGE.md): worked examples for every public area
11
14
  - [API reference](https://github.com/Photivra/photivra/blob/main/docs/API_REFERENCE.md): every root export, with a task-to-function index
12
15