@photivra/engine 2.21.0 → 2.23.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 (172) hide show
  1. package/CHANGELOG.md +150 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +11 -0
  6. package/dist/capture/c2pa-embed.d.ts +116 -0
  7. package/dist/capture/c2pa-embed.js +493 -0
  8. package/dist/capture/c2pa-embed.js.map +1 -0
  9. package/dist/capture/dng-import.d.ts +73 -8
  10. package/dist/capture/dng-import.js +315 -80
  11. package/dist/capture/dng-import.js.map +1 -1
  12. package/dist/capture/dng-opcodes.d.ts +96 -0
  13. package/dist/capture/dng-opcodes.js +391 -0
  14. package/dist/capture/dng-opcodes.js.map +1 -0
  15. package/dist/capture/dng-profile-maps.d.ts +99 -0
  16. package/dist/capture/dng-profile-maps.js +381 -0
  17. package/dist/capture/dng-profile-maps.js.map +1 -0
  18. package/dist/capture/dng-stage-image.d.ts +23 -0
  19. package/dist/capture/dng-stage-image.js +65 -0
  20. package/dist/capture/dng-stage-image.js.map +1 -0
  21. package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
  22. package/dist/capture/jpeg-xl-tiles.js +151 -0
  23. package/dist/capture/jpeg-xl-tiles.js.map +1 -0
  24. package/dist/capture/lossy-jpeg.d.ts +17 -0
  25. package/dist/capture/lossy-jpeg.js +369 -0
  26. package/dist/capture/lossy-jpeg.js.map +1 -0
  27. package/dist/capture/native-raw-development.d.ts +2 -1
  28. package/dist/capture/native-raw-development.js.map +1 -1
  29. package/dist/color/neighbor-highlight-reconstruction.d.ts +76 -0
  30. package/dist/color/neighbor-highlight-reconstruction.js +257 -0
  31. package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
  32. package/dist/color/sensor-color-development.d.ts +5 -0
  33. package/dist/color/sensor-color-development.js +7 -1
  34. package/dist/color/sensor-color-development.js.map +1 -1
  35. package/dist/color/spectral-spikiness.d.ts +11 -0
  36. package/dist/color/spectral-spikiness.js +23 -0
  37. package/dist/color/spectral-spikiness.js.map +1 -0
  38. package/dist/core/execution-error.d.ts +21 -0
  39. package/dist/core/execution-error.js +12 -0
  40. package/dist/core/execution-error.js.map +1 -0
  41. package/dist/core/version.d.ts +1 -1
  42. package/dist/core/version.js +1 -1
  43. package/dist/core/version.js.map +1 -1
  44. package/dist/facade/catalogue-lens-signal.d.ts +26 -1
  45. package/dist/facade/catalogue-lens-signal.js +25 -7
  46. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  47. package/dist/facade/diffraction-provider.d.ts +1 -1
  48. package/dist/facade/diffraction-provider.js +4 -4
  49. package/dist/facade/diffraction-provider.js.map +1 -1
  50. package/dist/facade/display-view-gpu-session.js +38 -6
  51. package/dist/facade/display-view-gpu-session.js.map +1 -1
  52. package/dist/facade/display-view-gpu.d.ts +13 -5
  53. package/dist/facade/display-view-gpu.js.map +1 -1
  54. package/dist/facade/display-view-normal.d.ts +15 -0
  55. package/dist/facade/display-view-normal.js +44 -0
  56. package/dist/facade/display-view-normal.js.map +1 -0
  57. package/dist/facade/display-view-rendering.d.ts +102 -0
  58. package/dist/facade/display-view-rendering.js +633 -0
  59. package/dist/facade/display-view-rendering.js.map +1 -0
  60. package/dist/facade/display-view.d.ts +37 -65
  61. package/dist/facade/display-view.js +147 -476
  62. package/dist/facade/display-view.js.map +1 -1
  63. package/dist/facade/expected-signal-setup.js +2 -2
  64. package/dist/facade/expected-signal-setup.js.map +1 -1
  65. package/dist/facade/fast-methods.d.ts +1 -1
  66. package/dist/facade/fast-methods.js +37 -8
  67. package/dist/facade/fast-methods.js.map +1 -1
  68. package/dist/facade/generic-lens-capture.d.ts +1 -1
  69. package/dist/facade/generic-lens-capture.js +1 -1
  70. package/dist/facade/generic-lens-capture.js.map +1 -1
  71. package/dist/facade/image-plane.js +41 -10
  72. package/dist/facade/image-plane.js.map +1 -1
  73. package/dist/facade/on-demand-kits.d.ts +14 -0
  74. package/dist/facade/on-demand-kits.js +36 -0
  75. package/dist/facade/on-demand-kits.js.map +1 -0
  76. package/dist/facade/opt-in-kit.d.ts +9 -0
  77. package/dist/facade/opt-in-kit.js +11 -0
  78. package/dist/facade/opt-in-kit.js.map +1 -0
  79. package/dist/facade/quality-kit.d.ts +21 -0
  80. package/dist/facade/quality-kit.js +23 -0
  81. package/dist/facade/quality-kit.js.map +1 -0
  82. package/dist/facade/quality-paths.d.ts +22 -0
  83. package/dist/facade/quality-paths.js +44 -0
  84. package/dist/facade/quality-paths.js.map +1 -0
  85. package/dist/facade/scene-radiance.d.ts +18 -2
  86. package/dist/facade/scene-radiance.js +6 -4
  87. package/dist/facade/scene-radiance.js.map +1 -1
  88. package/dist/facade/simulate-capture.d.ts +32 -13
  89. package/dist/facade/simulate-capture.js +162 -69
  90. package/dist/facade/simulate-capture.js.map +1 -1
  91. package/dist/facade/simulate-display-view.d.ts +10 -12
  92. package/dist/facade/simulate-display-view.js +57 -48
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
  95. package/dist/gpu/display-view-gpu-renderer.js +134 -0
  96. package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
  97. package/dist/gpu/display-view-image-material-module.d.ts +3 -2
  98. package/dist/gpu/display-view-image-material-module.js +25 -3
  99. package/dist/gpu/display-view-image-material-module.js.map +1 -1
  100. package/dist/gpu/display-view-image-quality.d.ts +43 -1
  101. package/dist/gpu/display-view-image-quality.js +256 -11
  102. package/dist/gpu/display-view-image-quality.js.map +1 -1
  103. package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
  104. package/dist/gpu/display-view-quality-gpu.js +55 -240
  105. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  106. package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
  107. package/dist/gpu/display-view-speed-gpu.js +62 -19
  108. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  109. package/dist/gpu/image-kernel-accelerator.js +3 -0
  110. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  111. package/dist/gpu/image-kernel-execution.d.ts +2 -13
  112. package/dist/gpu/image-kernel-execution.js +2 -10
  113. package/dist/gpu/image-kernel-execution.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +13 -0
  115. package/dist/gpu/wgsl-display-view-coupled-stub.js +29 -0
  116. package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
  117. package/dist/gpu/wgsl-display-view-coupled.d.ts +91 -0
  118. package/dist/gpu/wgsl-display-view-coupled.js +365 -0
  119. package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
  120. package/dist/gpu/wgsl-display-view-neighbors.d.ts +46 -0
  121. package/dist/gpu/wgsl-display-view-neighbors.js +114 -0
  122. package/dist/gpu/wgsl-display-view-neighbors.js.map +1 -0
  123. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +6 -9
  124. package/dist/gpu/wgsl-display-view-quality-kernel.js +113 -113
  125. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  126. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
  127. package/dist/gpu/wgsl-display-view-speed-kernel.js +73 -10
  128. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  129. package/dist/index.d.ts +11 -3
  130. package/dist/index.js +9 -1
  131. package/dist/index.js.map +1 -1
  132. package/dist/output/camera-channel-response.d.ts +50 -0
  133. package/dist/output/camera-channel-response.js +69 -0
  134. package/dist/output/camera-channel-response.js.map +1 -0
  135. package/dist/output/native-capture-sdr.js +5 -2
  136. package/dist/output/native-capture-sdr.js.map +1 -1
  137. package/dist/output/sdr-rendering.d.ts +18 -1
  138. package/dist/output/sdr-rendering.js +39 -2
  139. package/dist/output/sdr-rendering.js.map +1 -1
  140. package/dist/sensor/native-sensor-report.d.ts +10 -0
  141. package/dist/sensor/native-sensor-report.js +16 -0
  142. package/dist/sensor/native-sensor-report.js.map +1 -0
  143. package/dist/sensor/sensor-characteristics.d.ts +81 -13
  144. package/dist/sensor/sensor-characteristics.js +10 -6
  145. package/dist/sensor/sensor-characteristics.js.map +1 -1
  146. package/docs/API_REFERENCE.md +117 -4
  147. package/docs/QUICK_START.md +37 -8
  148. package/docs/USAGE.md +105 -22
  149. package/docs/api/capture-c2pa-embed.md +177 -0
  150. package/docs/api/capture-dng-import.md +86 -10
  151. package/docs/api/capture-dng-opcodes.md +93 -0
  152. package/docs/api/capture-dng-profile-maps.md +141 -0
  153. package/docs/api/capture-dng-stage-image.md +43 -0
  154. package/docs/api/capture-jpeg-xl-tiles.md +33 -0
  155. package/docs/api/capture-lossy-jpeg.md +41 -0
  156. package/docs/api/capture-native-raw-development.md +2 -1
  157. package/docs/api/core-execution-error.md +43 -0
  158. package/docs/api/core-version.md +1 -1
  159. package/docs/api/exports.json +255 -5
  160. package/docs/api/facade-display-view-gpu.md +5 -2
  161. package/docs/api/facade-display-view.md +11 -15
  162. package/docs/api/facade-fast-methods.md +38 -9
  163. package/docs/api/facade-quality-paths.md +24 -0
  164. package/docs/api/facade-scene-radiance.md +1 -1
  165. package/docs/api/facade-simulate-capture.md +24 -7
  166. package/docs/api/facade-simulate-display-view.md +10 -12
  167. package/docs/api/gpu-image-kernel-execution.md +0 -36
  168. package/docs/api/output-camera-channel-response.md +55 -0
  169. package/docs/api/output-sdr-rendering.md +6 -1
  170. package/docs/api/sensor-sensor-characteristics.md +54 -2
  171. package/llms.txt +1 -0
  172. package/package.json +6 -5
package/docs/USAGE.md CHANGED
@@ -3324,6 +3324,18 @@ 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 and (since 2.23.0, #605) in its
3338
+ WebGPU Speed and Quality kernels, fast and full.
3327
3339
  - `createPhotographicExportPair(input, { signal })` accepts an optional `AbortSignal`.
3328
3340
 
3329
3341
  ## Capture-mode profiles
@@ -7096,7 +7108,8 @@ See [Local POC Simulation API](https://github.com/Photivra/photivra/blob/main/do
7096
7108
 
7097
7109
  ## DNG import and PNG output
7098
7110
 
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.
7111
+ - `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.
7112
+ - `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
7113
  - `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
7114
 
7102
7115
  See [DNG import and PNG output](https://github.com/Photivra/photivra/blob/main/docs/IMPORT_AND_PNG.md) for supported subsets, limits and sources.
@@ -7533,6 +7546,25 @@ for (const p of SENSOR_PROFILES) {
7533
7546
  }
7534
7547
  ```
7535
7548
 
7549
+ ### Read noise and native-pixel figures (2.22.0, #595, #599)
7550
+
7551
+ `sensor.readNoise` is the read noise of one **physical photosite**, as a sensor datasheet gives it, on a generic tier, a caller
7552
+ 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
7553
+ 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
7554
+ `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
7555
+ kept. `result.sensor` reports the rendered site; `result.sensor.native` one photosite (its `readNoiseElectrons` is the value you
7556
+ gave), with the EMVA 1288 and the 8 MP-normalized dynamic range
7557
+ ([QUICK_START.md, "Native-pixel dynamic range"](QUICK_START.md#sensor-characteristics-555)).
7558
+
7559
+ ```ts
7560
+ import { simulateCapture } from "@photivra/engine";
7561
+
7562
+ const r = await simulateCapture({ scene: { preset: "uniform" }, sensor: { pixelPitchMicrometers: 12, readNoise: { lowGainElectrons: 3 } }, fast: false });
7563
+ r.sensor.native.readNoiseElectrons; // 3, one 6 µm photosite's (the value given)
7564
+ r.sensor.readNoise.lowGainElectrons; // 6, the rendered 12 µm site's: 3 × √4
7565
+ r.sensor.native.emva1288.dynamicRangeStops;
7566
+ ```
7567
+
7536
7568
  ## Generic lens catalogue (#549)
7537
7569
 
7538
7570
  `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
@@ -7547,7 +7579,8 @@ correction profile for `resolveLensCorrectionPlan`, which `simulateCapture` appl
7547
7579
  ([quick start](QUICK_START.md#generic-lens-catalogue-549)); `fast: true` (or `lens: { catalogue: { kind, fast: true } }`) takes its
7548
7580
  fast method (#568), a labeled approximation that blends the PSF over a field grid, for interactive captures.
7549
7581
  `calculateLateralColorFringe({ lensId, activeImagingArea, pixelPitchMicrometers, fieldPosition? })` gives the lens's lateral color as a
7550
- fringe width: the blue-minus-red image displacement in µm and pixels at a field position (default the corner; #580). Equations, tables and the
7582
+ fringe width: the blue-minus-red image displacement in µm and pixels at a field position (default the corner; #580); its `channelDispersion` for
7583
+ the capture's own channels comes from `calculateCameraChannelResponse({ illuminant })` (#591, full quality: a 5 nm grid by default). Equations, tables and the
7551
7584
  chart measurements: [LENS_CATALOGUE.md](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md).
7552
7585
 
7553
7586
  ```ts
@@ -7861,6 +7894,27 @@ const print = await simulateCapture({
7861
7894
  console.log(print.scene.parameters.encoding, print.assumptions.some((a) => a.id === "scene.imagePlane")); // "srgb-8", true
7862
7895
  ```
7863
7896
 
7897
+ ### Loading the full-quality code ahead of time (2.22, #594)
7898
+
7899
+ The full-quality paths load on demand: a bundler that splits dynamic imports keeps their code out of the first load, and the
7900
+ first request that needs it fetches it: the CPU full-quality code (diffraction, the capture's AHD demosaic, `lens.defocus`
7901
+ bokeh shapes, cat's eye and large discs, lens projection and distortion, the display view's luminance tone curves,
7902
+ reconstruct and desaturation, `highlightMode: "reconstruct-neighbors"`), the opt-in features (`sensor.profile`,
7903
+ `illumination`, `motion.subject`) and the display view's GPU full-quality code (diffraction, AHD, defocus and image-plane
7904
+ prints with `prepareDisplayView(…, { gpu: "auto" | "require" })`). `prepareQualityPaths()` fetches all of it ahead of time,
7905
+ so that request does not wait. It is optional, changes no result, and is safe to call at any time: concurrent calls share one
7906
+ load, and later calls load nothing. If the code does not load, the promise rejects with `ImageKernelExecutionError` reason
7907
+ `quality-code-unavailable` and nothing is remembered; a GPU render in the meantime runs the CPU reference (the same full-quality
7908
+ result, slower) with `diagnostics.fast.execution.fallbackReason: "quality-code-unavailable"`, a CPU request whose code does not
7909
+ load fails with that error, and the next request tries again.
7910
+
7911
+ ```ts
7912
+ import { prepareQualityPaths } from "@photivra/engine";
7913
+
7914
+ // For example when the user opens Export, where full-quality renders follow.
7915
+ await prepareQualityPaths().catch(() => undefined); // a failed preload is retried by the request that needs the code
7916
+ ```
7917
+
7864
7918
  **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
7865
7919
  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
7866
7920
  2700 K, each "representative, not a named product". `createRepresentativeLightSourceIlluminant({ sourceId })` returns an
@@ -8009,14 +8063,13 @@ it: Vite builds workers as `iife` by default, so a worker that calls `prepareDis
8009
8063
  modules (`worker: { format: "es" }`, with `new Worker(url, { type: "module" })`) to keep it out. Measured sizes:
8010
8064
  [Bundle size](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#bundle-size).
8011
8065
 
8012
- #### Sharing AHD correction states between workers
8066
+ #### AHD correction states (deprecated in 2.22.0)
8013
8067
 
8014
- With `output.demosaic: "ahd"`, low-signal display pixels read correction states that each realm (a page, a Web Worker)
8015
- computes and keeps for itself. Two workers can split a part's states and exchange them instead of both computing all
8016
- of them; the renders have the same bytes either way (see "AHD at low signal" in
8017
- [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#ahd-at-low-signal)). Import only
8018
- states whose key a prepared view reported. The two handles below run in one script for the example; in an app each
8019
- runs in its own worker, and the arrays travel with `postMessage(set, [set.gridKeys.buffer, set.values.buffer])`.
8068
+ From 2.10 to 2.21, low-signal AHD display pixels read correction states that workers could exchange. 2.22.0 removes the
8069
+ correction (a labelled approximation remains; see "AHD at low signal" in
8070
+ [DISPLAY_VIEW.md](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#ahd-at-low-signal)), so there is nothing
8071
+ to exchange: workers render AHD views with no shared state. The exchange functions stay until 3.0.0 as checked no-ops;
8072
+ remove the calls.
8020
8073
 
8021
8074
  ```ts
8022
8075
  import { serializeDisplayViewCorrectionStates, bindDisplayViewCorrectionStates, prepareDisplayView, type SimulateCaptureOptions } from "@photivra/engine";
@@ -8024,19 +8077,13 @@ import { serializeDisplayViewCorrectionStates, bindDisplayViewCorrectionStates,
8024
8077
  const request: SimulateCaptureOptions = { scene: { preset: "uniform", reflectance: 0.18 }, exposure: { iso: 1600, shutterSeconds: 1 / 2000 },
8025
8078
  sensor: { pixelPitchMicrometers: 1.4 }, output: { width: 200, height: 150, demosaic: "ahd" } };
8026
8079
  const part = { width: 20, height: 15, region: { x: 0, y: 0, width: 10, height: 8 } };
8027
- // Each worker prepares the same request; the key says which states it reads.
8028
- const workerA = await prepareDisplayView(request), workerB = await prepareDisplayView(request);
8029
- console.log(workerA.correctionKey() === workerB.correctionKey());
8030
- // Worker A finds the states the part needs (no image, no state computed) and gives half to each worker.
8031
- const { gridKeys } = await workerA.collectCorrectionStates(part);
8032
- const half = Math.ceil(gridKeys.length / 2);
8033
- const fromA = await workerA.computeCorrectionStates(gridKeys.slice(0, half));
8034
- const fromB = await workerB.computeCorrectionStates(gridKeys.slice(half));
8035
- // Each worker imports the other's share (in one realm, as here, the second import adds nothing new).
8036
- console.log(bindDisplayViewCorrectionStates([fromB]), bindDisplayViewCorrectionStates([fromA]));
8037
- // Either worker now renders the part without computing a state; a new worker can start from an export.
8038
- const view = await workerA.render(part);
8039
- console.log(view.diagnostics.ahdCorrectedPixelCount, serializeDisplayViewCorrectionStates().map((set) => set.gridKeys.length));
8080
+ const prepared = await prepareDisplayView(request);
8081
+ // Deprecated: null, no grid keys, no states, nothing stored.
8082
+ console.log(prepared.correctionKey(), (await prepared.collectCorrectionStates(part)).gridKeys.length);
8083
+ console.log(serializeDisplayViewCorrectionStates().length, bindDisplayViewCorrectionStates([]));
8084
+ // The view itself needs none of them.
8085
+ const view = await prepared.render(part);
8086
+ console.log(view.diagnostics.ahdCorrectedPixelCount); // always 0 since 2.22.0
8040
8087
  ```
8041
8088
 
8042
8089
  ### Facade 2.0 options: output, demosaic, optics and motion
@@ -8239,3 +8286,39 @@ console.log(outOfFocus.limitingFactor); // "defocus"
8239
8286
  - Demosaicing, sharpening, noise and motion are not included.
8240
8287
 
8241
8288
  See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#which-limits-detail-the-lens-or-the-sensor).
8289
+
8290
+ ## Content Credentials in exports (#165)
8291
+
8292
+ `bindC2paManifestStore`, `calculateC2paDataHash` and `setC2paManifestStore` embed a C2PA Manifest Store in a JPEG, PNG or
8293
+ DNG export, hash the bytes the signature covers and write the signed store in place at the same length (C2PA 2.4 §10.4);
8294
+ `parseC2paManifestStore` finds a store already in a file, without verifying it. The application builds, signs and validates
8295
+ the manifest; the engine holds no keys and makes no network calls. Here a minimal one-IFD TIFF
8296
+ stands in for a DNG export, and the stores are only the C2PA framing around zero or one bytes, not real manifests:
8297
+
8298
+ ```ts
8299
+ import { bindC2paManifestStore, calculateC2paDataHash, parseC2paManifestStore, setC2paManifestStore } from '@photivra/engine';
8300
+
8301
+ // 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.
8302
+ function framedStore(length: number, fill: number): Uint8Array {
8303
+ const store = new Uint8Array(length).fill(fill), view = new DataView(store.buffer);
8304
+ view.setUint32(0, length); store.set([0x6a, 0x75, 0x6d, 0x62], 4); // 'jumb' superbox
8305
+ view.setUint32(8, 30); store.set([0x6a, 0x75, 0x6d, 0x64], 12); // 'jumd' description box
8306
+ 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"
8307
+ view.setUint32(38, length - 38); store.set([0x6a, 0x75, 0x6d, 0x62], 42); // one child box filling the rest
8308
+ return store;
8309
+ }
8310
+ 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);
8311
+
8312
+ const bound = bindC2paManifestStore({ file: tiff, manifestStore: framedStore(1024, 0) });
8313
+ console.log(bound.binding.exclusions); // [{ start: 26, length: 4 }, { start: 40, length: 1024 }]: the entry's count field and the store
8314
+ const { hashHex } = await calculateC2paDataHash({ file: bound.bytes, exclusions: bound.binding.exclusions });
8315
+ // The app writes the exclusions and hashHex into its c2pa.hash.data assertion, signs, and builds the final store at 1,024 bytes.
8316
+ const signed = setC2paManifestStore({ file: bound.bytes, binding: bound.binding, manifestStore: framedStore(1024, 1) });
8317
+ const after = await calculateC2paDataHash({ file: signed, exclusions: bound.binding.exclusions });
8318
+ if (signed.length !== bound.bytes.length || after.hashHex !== hashHex) throw new Error('The signed file must keep its length and data hash');
8319
+ const found = parseC2paManifestStore({ file: signed }); // the store and the same binding, or null for a file without one
8320
+ if (found?.manifestStore[46] !== 1) throw new Error('The signed store must be found');
8321
+ ```
8322
+
8323
+ See [Content Credentials](https://github.com/Photivra/photivra/blob/main/docs/CONTENT_CREDENTIALS.md) for the byte layouts,
8324
+ 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;
@@ -0,0 +1,93 @@
1
+ # capture/dng-opcodes.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
+ ## applyDngOpcodes
6
+
7
+ Applies an opcode list to a stage image in place (WarpRectilinear returns a new buffer in `image.data`). Returns one assumption
8
+ per opcode: what was applied, or why it was not.
9
+
10
+ ```ts
11
+ export function applyDngOpcodes(image: DngStageImage, opcodes: readonly DngOpcode[], options: ApplyDngOpcodesOptions): string[];
12
+ ```
13
+
14
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
15
+
16
+ ## ApplyDngOpcodesOptions
17
+
18
+ Options of {@link applyDngOpcodes}.
19
+
20
+ ```ts
21
+ export interface ApplyDngOpcodesOptions {
22
+ /** 1, 2 or 3: the list's stage (for the report). */
23
+ stage: 1 | 2 | 3;
24
+ /**
25
+ * WarpRectilinear resampling: false (full quality) uses cubic convolution (Keys, a = −0.75, the DNG SDK's kernel); true uses
26
+ * bilinear interpolation (fast). Registered as "dngImport.warpRectilinear" in SIMULATE_CAPTURE_FAST_METHODS.
27
+ */
28
+ fast: boolean;
29
+ /** 1, or 0.5 for a half-size image: pixel-coordinate parameters are scaled; pitched areas and bad pixels are then not applied. */
30
+ scale?: 1 | 0.5;
31
+ }
32
+ ```
33
+
34
+ ## DngOpcode
35
+
36
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
37
+
38
+ ```ts
39
+ export interface DngOpcode {
40
+ id: number;
41
+ /** The specification's name, or "Unknown". */
42
+ name: string;
43
+ dngVersion: [number, number, number, number];
44
+ /** Flag bit 0: a reader may skip it. */
45
+ optional: boolean;
46
+ /** Flag bit 1: may be skipped for preview-quality rendering. */
47
+ previewSkip: boolean;
48
+ /** Decoded parameters of an applied opcode; null for one this reader does not apply. */
49
+ parameters: DngOpcodeParameters | null;
50
+ }
51
+ ```
52
+
53
+ ## DngOpcodeArea
54
+
55
+ The rectangle, plane range and pitch an area-based opcode acts on (stage pixel coordinates, bottom and right exclusive).
56
+
57
+ ```ts
58
+ export interface DngOpcodeArea { top: number; left: number; bottom: number; right: number; plane: number; planes: number; rowPitch: number; colPitch: number }
59
+ ```
60
+
61
+ ## DngOpcodeParameters
62
+
63
+ Opcode parameters, by opcode.
64
+
65
+ ```ts
66
+ export type DngOpcodeParameters =
67
+ | { opcode: "WarpRectilinear"; planes: { radial: [number, number, number, number]; tangential: [number, number] }[]; centerX: number; centerY: number }
68
+ | { opcode: "FixVignetteRadial"; coefficients: [number, number, number, number, number]; centerX: number; centerY: number }
69
+ | { opcode: "FixBadPixelsConstant"; constant: number; bayerPhase: number }
70
+ | { opcode: "FixBadPixelsList"; bayerPhase: number; points: [number, number][]; rects: [number, number, number, number][] }
71
+ | { opcode: "MapTable"; area: DngOpcodeArea; table: Uint16Array }
72
+ | { opcode: "MapPolynomial"; area: DngOpcodeArea; coefficients: number[] }
73
+ | { opcode: "GainMap"; area: DngOpcodeArea; pointsV: number; pointsH: number; spacingV: number; spacingH: number; originV: number; originH: number; mapPlanes: number; gains: Float32Array }
74
+ | { opcode: "DeltaPerRow" | "DeltaPerColumn" | "ScalePerRow" | "ScalePerColumn"; area: DngOpcodeArea; values: Float32Array };
75
+ ```
76
+
77
+ ## DngStageImage
78
+
79
+ A stage image: interleaved (row, column, plane), normalized values.
80
+
81
+ ```ts
82
+ export interface DngStageImage { width: number; height: number; planes: number; data: Float32Array }
83
+ ```
84
+
85
+ ## parseDngOpcodeList
86
+
87
+ Parses an opcode list (the tag's bytes). Every count and length is checked against the bytes before allocation.
88
+
89
+ ```ts
90
+ export function parseDngOpcodeList(bytes: Uint8Array): DngOpcode[];
91
+ ```
92
+
93
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.