@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
package/docs/USAGE.md CHANGED
@@ -3324,6 +3324,17 @@ void develop;
3324
3324
  value by 2^stops before demosaic and white balance. A site pushed to white clips and counts as saturated; the RAW
3325
3325
  codes are not changed. 0 develops byte for byte as before. The WebGPU accelerator rejects a non-zero push.
3326
3326
  - `extended-reinhard-luminance` takes `{ whiteLuminance }`, the luminance mapped to output white.
3327
+ - `gamutHandling: "desaturate-to-luminance"` (#601, 2.22.0; opt-in, also `simulateCapture`'s `output.gamutHandling`) moves a
3328
+ pixel whose largest component exceeds 1 after the tone curve toward the gray of its own luminance until that component is 1,
3329
+ keeping its luminance and hue; the default `"clip-components"` clips each component. The WebGPU SDR kernels refuse it.
3330
+ - The display view (`simulateDisplayView`, `prepareDisplayView`) renders the luminance tone curves, `highlightMode: "reconstruct"`
3331
+ and the desaturation since 2.22.0 (#593), with fast rules on screen and full ones for `fast: false`
3332
+ ([Display view, Coupled rendering](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#coupled-rendering-593-601)). Photivra's Standard look is
3333
+ `{ exponent: 1.5, semiSaturation: 0.5009, whiteLuminance: 8 }` and its Flat look `{ exponent: 1, semiSaturation: 0.8465, whiteLuminance: 32 }`
3334
+ for `naka-rushton-luminance` (18 % gray stays at 0.18; the engine's own looks, not a camera maker's).
3335
+ - `simulateCapture`'s `output.highlightMode: "reconstruct-neighbors"` (#602, 2.22.0; opt-in, an estimate) rebuilds clipped
3336
+ channels from the color ratios around each clipped region: the ring's mean ratios with `fast` (the sRGB default), their harmonic
3337
+ interpolation inward for exports and `fast: false`. Whole-image, so not with `output.region`; the display view renders it on the CPU.
3327
3338
  - `createPhotographicExportPair(input, { signal })` accepts an optional `AbortSignal`.
3328
3339
 
3329
3340
  ## Capture-mode profiles
@@ -7096,7 +7107,8 @@ See [Local POC Simulation API](https://github.com/Photivra/photivra/blob/main/do
7096
7107
 
7097
7108
  ## DNG import and PNG output
7098
7109
 
7099
- - `parseDngRawImage` reads a DNG or TIFF-EP CFA/LinearRaw file (uncompressed or lossless JPEG, 8–16 bits). It returns the stored raw codes plus the CFA, black/white levels, active area, default crop, colour matrices, as-shot neutral and orientation. Nothing is applied to the codes.
7110
+ - `parseDngRawImage` reads a DNG or TIFF-EP CFA/LinearRaw file (uncompressed, lossless JPEG or, from 2.22.0, 8-bit lossy JPEG; 8–16 bits). It returns the stored raw codes plus the CFA, black/white levels, active area, default crop, colour matrices, as-shot neutral and orientation, and (2.22.0) the opcode lists, the profile gain table map, RGBTables, semantic masks and an `assumptions` list naming every rendering tag it does not apply. Nothing is applied to the codes. `importDngRawImage` (2.22.0) does the same asynchronously and also reads JPEG XL raw data through a decoder the application supplies (`createJxlOxideDngDecoder` over the jxl-oxide-wasm package, which the app installs and loads only when a JPEG XL DNG is opened; the engine does not depend on it); `size: "half"` gives a half-size LinearRaw preview and `maximumBytes` bounds the memory.
7111
+ - `developDngStageImage` (2.22.0) builds the DNG's linear stage image (linearization, black and white levels, OpcodeList1/2/3); `applyDngProfileGainTableMap` and `applyDngRgbTables` apply the phone rendering tags at their places in a development.
7100
7112
  - `serializePngImage` writes an 8- or 16-bit RGB PNG with an sRGB, ICC or cICP (Display P3, BT.2020; sRGB, BT.709 or linear transfer) colour description, and optional mDCV/cLLI display metadata. PQ and HLG are not part of 2.0 (see the patent review in `THIRD_PARTY.md`).
7101
7113
 
7102
7114
  See [DNG import and PNG output](https://github.com/Photivra/photivra/blob/main/docs/IMPORT_AND_PNG.md) for supported subsets, limits and sources.
@@ -7146,6 +7158,7 @@ console.log(png.length, same);
7146
7158
  See [Print planning](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md) for a complete, strict packed example and the exact-ratio integer policy.
7147
7159
 
7148
7160
  - `calculatePrintPlan()` says whether the native sampling is enough, and gives a compatible minimum file raster.
7161
+ - `fit: { kind: "contain", paperOrientation }` (2.22.0) fits the whole image inside paper of another shape and reports the borders ([Contain fit](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md#integer-policy-fit-and-sufficiency)).
7149
7162
  - `calculatePrintSizeLimit()` distinguishes finite, unbounded and unavailable sampling limits.
7150
7163
  - `calculatePrintSizeLimitWithBlurAndNoise()` (2.20.0) gives the largest print at a viewing distance that still looks sharp and clean, counting blur and noise as well as pixels, and names the limiting factor ([Print size limit with blur and noise](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md#print-size-limit-with-blur-and-noise)). An approximation.
7151
7164
  - Print quality and delivery are not assessed.
@@ -7512,6 +7525,45 @@ console.log(calculateGeometricFocusBreathing({
7512
7525
  }).value.deltaDegrees);
7513
7526
  ```
7514
7527
 
7528
+ ## Sensor profiles (#554)
7529
+
7530
+ `SENSOR_PROFILES` lists eight representative sensors (full frame, APS-C, medium format and a large phone sensor; BSI,
7531
+ stacked and front-illuminated; 2010s and 2020s) whose values come from published specifications, values derived from them
7532
+ or labelled design values, with no maker or product named. `simulateCapture({ sensor: { profile: record } })` simulates one
7533
+ (the record is checked strictly; `resolveSensorProfile(id)` finds one by id):
7534
+ it replaces the body's ISO range, readout regimes, readout time and fixed-pattern noise, and the #555 characteristics
7535
+ override it one at a time. `resolveSensorProfile(id)` returns one profile; `SENSOR_PROFILE_ISO_ANCHOR` is the ISO anchor its base ISO is re-rated from. Values, sources and the ISO
7536
+ rating: [SENSOR_PROFILES.md](https://github.com/Photivra/photivra/blob/main/docs/SENSOR_PROFILES.md).
7537
+
7538
+ ```ts
7539
+ import { SENSOR_PROFILES, simulateCapture } from "@photivra/engine";
7540
+
7541
+ for (const p of SENSOR_PROFILES) {
7542
+ // fast: false (full quality, as the calculator asks) computes the mid-gray noise figure.
7543
+ const r = await simulateCapture({ scene: { preset: "uniform" }, fast: false, sensor: { profile: p }, exposure: { mode: "manual", iso: 3200, shutterSeconds: 1 / 4000 } });
7544
+ console.log(p.name, r.sensor.dynamicRangeStops.toFixed(1), r.sensor.midGrey?.snr.toFixed(1));
7545
+ }
7546
+ ```
7547
+
7548
+ ### Read noise and native-pixel figures (2.22.0, #595, #599)
7549
+
7550
+ `sensor.readNoise` is the read noise of one **physical photosite**, as a sensor datasheet gives it, on a generic tier, a caller
7551
+ preset and a sensor profile alike (2.22.0; before, a tier took it as the rendered site's). A rendered site that stands for N
7552
+ photosites (pitch above the tier's 6 µm, or above a profile's own pitch) sums their read noise: it takes the value ×√N. Without
7553
+ `sensor.readNoise` a tier's site takes its 6 µm photosite's read noise ×√N in the same way (#599). Below one photosite the value is
7554
+ kept. `result.sensor` reports the rendered site; `result.sensor.native` one photosite (its `readNoiseElectrons` is the value you
7555
+ gave), with the EMVA 1288 and the 8 MP-normalized dynamic range
7556
+ ([QUICK_START.md, "Native-pixel dynamic range"](QUICK_START.md#sensor-characteristics-555)).
7557
+
7558
+ ```ts
7559
+ import { simulateCapture } from "@photivra/engine";
7560
+
7561
+ const r = await simulateCapture({ scene: { preset: "uniform" }, sensor: { pixelPitchMicrometers: 12, readNoise: { lowGainElectrons: 3 } }, fast: false });
7562
+ r.sensor.native.readNoiseElectrons; // 3, one 6 µm photosite's (the value given)
7563
+ r.sensor.readNoise.lowGainElectrons; // 6, the rendered 12 µm site's: 3 × √4
7564
+ r.sensor.native.emva1288.dynamicRangeStops;
7565
+ ```
7566
+
7515
7567
  ## Generic lens catalogue (#549)
7516
7568
 
7517
7569
  `GENERIC_LENS_CATALOGUE` lists the engine's 30 generic primes (17 professional, 13 consumer, "PV 50 1.2 Pro", "PV 50 1.8"; Photivra's
@@ -7524,7 +7576,10 @@ correction profile for `resolveLensCorrectionPlan`, which `simulateCapture` appl
7524
7576
  `output.lensCorrections` (#570; never to the RAW or the DNG), and which a DNG can carry for RAW editors as optional DNG opcodes with
7525
7577
  `output.dng.lensCorrectionOpcodes` (#587; the RAW codes unchanged). `simulateCapture` renders a catalogue lens with `lens.catalogue`
7526
7578
  ([quick start](QUICK_START.md#generic-lens-catalogue-549)); `fast: true` (or `lens: { catalogue: { kind, fast: true } }`) takes its
7527
- fast method (#568), a labeled approximation that blends the PSF over a field grid, for interactive captures. Equations, tables and the
7579
+ fast method (#568), a labeled approximation that blends the PSF over a field grid, for interactive captures.
7580
+ `calculateLateralColorFringe({ lensId, activeImagingArea, pixelPitchMicrometers, fieldPosition? })` gives the lens's lateral color as a
7581
+ fringe width: the blue-minus-red image displacement in µm and pixels at a field position (default the corner; #580); its `channelDispersion` for
7582
+ the capture's own channels comes from `calculateCameraChannelResponse({ illuminant })` (#591, full quality: a 5 nm grid by default). Equations, tables and the
7528
7583
  chart measurements: [LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md).
7529
7584
 
7530
7585
  ```ts
@@ -7790,6 +7845,13 @@ including the whole-image stages `output.diffractionCorrection` and `output.shar
7790
7845
  bytes as a whole-frame `simulateCapture()` and per-row progress (`diffraction-correction`, `sharpening`). See
7791
7846
  [Quick start, Developing a capture rendered in parts](QUICK_START.md#developing-a-capture-rendered-in-parts-571).
7792
7847
 
7848
+ `simulateExposureBuildUp(options, { fractions })` (#534) renders fractions of one exposure as one accumulating process. Each
7849
+ fraction's expected signal is its own capture at the elapsed time, so camera rotation, a moving subject and mixed lighting are
7850
+ included. Its photo and dark counts are binomial splits of the next fraction's, so they build up in place. Each fraction is
7851
+ clipped at the full well, read out once with its own read noise and developed as its own photo. The fraction at 1 is
7852
+ `simulateCapture(options)` byte for byte. Only the sRGB sample formats are accepted (on screen). See
7853
+ [Quick start, Exposure build-up](QUICK_START.md#exposure-build-up-534).
7854
+
7793
7855
  ### Color charts (2.14)
7794
7856
 
7795
7857
  The engine's own [color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md) are reflectance spectra lit by the capture's illuminant. Draw the
@@ -7810,6 +7872,54 @@ const sky = getColorPatchSet().patches.find((p) => p.patchId === "sky-blue");
7810
7872
  console.log(tungsten.scene.parameters.patchSet, sky?.code, sky?.name, sky?.reference.cielabD65); // "PV-M1", "Midday Sky"
7811
7873
  ```
7812
7874
 
7875
+ ### A photo as a print (2.21)
7876
+
7877
+ An [image-plane scene](https://github.com/Photivra/photivra/blob/main/docs/IMAGE_PLANE.md) (#581) puts an sRGB (8- or 16-bit) or linear image in front of the camera as a flat matte
7878
+ print. Each pixel becomes a smooth reflectance spectrum in [0, 1] that gives back its sRGB under CIE D65. Under other lights the print's colors are an approximation.
7879
+ `chartSize` sets the print's size (its height follows the image). Without it the print is fitted inside the frame. `prepareDisplayView` renders it on WebGPU in the Speed path.
7880
+ `prepareImagePlaneScenes()` loads the scene's module first, so bundles that never print a photo do not carry it.
7881
+
7882
+ ```ts
7883
+ import { prepareImagePlaneScenes, simulateCapture } from "@photivra/engine";
7884
+
7885
+ await prepareImagePlaneScenes(); // loads the scene's module (a lazy chunk) once
7886
+ const width = 2, height = 1, data = Uint8Array.of(255, 255, 255, 30, 60, 200);
7887
+ const print = await simulateCapture({
7888
+ scene: { kind: "image-plane", sceneId: "snapshot", image: { width, height, channels: 3, encoding: "srgb-8", data }, paper: { whiteReflectance: 0.9 } },
7889
+ chartSize: { widthM: 0.1 },
7890
+ subjectDistanceM: 0.4,
7891
+ output: { width: 32, height: 16 }
7892
+ });
7893
+ console.log(print.scene.parameters.encoding, print.assumptions.some((a) => a.id === "scene.imagePlane")); // "srgb-8", true
7894
+ ```
7895
+
7896
+ ### Loading the full-quality code ahead of time (2.22, #594)
7897
+
7898
+ The full-quality paths load on demand: a bundler that splits dynamic imports keeps their code out of the first load, and the
7899
+ first request that needs it fetches it: the CPU full-quality code (diffraction, the capture's AHD demosaic, `lens.defocus`
7900
+ bokeh shapes, cat's eye and large discs, lens projection and distortion, the display view's luminance tone curves,
7901
+ reconstruct and desaturation, `highlightMode: "reconstruct-neighbors"`), the opt-in features (`sensor.profile`,
7902
+ `illumination`, `motion.subject`) and the display view's GPU full-quality code (diffraction, AHD, defocus and image-plane
7903
+ prints with `prepareDisplayView(…, { gpu: "auto" | "require" })`). `prepareQualityPaths()` fetches all of it ahead of time,
7904
+ so that request does not wait. It is optional, changes no result, and is safe to call at any time: concurrent calls share one
7905
+ load, and later calls load nothing. If the code does not load, the promise rejects with `ImageKernelExecutionError` reason
7906
+ `quality-code-unavailable` and nothing is remembered; a GPU render in the meantime runs the CPU reference (the same full-quality
7907
+ result, slower) with `diagnostics.fast.execution.fallbackReason: "quality-code-unavailable"`, a CPU request whose code does not
7908
+ load fails with that error, and the next request tries again.
7909
+
7910
+ ```ts
7911
+ import { prepareQualityPaths } from "@photivra/engine";
7912
+
7913
+ // For example when the user opens Export, where full-quality renders follow.
7914
+ await prepareQualityPaths().catch(() => undefined); // a failed preload is retried by the request that needs the code
7915
+ ```
7916
+
7917
+ **Representative light sources (2.22).** The engine's own [representative light sources](https://github.com/Photivra/photivra/blob/main/docs/LIGHT_SOURCES.md) are four relative spectra
7918
+ designed by Photivra: a tri-phosphor fluorescent tube and white LEDs of low and high color rendering at 4000 K and a warm one at
7919
+ 2700 K, each "representative, not a named product". `createRepresentativeLightSourceIlluminant({ sourceId })` returns an
7920
+ `illuminant` (`{ kind: "spectrum", spectrum, label }`); under the two 4000 K LEDs the color patches differ by up to ΔE00 5.9 while
7921
+ the neutrals stay neutral. Use `sensor.spectralStepNanometers: 5`. The page has a runnable example.
7922
+
7813
7923
  In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relations S = 10/H and H = 0.65·L·t/N²) through
7814
7924
  `resolveAperturePriorityExposureMode()`.
7815
7925
  `exposure.standardExposureDeviationStops` is the exposure used minus the standard exposure for the metered luminance L, in stops,
@@ -7866,6 +7976,28 @@ The chart presets are also available on their own:
7866
7976
  - `createPlanarSceneRadianceProvider()` turns a chart into an `evaluateRadiance` callback for the native
7867
7977
  environment RAW contract.
7868
7978
 
7979
+ ### Mixed lighting (2.22, #550)
7980
+
7981
+ `illumination` lights the chart with two or more sources blended across it (a linear gradient, a radial falloff or regions),
7982
+ for example window daylight on one side and a lamp on the other. The frame center's light sets the grey-card meter and its
7983
+ strongest source the `"scene-illuminant"` white balance; both are reported in `assumptions`. See
7984
+ [Mixed lighting](https://github.com/Photivra/photivra/blob/main/docs/MIXED_ILLUMINATION.md) for the model and every field.
7985
+
7986
+ ```ts
7987
+ import { simulateCapture } from "@photivra/engine";
7988
+
7989
+ const lampPool = await simulateCapture({
7990
+ scene: { preset: "step-wedge" },
7991
+ exposure: { mode: "aperture-priority", metering: "grey-card" },
7992
+ illumination: {
7993
+ sources: [{ illuminant: "cie-a", illuminanceLux: 500 }, { illuminant: { kind: "planckian", temperatureKelvin: 6500 }, illuminanceLux: 20000 }],
7994
+ blend: { kind: "radial", radius: 0.8, stops: [0.25, 1] }
7995
+ },
7996
+ output: { width: 32, height: 16 }
7997
+ });
7998
+ console.log(lampPool.assumptions.find((a) => a.id === "output.whiteBalanceSource")?.value); // { source: 0, illuminant: "cie-a" }
7999
+ ```
8000
+
7869
8001
  ### Display view of a capture
7870
8002
 
7871
8003
  `simulateDisplayView(options, view)` takes a capture request and a display size and returns the real
@@ -7930,14 +8062,13 @@ it: Vite builds workers as `iife` by default, so a worker that calls `prepareDis
7930
8062
  modules (`worker: { format: "es" }`, with `new Worker(url, { type: "module" })`) to keep it out. Measured sizes:
7931
8063
  [Bundle size](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#bundle-size).
7932
8064
 
7933
- #### Sharing AHD correction states between workers
8065
+ #### AHD correction states (deprecated in 2.22.0)
7934
8066
 
7935
- With `output.demosaic: "ahd"`, low-signal display pixels read correction states that each realm (a page, a Web Worker)
7936
- computes and keeps for itself. Two workers can split a part's states and exchange them instead of both computing all
7937
- of them; the renders have the same bytes either way (see "AHD at low signal" in
7938
- [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#ahd-at-low-signal)). Import only
7939
- states whose key a prepared view reported. The two handles below run in one script for the example; in an app each
7940
- runs in its own worker, and the arrays travel with `postMessage(set, [set.gridKeys.buffer, set.values.buffer])`.
8067
+ From 2.10 to 2.21, low-signal AHD display pixels read correction states that workers could exchange. 2.22.0 removes the
8068
+ correction (a labelled approximation remains; see "AHD at low signal" in
8069
+ [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#ahd-at-low-signal)), so there is nothing
8070
+ to exchange: workers render AHD views with no shared state. The exchange functions stay until 3.0.0 as checked no-ops;
8071
+ remove the calls.
7941
8072
 
7942
8073
  ```ts
7943
8074
  import { serializeDisplayViewCorrectionStates, bindDisplayViewCorrectionStates, prepareDisplayView, type SimulateCaptureOptions } from "@photivra/engine";
@@ -7945,19 +8076,13 @@ import { serializeDisplayViewCorrectionStates, bindDisplayViewCorrectionStates,
7945
8076
  const request: SimulateCaptureOptions = { scene: { preset: "uniform", reflectance: 0.18 }, exposure: { iso: 1600, shutterSeconds: 1 / 2000 },
7946
8077
  sensor: { pixelPitchMicrometers: 1.4 }, output: { width: 200, height: 150, demosaic: "ahd" } };
7947
8078
  const part = { width: 20, height: 15, region: { x: 0, y: 0, width: 10, height: 8 } };
7948
- // Each worker prepares the same request; the key says which states it reads.
7949
- const workerA = await prepareDisplayView(request), workerB = await prepareDisplayView(request);
7950
- console.log(workerA.correctionKey() === workerB.correctionKey());
7951
- // Worker A finds the states the part needs (no image, no state computed) and gives half to each worker.
7952
- const { gridKeys } = await workerA.collectCorrectionStates(part);
7953
- const half = Math.ceil(gridKeys.length / 2);
7954
- const fromA = await workerA.computeCorrectionStates(gridKeys.slice(0, half));
7955
- const fromB = await workerB.computeCorrectionStates(gridKeys.slice(half));
7956
- // Each worker imports the other's share (in one realm, as here, the second import adds nothing new).
7957
- console.log(bindDisplayViewCorrectionStates([fromB]), bindDisplayViewCorrectionStates([fromA]));
7958
- // Either worker now renders the part without computing a state; a new worker can start from an export.
7959
- const view = await workerA.render(part);
7960
- console.log(view.diagnostics.ahdCorrectedPixelCount, serializeDisplayViewCorrectionStates().map((set) => set.gridKeys.length));
8079
+ const prepared = await prepareDisplayView(request);
8080
+ // Deprecated: null, no grid keys, no states, nothing stored.
8081
+ console.log(prepared.correctionKey(), (await prepared.collectCorrectionStates(part)).gridKeys.length);
8082
+ console.log(serializeDisplayViewCorrectionStates().length, bindDisplayViewCorrectionStates([]));
8083
+ // The view itself needs none of them.
8084
+ const view = await prepared.render(part);
8085
+ console.log(view.diagnostics.ahdCorrectedPixelCount); // always 0 since 2.22.0
7961
8086
  ```
7962
8087
 
7963
8088
  ### Facade 2.0 options: output, demosaic, optics and motion
@@ -8051,6 +8176,18 @@ const rolling = await simulateCapture({ scene: { preset: "checkerboard" }, motio
8051
8176
  console.log(rolling.request.siteInput(7 * 8).charge.completenessProfile.startOffsetSecondsFromOpeningReference);
8052
8177
  ```
8053
8178
 
8179
+ `motion.subject` (2.22.0) moves the subject instead: the whole scene, or one `region` of it over a still background,
8180
+ at `velocityMPerSec` on the scene plane, optionally rotating at `angularVelocityRadPerSec` about the optical axis. It
8181
+ uses the same temporal midpoints, so a rolling shutter skews the moving subject. The display view renders sideways
8182
+ motion under a global shutter and refuses the rest. See [Subject motion](QUICK_START.md#subject-motion).
8183
+
8184
+ ```ts
8185
+ import { simulateCapture } from "@photivra/engine";
8186
+
8187
+ const moving = await simulateCapture({ scene: { preset: "slanted-edge" }, motion: { subject: { velocityMPerSec: { x: 0.5 } } }, output: { width: 8, height: 8 } });
8188
+ console.log(moving.assumptions.find((a) => a.id === "motion.subject")?.value);
8189
+ ```
8190
+
8054
8191
  ## Equivalent f-number, sensor area and blur as a share of the frame
8055
8192
 
8056
8193
  `calculateSensorEquivalence()` says what a lens on one sensor corresponds to on a 36 × 24 mm frame (or on a `reference` you pass). Call it once per sensor to fill a lens table:
@@ -8148,3 +8285,39 @@ console.log(outOfFocus.limitingFactor); // "defocus"
8148
8285
  - Demosaicing, sharpening, noise and motion are not included.
8149
8286
 
8150
8287
  See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#which-limits-detail-the-lens-or-the-sensor).
8288
+
8289
+ ## Content Credentials in exports (#165)
8290
+
8291
+ `bindC2paManifestStore`, `calculateC2paDataHash` and `setC2paManifestStore` embed a C2PA Manifest Store in a JPEG, PNG or
8292
+ DNG export, hash the bytes the signature covers and write the signed store in place at the same length (C2PA 2.4 §10.4);
8293
+ `parseC2paManifestStore` finds a store already in a file, without verifying it. The application builds, signs and validates
8294
+ the manifest; the engine holds no keys and makes no network calls. Here a minimal one-IFD TIFF
8295
+ stands in for a DNG export, and the stores are only the C2PA framing around zero or one bytes, not real manifests:
8296
+
8297
+ ```ts
8298
+ import { bindC2paManifestStore, calculateC2paDataHash, parseC2paManifestStore, setC2paManifestStore } from '@photivra/engine';
8299
+
8300
+ // A C2PA Manifest Store's outer framing (C2PA 2.4 §11.1.4.2) around `fill` bytes. A real store comes from the app's manifest builder.
8301
+ function framedStore(length: number, fill: number): Uint8Array {
8302
+ const store = new Uint8Array(length).fill(fill), view = new DataView(store.buffer);
8303
+ view.setUint32(0, length); store.set([0x6a, 0x75, 0x6d, 0x62], 4); // 'jumb' superbox
8304
+ view.setUint32(8, 30); store.set([0x6a, 0x75, 0x6d, 0x64], 12); // 'jumd' description box
8305
+ store.set([0x63, 0x32, 0x70, 0x61, 0, 0x11, 0, 0x10, 0x80, 0, 0, 0xaa, 0, 0x38, 0x9b, 0x71, 3, 0x63, 0x32, 0x70, 0x61, 0], 16); // C2PA UUID, label "c2pa"
8306
+ view.setUint32(38, length - 38); store.set([0x6a, 0x75, 0x6d, 0x62], 42); // one child box filling the rest
8307
+ return store;
8308
+ }
8309
+ const tiff = Uint8Array.of(0x49, 0x49, 42, 0, 8, 0, 0, 0, 1, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0);
8310
+
8311
+ const bound = bindC2paManifestStore({ file: tiff, manifestStore: framedStore(1024, 0) });
8312
+ console.log(bound.binding.exclusions); // [{ start: 26, length: 4 }, { start: 40, length: 1024 }]: the entry's count field and the store
8313
+ const { hashHex } = await calculateC2paDataHash({ file: bound.bytes, exclusions: bound.binding.exclusions });
8314
+ // The app writes the exclusions and hashHex into its c2pa.hash.data assertion, signs, and builds the final store at 1,024 bytes.
8315
+ const signed = setC2paManifestStore({ file: bound.bytes, binding: bound.binding, manifestStore: framedStore(1024, 1) });
8316
+ const after = await calculateC2paDataHash({ file: signed, exclusions: bound.binding.exclusions });
8317
+ if (signed.length !== bound.bytes.length || after.hashHex !== hashHex) throw new Error('The signed file must keep its length and data hash');
8318
+ const found = parseC2paManifestStore({ file: signed }); // the store and the same binding, or null for a file without one
8319
+ if (found?.manifestStore[46] !== 1) throw new Error('The signed store must be found');
8320
+ ```
8321
+
8322
+ See [Content Credentials](https://github.com/Photivra/photivra/blob/main/docs/CONTENT_CREDENTIALS.md) for the byte layouts,
8323
+ the checks and what is still to be validated externally.
@@ -0,0 +1,177 @@
1
+ # capture/c2pa-embed.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
+ ## bindC2paManifestStore
6
+
7
+ Embeds a placeholder C2PA Manifest Store in a JPEG, PNG or TIFF/DNG export and returns the new file with its hard binding:
8
+ the exclusions for `c2pa.hash.data` and where the store sits (C2PA 2.4 §A.3.1, §A.3.2, §A.3.6, §18.5, §10.4; see the module
9
+ notes). The input file is not changed. Only the first store of a file can be added; a file that already has one is rejected.
10
+
11
+ ```ts
12
+ export function bindC2paManifestStore(input: BindC2paManifestStoreInput): C2paBoundFile;
13
+ ```
14
+
15
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
16
+
17
+ ## BindC2paManifestStoreInput
18
+
19
+ Input of `bindC2paManifestStore`.
20
+
21
+ ```ts
22
+ export interface BindC2paManifestStoreInput {
23
+ /** A JPEG, a PNG, or a TIFF-based file such as a DNG, as written by the engine's exporters. It is read, never changed. */
24
+ file: Uint8Array;
25
+ /**
26
+ * The placeholder store (§10.4): a JUMBF C2PA Manifest Store sized for the final signature. For JPEG it may instead be the
27
+ * same store already split into APP11 segments (what the C2PA reference SDK's placeholder call returns), as long as that
28
+ * split is the one described above.
29
+ */
30
+ manifestStore: Uint8Array;
31
+ }
32
+ ```
33
+
34
+ ## C2PA_ALGORITHMIC_MEDIA_SOURCE_TYPE
35
+
36
+ IPTC digital source type for media created purely by an algorithm (https://cv.iptc.org/newscodes/digitalsourcetype/).
37
+
38
+ ```ts
39
+ C2PA_ALGORITHMIC_MEDIA_SOURCE_TYPE = "http://cv.iptc.org/newscodes/digitalsourcetype/algorithmicMedia" as const
40
+ ```
41
+
42
+ ## C2PA_MANIFEST_STORE_MAXIMUM_BYTES
43
+
44
+ Largest manifest store the engine embeds or reads, bytes (an engine limit, not a C2PA one).
45
+
46
+ ```ts
47
+ C2PA_MANIFEST_STORE_MAXIMUM_BYTES = 33_554_432
48
+ ```
49
+
50
+ ## C2paBoundFile
51
+
52
+ A file with a manifest store embedded, and its binding.
53
+
54
+ ```ts
55
+ export interface C2paBoundFile { bytes: Uint8Array; binding: C2paHardBinding }
56
+ ```
57
+
58
+ ## C2paByteRange
59
+
60
+ A byte range of a file: `start` is a zero-based offset, `length` a byte count.
61
+
62
+ ```ts
63
+ export interface C2paByteRange { start: number; length: number }
64
+ ```
65
+
66
+ ## C2paDataHash
67
+
68
+ SHA-256 over every byte of the file outside the exclusions, in file order (§18.5): the `hash` of `c2pa.hash.data`.
69
+
70
+ ```ts
71
+ export interface C2paDataHash { alg: "sha256"; hash: Uint8Array; hashHex: string; hashedByteCount: number }
72
+ ```
73
+
74
+ ## C2paFoundManifestStore
75
+
76
+ A manifest store found in a file (`parseC2paManifestStore`): its binding and a copy of the store's JUMBF bytes.
77
+
78
+ ```ts
79
+ export interface C2paFoundManifestStore { binding: C2paHardBinding; manifestStore: Uint8Array }
80
+ ```
81
+
82
+ ## C2paHardBinding
83
+
84
+ Where the manifest store sits in a file and which bytes the data hash excludes. Plain data: it can be kept, sent to a
85
+ worker or serialized, and must be passed back unchanged to `setC2paManifestStore`.
86
+
87
+ ```ts
88
+ export interface C2paHardBinding {
89
+ /** `image/jpeg`, `image/png`, or for TIFF files `image/dng` when the main IFD has DNGVersion (50706), otherwise `image/tiff`. */
90
+ mediaType: "image/jpeg" | "image/png" | "image/dng" | "image/tiff";
91
+ /** Length of the file with the store embedded, bytes. */
92
+ fileLength: number;
93
+ /** The `exclusions` of the `c2pa.hash.data` assertion, ascending and non-overlapping (§18.5). */
94
+ exclusions: readonly C2paByteRange[];
95
+ /** JPEG: every C2PA APP11 segment, markers and length fields included. PNG: the whole caBX chunk. TIFF/DNG: the store's own bytes. */
96
+ manifestRegion: C2paByteRange;
97
+ /** Length of the JUMBF manifest store itself, bytes. A replacement store must have exactly this length. */
98
+ manifestStoreLength: number;
99
+ }
100
+ ```
101
+
102
+ ## calculateC2paDataHash
103
+
104
+ SHA-256 over every byte of a file outside the given exclusions, in file order: the value of `hash` in a `c2pa.hash.data`
105
+ assertion with `alg` "sha256" (C2PA 2.4 §18.5). It uses Web Crypto (`crypto.subtle`, browsers in secure contexts and
106
+ Node 22+). The bytes outside the exclusions are copied once into one array before hashing.
107
+
108
+ ```ts
109
+ export async function calculateC2paDataHash(input: CalculateC2paDataHashInput): Promise<C2paDataHash>;
110
+ ```
111
+
112
+ This call is asynchronous; byte hashing uses the caller runtime's Web Crypto.
113
+
114
+ ## CalculateC2paDataHashInput
115
+
116
+ Input of `calculateC2paDataHash`.
117
+
118
+ ```ts
119
+ export interface CalculateC2paDataHashInput {
120
+ /** The bound file. */
121
+ file: Uint8Array;
122
+ /** The binding's exclusions (or any ascending, non-overlapping ranges inside the file). */
123
+ exclusions: readonly C2paByteRange[];
124
+ }
125
+ ```
126
+
127
+ ## parseC2paManifestStore
128
+
129
+ Finds the C2PA Manifest Store embedded in a JPEG (APP11), PNG (caBX) or TIFF/DNG (tag 52545) and returns its location as a
130
+ binding (the exclusions the engine's own writer uses for that format) and a copy of the store. It checks the file's structure
131
+ and the store's outer framing only: it does not read the manifests, check the data hash or verify any signature. The
132
+ manifest's own `c2pa.hash.data` assertion is what a validator uses; this binding is what `setC2paManifestStore` needs.
133
+
134
+ ```ts
135
+ export function parseC2paManifestStore(input: ParseC2paManifestStoreInput): C2paFoundManifestStore | null;
136
+ ```
137
+
138
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
139
+
140
+ ## ParseC2paManifestStoreInput
141
+
142
+ Input of `parseC2paManifestStore`.
143
+
144
+ ```ts
145
+ export interface ParseC2paManifestStoreInput {
146
+ /** A JPEG, PNG or TIFF-based file such as a DNG. It is read, never changed. */
147
+ file: Uint8Array;
148
+ }
149
+ ```
150
+
151
+ ## setC2paManifestStore
152
+
153
+ Writes the final, signed manifest store over the placeholder of a bound file (C2PA 2.4 §10.4.4). The store must have
154
+ exactly the placeholder's length, so the file's length, every offset and every byte outside the exclusions stay the same
155
+ and the data hash computed before signing still holds. The binding is checked against the file first. The new store goes
156
+ into the file's existing segments or chunk as they are (for PNG the chunk CRC is recomputed; it is inside the exclusion).
157
+
158
+ ```ts
159
+ export function setC2paManifestStore(input: SetC2paManifestStoreInput): Uint8Array;
160
+ ```
161
+
162
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
163
+
164
+ ## SetC2paManifestStoreInput
165
+
166
+ Input of `setC2paManifestStore`.
167
+
168
+ ```ts
169
+ export interface SetC2paManifestStoreInput {
170
+ /** The bound file (`C2paBoundFile.bytes`). It is read, never changed. */
171
+ file: Uint8Array;
172
+ /** The binding `bindC2paManifestStore` (or `parseC2paManifestStore`) returned for it. */
173
+ binding: C2paHardBinding;
174
+ /** The final, signed store: exactly `binding.manifestStoreLength` bytes, as JUMBF or (JPEG) in the same APP11 split. */
175
+ manifestStore: Uint8Array;
176
+ }
177
+ ```
@@ -4,18 +4,31 @@ The package and root API versions are recorded once in [Navigation](../API_REFER
4
4
 
5
5
  ## DNG_IMPORT_LIMITS
6
6
 
7
- Upper bound for `maximumPixels`. 200 MP (megapixels) of 16-bit codes is 400 MB.
7
+ Upper bound for `maximumPixels`: 200 MP (megapixels) of 16-bit codes is 400 MB. `maximumBytes` bounds the estimated peak memory
8
+ of the import (default 1 GiB, at most 4 GiB).
8
9
 
9
10
  ```ts
10
- DNG_IMPORT_LIMITS = Object.freeze({ defaultMaximumPixels: 64_000_000, maximumPixels: 200_000_000, defaultMaximumIfds: 64, maximumIfds: 1024 })
11
+ DNG_IMPORT_LIMITS = Object.freeze({ defaultMaximumPixels: 64_000_000, maximumPixels: 200_000_000, defaultMaximumIfds: 64, maximumIfds: 1024,
12
+ defaultMaximumBytes: 1_073_741_824, maximumBytes: 4_294_967_296 })
11
13
  ```
12
14
 
13
15
  ## DNG_IMPORT_SCHEMA_VERSION
14
16
 
15
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
17
+ 1.1.0 (2.22.0): lossy JPEG and JPEG XL raw data, opcode lists, profile maps, semantic masks, `scale`, `assumptions`.
16
18
 
17
19
  ```ts
18
- DNG_IMPORT_SCHEMA_VERSION = "1.0.0" as const
20
+ DNG_IMPORT_SCHEMA_VERSION = "1.1.0" as const
21
+ ```
22
+
23
+ ## DngImportAsyncInput
24
+
25
+ {@link importDngRawImage}'s input: {@link DngImportInput} plus the JPEG XL decoder a 52546 file needs.
26
+
27
+ ```ts
28
+ export interface DngImportAsyncInput extends DngImportInput {
29
+ /** Decodes one JPEG XL strip or tile to PNG (`createJxlOxideDngDecoder`). Needed only for JPEG XL raw data or masks. */
30
+ jpegXlDecoder?: DngJpegXlDecoder;
31
+ }
19
32
  ```
20
33
 
21
34
  ## DngImportInput
@@ -29,9 +42,31 @@ export interface DngImportInput {
29
42
  maximumPixels?: number;
30
43
  /** Total IFDs (main chain plus SubIFDs) visited. Default 64, at most 1024. */
31
44
  maximumIfds?: number;
45
+ /**
46
+ * Estimated peak memory of the import, bytes (the codes, the largest strip or tile's decode buffers and its decoder's working
47
+ * memory; see docs/IMPORT_AND_PNG.md, "Memory"), checked before any image allocation. Default 1 073 741 824 (1 GiB), at most 4 GiB.
48
+ */
49
+ maximumBytes?: number;
50
+ /**
51
+ * "half": a half-size preview of a LinearRaw image, each output pixel the rounded mean of a 2 × 2 block of stored codes, decoded
52
+ * strip by strip (a quarter of the codes' memory). Areas, crops and black-level deltas are halved; CFA images are refused.
53
+ * Default "full".
54
+ */
55
+ size?: "full" | "half";
32
56
  }
33
57
  ```
34
58
 
59
+ ## importDngRawImage
60
+
61
+ {@link parseDngRawImage} for every supported compression, JPEG XL included: each JPEG XL strip or tile goes to `jpegXlDecoder` in
62
+ turn (one at a time, so only one tile's decoder memory is held), and its PNG is read at the DNG's bit depth (jpeg-xl-tiles.ts).
63
+
64
+ ```ts
65
+ export async function importDngRawImage(input: DngImportAsyncInput): Promise<ImportedDngRaw>;
66
+ ```
67
+
68
+ This call is asynchronous; byte hashing uses the caller runtime's Web Crypto.
69
+
35
70
  ## ImportedDngBlackLevel
36
71
 
37
72
  Black level (the code for zero light) pattern, from BlackLevel, BlackLevelRepeatDim and BlackLevelDeltaH/V. Origin is the active-area top-left.
@@ -83,7 +118,14 @@ export interface ImportedDngRaw {
83
118
  height: number;
84
119
  samplesPerPixel: number;
85
120
  bitsPerSample: number;
86
- compression: "uncompressed" | "lossless-jpeg";
121
+ compression: "uncompressed" | "lossless-jpeg" | "lossy-jpeg" | "jpeg-xl";
122
+ /**
123
+ * True when the raw data went through a lossy coder: lossy JPEG always; JPEG XL when JXLDistance (52553) is above 0 or absent
124
+ * (the file then does not say it is lossless).
125
+ */
126
+ lossySource: boolean;
127
+ /** 1, or 0.5 for a `size: "half"` preview (every geometry field is already halved). */
128
+ scale: 1 | 0.5;
87
129
  photometricInterpretation: "cfa" | "linear-raw";
88
130
  /** Stored raw codes, row-major then sample, length width x height x samplesPerPixel. */
89
131
  codes: Uint16Array;
@@ -107,6 +149,23 @@ export interface ImportedDngRaw {
107
149
  asShotNeutral: number[] | null;
108
150
  /** RawDataUniqueID as 32 lowercase hex digits, or null. */
109
151
  rawDataUniqueId: string | null;
152
+ /** ForwardMatrix1/2 (white-balanced camera → XYZ D50), row-major 3 x colorPlanes, or null. */
153
+ forwardMatrix1: number[] | null;
154
+ forwardMatrix2: number[] | null;
155
+ /** BaselineExposure plus BaselineExposureOffset (stops), or null when neither is present. */
156
+ baselineExposure: number | null;
157
+ /** OpcodeList1/2/3 (raw IFD), parsed but not applied (`developDngStageImage` applies them), or null when absent. */
158
+ opcodeList1: DngOpcode[] | null;
159
+ opcodeList2: DngOpcode[] | null;
160
+ opcodeList3: DngOpcode[] | null;
161
+ /** ProfileGainTableMap2 (IFD 0), else ProfileGainTableMap (raw IFD), or null; applied by `applyDngProfileGainTableMap`. */
162
+ profileGainTableMap: DngProfileGainTableMap | null;
163
+ /** RGBTables (IFD 0), or null; applied by `applyDngRgbTables`. */
164
+ rgbTables: DngRgbTables | null;
165
+ /** Decoded semantic masks, for `applyDngRgbTables`. */
166
+ semanticMasks: ImportedDngSemanticMask[];
167
+ /** What the import did not do: tags present but not returned or applied, masks not decoded, lossy sources, inferred layouts. */
168
+ assumptions: string[];
110
169
  }
111
170
  ```
112
171
 
@@ -118,14 +177,31 @@ Half-open raster rectangle in stored-image pixel coordinates (+X right, +Y down)
118
177
  export interface ImportedDngRect { x: number; y: number; width: number; height: number }
119
178
  ```
120
179
 
180
+ ## ImportedDngSemanticMask
181
+
182
+ A decoded semantic mask (DNG 1.6 SemanticName, NewSubFileType 0x10004, PhotometricInterpretation 52527).
183
+
184
+ ```ts
185
+ export interface ImportedDngSemanticMask {
186
+ name: string;
187
+ /** SemanticInstanceID, or null. */
188
+ instanceId: string | null;
189
+ width: number;
190
+ height: number;
191
+ /** Mask values 0..1 (code / (2^bits − 1)), row-major. */
192
+ data: Float32Array;
193
+ }
194
+ ```
195
+
121
196
  ## parseDngRawImage
122
197
 
123
- Parse one DNG or TIFF-EP file and decode its primary CFA or LinearRaw image (the first IFD with
124
- NewSubFileType 0 and PhotometricInterpretation 32803 or 34892, in IFD-chain then SubIFD order).
198
+ Parse one DNG or TIFF-EP file and decode its primary CFA or LinearRaw image (the first IFD with NewSubFileType 0 and
199
+ PhotometricInterpretation 32803 or 34892, in IFD-chain then SubIFD order), synchronously.
125
200
 
126
- Supported raw data: Compression 1 at 8..16 bits/sample (8 bits as bytes, 16 bits in the file byte
127
- order, other depths packed MSB-first with byte-aligned rows, per DNG BitsPerSample) and Compression 7
128
- lossless Huffman JPEG (T.81 Annex H) in strips or tiles, chunky planar configuration only.
201
+ Supported raw data: Compression 1 at 8..16 bits/sample (8 bits as bytes, 16 bits in the file byte order, other depths packed
202
+ MSB-first with byte-aligned rows, per DNG BitsPerSample), Compression 7 lossless Huffman JPEG (T.81 Annex H) and Compression 34892
203
+ 8-bit lossy JPEG (T.81 sequential DCT), in strips or tiles, chunky planar configuration only. JPEG XL (52546) needs
204
+ {@link importDngRawImage}; a JPEG XL semantic mask is skipped here and named in `assumptions`.
129
205
 
130
206
  ```ts
131
207
  export function parseDngRawImage(input: DngImportInput): ImportedDngRaw;