@photivra/engine 2.20.0 → 2.21.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 (174) hide show
  1. package/CHANGELOG.md +178 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +7 -0
  6. package/dist/capture/dng-lens-correction-opcodes.d.ts +2 -2
  7. package/dist/capture/dng-lens-correction-opcodes.js +2 -2
  8. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
  9. package/dist/capture/exposure-build-up-stage.d.ts +16 -0
  10. package/dist/capture/exposure-build-up-stage.js +4 -0
  11. package/dist/capture/exposure-build-up-stage.js.map +1 -0
  12. package/dist/capture/native-environment-lean.d.ts +2 -1
  13. package/dist/capture/native-environment-lean.js +41 -73
  14. package/dist/capture/native-environment-lean.js.map +1 -1
  15. package/dist/capture/native-environment-tabulated.js +4 -3
  16. package/dist/capture/native-environment-tabulated.js.map +1 -1
  17. package/dist/capture/native-raw.d.ts +3 -2
  18. package/dist/capture/native-raw.js +5 -4
  19. package/dist/capture/native-raw.js.map +1 -1
  20. package/dist/capture/sensor-raw-producer.d.ts +4 -1
  21. package/dist/capture/sensor-raw-producer.js +12 -3
  22. package/dist/capture/sensor-raw-producer.js.map +1 -1
  23. package/dist/core/version.d.ts +1 -1
  24. package/dist/core/version.js +1 -1
  25. package/dist/core/version.js.map +1 -1
  26. package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
  27. package/dist/equipment/generic-lens-catalogue.js +18 -5
  28. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  29. package/dist/equipment/generic-tier-assets.d.ts +32 -0
  30. package/dist/equipment/generic-tier-assets.js +16 -11
  31. package/dist/equipment/generic-tier-assets.js.map +1 -1
  32. package/dist/equipment/sensor-profile-body.d.ts +78 -0
  33. package/dist/equipment/sensor-profile-body.js +77 -0
  34. package/dist/equipment/sensor-profile-body.js.map +1 -0
  35. package/dist/equipment/sensor-profiles.d.ts +8 -0
  36. package/dist/equipment/sensor-profiles.js +82 -0
  37. package/dist/equipment/sensor-profiles.js.map +1 -0
  38. package/dist/facade/catalogue-lens-signal.d.ts +2 -2
  39. package/dist/facade/catalogue-lens-signal.js +58 -71
  40. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  41. package/dist/facade/chart-radiance-batch.js +2 -1
  42. package/dist/facade/chart-radiance-batch.js.map +1 -1
  43. package/dist/facade/defocus-image-space-provider.d.ts +7 -0
  44. package/dist/facade/defocus-image-space-provider.js +64 -76
  45. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  46. package/dist/facade/display-view-gpu-session.js +2 -0
  47. package/dist/facade/display-view-gpu-session.js.map +1 -1
  48. package/dist/facade/display-view-gpu.d.ts +6 -1
  49. package/dist/facade/display-view-gpu.js.map +1 -1
  50. package/dist/facade/display-view-lens-signal.js +25 -23
  51. package/dist/facade/display-view-lens-signal.js.map +1 -1
  52. package/dist/facade/display-view.js +5 -23
  53. package/dist/facade/display-view.js.map +1 -1
  54. package/dist/facade/expected-signal-setup.d.ts +3 -1
  55. package/dist/facade/expected-signal-setup.js +1 -1
  56. package/dist/facade/expected-signal-setup.js.map +1 -1
  57. package/dist/facade/expected-signal.d.ts +47 -14
  58. package/dist/facade/expected-signal.js +117 -112
  59. package/dist/facade/expected-signal.js.map +1 -1
  60. package/dist/facade/exposure-build-up.d.ts +70 -0
  61. package/dist/facade/exposure-build-up.js +164 -0
  62. package/dist/facade/exposure-build-up.js.map +1 -0
  63. package/dist/facade/fast-methods.js +9 -1
  64. package/dist/facade/fast-methods.js.map +1 -1
  65. package/dist/facade/generic-lens-capture.d.ts +2 -2
  66. package/dist/facade/generic-lens-capture.js +2 -2
  67. package/dist/facade/generic-lens-capture.js.map +1 -1
  68. package/dist/facade/image-plane-enable.d.ts +7 -0
  69. package/dist/facade/image-plane-enable.js +17 -0
  70. package/dist/facade/image-plane-enable.js.map +1 -0
  71. package/dist/facade/image-plane-lattice-data.d.ts +6 -0
  72. package/dist/facade/image-plane-lattice-data.js +10 -0
  73. package/dist/facade/image-plane-lattice-data.js.map +1 -0
  74. package/dist/facade/image-plane-support.d.ts +33 -0
  75. package/dist/facade/image-plane-support.js +11 -0
  76. package/dist/facade/image-plane-support.js.map +1 -0
  77. package/dist/facade/image-plane.d.ts +36 -0
  78. package/dist/facade/image-plane.js +353 -0
  79. package/dist/facade/image-plane.js.map +1 -0
  80. package/dist/facade/mixed-illumination.d.ts +108 -0
  81. package/dist/facade/mixed-illumination.js +174 -0
  82. package/dist/facade/mixed-illumination.js.map +1 -0
  83. package/dist/facade/representative-light-sources.d.ts +72 -0
  84. package/dist/facade/representative-light-sources.js +124 -0
  85. package/dist/facade/representative-light-sources.js.map +1 -0
  86. package/dist/facade/scene-radiance.d.ts +129 -4
  87. package/dist/facade/scene-radiance.js +48 -13
  88. package/dist/facade/scene-radiance.js.map +1 -1
  89. package/dist/facade/simulate-capture.d.ts +39 -10
  90. package/dist/facade/simulate-capture.js +252 -92
  91. package/dist/facade/simulate-capture.js.map +1 -1
  92. package/dist/facade/simulate-display-view.js +14 -6
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/chart-composite-gpu.d.ts +13 -1
  95. package/dist/gpu/chart-composite-gpu.js +48 -9
  96. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  97. package/dist/gpu/chart-radiance-gpu.js +4 -30
  98. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  99. package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
  100. package/dist/gpu/display-view-composite-material-module.js +39 -13
  101. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  102. package/dist/gpu/display-view-image-material-module.d.ts +86 -0
  103. package/dist/gpu/display-view-image-material-module.js +339 -0
  104. package/dist/gpu/display-view-image-material-module.js.map +1 -0
  105. package/dist/gpu/display-view-image-quality.d.ts +45 -0
  106. package/dist/gpu/display-view-image-quality.js +380 -0
  107. package/dist/gpu/display-view-image-quality.js.map +1 -0
  108. package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
  109. package/dist/gpu/display-view-quality-gpu.js +136 -205
  110. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  111. package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
  112. package/dist/gpu/display-view-speed-gpu.js +131 -93
  113. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
  115. package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
  116. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  117. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
  118. package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
  119. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  120. package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
  121. package/dist/gpu/wgsl-exact-sum.js +42 -0
  122. package/dist/gpu/wgsl-exact-sum.js.map +1 -0
  123. package/dist/index.d.ts +11 -3
  124. package/dist/index.js +7 -1
  125. package/dist/index.js.map +1 -1
  126. package/dist/motion/subject-motion.d.ts +66 -0
  127. package/dist/motion/subject-motion.js +52 -0
  128. package/dist/motion/subject-motion.js.map +1 -0
  129. package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
  130. package/dist/optics/generic-lens-lateral-color.js +77 -0
  131. package/dist/optics/generic-lens-lateral-color.js.map +1 -0
  132. package/dist/optics/generic-lens-psf.js +54 -46
  133. package/dist/optics/generic-lens-psf.js.map +1 -1
  134. package/dist/optics/lens-psf-profile.d.ts +7 -0
  135. package/dist/optics/lens-psf-profile.js +2 -1
  136. package/dist/optics/lens-psf-profile.js.map +1 -1
  137. package/dist/output/developed-lens-corrections.d.ts +19 -5
  138. package/dist/output/developed-lens-corrections.js +28 -9
  139. package/dist/output/developed-lens-corrections.js.map +1 -1
  140. package/dist/output/generic-lens-corrections.d.ts +41 -5
  141. package/dist/output/generic-lens-corrections.js +52 -8
  142. package/dist/output/generic-lens-corrections.js.map +1 -1
  143. package/dist/output/print-plan.d.ts +41 -5
  144. package/dist/output/print-plan.js +59 -14
  145. package/dist/output/print-plan.js.map +1 -1
  146. package/dist/sensor/binomial-sample.d.ts +3 -0
  147. package/dist/sensor/binomial-sample.js +86 -0
  148. package/dist/sensor/binomial-sample.js.map +1 -0
  149. package/dist/sensor/sensor-characteristics.d.ts +10 -0
  150. package/dist/sensor/sensor-characteristics.js +20 -9
  151. package/dist/sensor/sensor-characteristics.js.map +1 -1
  152. package/docs/AGENT_GUIDE.md +1 -0
  153. package/docs/API_REFERENCE.md +98 -1
  154. package/docs/QUICK_START.md +125 -0
  155. package/docs/USAGE.md +92 -1
  156. package/docs/api/core-version.md +1 -1
  157. package/docs/api/equipment-generic-lens-catalogue.md +10 -5
  158. package/docs/api/equipment-sensor-profile-body.md +64 -0
  159. package/docs/api/equipment-sensor-profiles.md +86 -0
  160. package/docs/api/exports.json +207 -2
  161. package/docs/api/facade-exposure-build-up.md +105 -0
  162. package/docs/api/facade-fast-methods.md +9 -1
  163. package/docs/api/facade-image-plane-enable.md +16 -0
  164. package/docs/api/facade-mixed-illumination.md +67 -0
  165. package/docs/api/facade-representative-light-sources.md +131 -0
  166. package/docs/api/facade-scene-radiance.md +54 -3
  167. package/docs/api/facade-simulate-capture.md +26 -6
  168. package/docs/api/motion-subject-motion.md +23 -0
  169. package/docs/api/optics-generic-lens-lateral-color.md +79 -0
  170. package/docs/api/output-developed-lens-corrections.md +10 -1
  171. package/docs/api/output-generic-lens-corrections.md +40 -4
  172. package/docs/api/output-print-plan.md +35 -1
  173. package/llms.txt +2 -0
  174. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"developed-lens-corrections.js","sourceRoot":"","sources":["../../src/output/developed-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * @internal `output.lensCorrections` (#570): a catalogue lens's generic correction profile (`createGenericLensCorrectionProfile`)\n * applied to the developed linear RGB of `simulateCapture`, after demosaic, colour and white balance and before the tone curve.\n * The RAW and the DNG are never corrected.\n *\n * For an output pixel at p (pixels from the frame centre, +y up) and channel c, the source point is read by inverse mapping,\n * as the profile composes it (lateral colour, then distortion):\n * q = m · a_c · p, s = q · (1 + k1·|q|²/R²),\n * with a_c = 1 + L·D(λ_c) the channel's lateral-colour scale, k1 the lens's corner distortion, R the half-diagonal and m ≤ 1 the\n * fill factor (the largest that keeps every output pixel centre's source inside the frame for every channel, so the output keeps\n * its size with no missing pixels; the image is magnified by 1/m). The channel is sampled bilinearly at s.\n *\n * Vignetting is corrected to a flat field: the sample is multiplied by det J(ρ_i) / E(ρ_s), the inverse of the relative\n * illumination the capture delivered at s: the lens's E(ρ) = (1 − bρ²)² at the sensor point and its distortion's area factor\n * 1/det J, det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal point ρ_i that the lens images to ρ_s (the capture's\n * \"inverse-distortion-jacobian\"; a relative illumination measured on a flat field contains it too). From profile 1.1.0 (#584) the\n * profile's vignetting component carries the same area factor (`distortionAreaFactor`), and its gain equals this kernel's.\n * Every position is computed in whole-frame pixel coordinates, so a region renders the same values as the whole frame.\n */\n\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport type { LensCorrectionChannel } from \"./lens-corrections.js\";\n\n/**\n * Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where\n * a camera's channel responses peak, not a measured camera.\n */\nexport const GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = /* @__PURE__ */ Object.freeze({ red: 610, green: 540, blue: 465 });\n\n/** @internal The lateral-colour scale of one channel, 1 + L·D(λ_channel) (`D` the catalogue's normalized dispersion). */\nexport function genericLensChannelScale(fraction: number, dispersion: (wavelengthNm: number) => number, channel: LensCorrectionChannel): number {\n return 1 + fraction * dispersion(GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM[channel]);\n}\n\n/** @internal Which corrections run. */\nexport interface DevelopedLensCorrectionSelection { distortion: boolean; lateralColor: boolean; vignetting: boolean }\n/** @internal A prepared correction for one frame (pixel units). */\nexport interface DevelopedLensCorrection {\n width: number; height: number;\n /** Half-diagonal R in pixels. */\n radius: number;\n /** k1 (0 when distortion is off), the lens's own k1, and the red, green and blue scales a_c (1 when lateral colour is off). */\n k1: number; lensK1: number; scales: readonly [number, number, number];\n /** Fill factor m ≤ 1 (1/m is the magnification). */\n fill: number;\n}\ntype Rect = { x: number; y: number; width: number; height: number };\n\n/** Source position (pixel-centre coordinates of the whole frame, unclamped) of output pixel (x, y), channel c. */\nfunction sourceOf(c: DevelopedLensCorrection, m: number, x: number, y: number, channel: number, out: Float64Array): void {\n const a = m * c.scales[channel]!, px = (x + 0.5 - c.width / 2) * a, py = (c.height / 2 - y - 0.5) * a;\n const s = 1 + c.k1 * (px * px + py * py) / (c.radius * c.radius);\n out[0] = px * s + c.width / 2 - 0.5; out[1] = c.height / 2 - 0.5 - py * s;\n}\n/** Calls `visit` for every pixel centre on the border of `r`. */\nfunction border(r: Rect, visit: (x: number, y: number) => void): void {\n for (let x = r.x; x < r.x + r.width; x++) { visit(x, r.y); visit(x, r.y + r.height - 1); }\n for (let y = r.y; y < r.y + r.height; y++) { visit(r.x, y); visit(r.x + r.width - 1, y); }\n}\n\n/**\n * @internal Prepares the correction of a W × H frame. `lens` is the resolved catalogue lens (`resolveGenericLens`) on the\n * frame; `dispersion` is `genericLensDispersion`. The mapping's source radius grows with the output radius (the catalogue's\n * distortion is monotonic over the frame), so the sources of the border pixel centres bound every source, and the fill factor\n * is found by bisection on them.\n */\nexport function prepareDevelopedLensCorrection(lens: { halfDiagonalMm: number; character: { distortion: { corner: number }; lateralColor: { fraction: number } } },\n selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number, dispersion: (wavelengthNm: number) => number): DevelopedLensCorrection {\n const fraction = selection.lateralColor ? lens.character.lateralColor.fraction : 0;\n const c: DevelopedLensCorrection = { width, height, radius: lens.halfDiagonalMm / pitchMm,\n k1: selection.distortion ? lens.character.distortion.corner : 0, lensK1: lens.character.distortion.corner,\n scales: [genericLensChannelScale(fraction, dispersion, \"red\"), genericLensChannelScale(fraction, dispersion, \"green\"), genericLensChannelScale(fraction, dispersion, \"blue\")], fill: 1 };\n const p = new Float64Array(2), frame = { x: 0, y: 0, width, height };\n const fits = (m: number): boolean => {\n let ok = true;\n border(frame, (x, y) => {\n for (let k = 0; k < 3 && ok; k++) { sourceOf(c, m, x, y, k, p); ok = p[0]! >= 0 && p[0]! <= width - 1 && p[1]! >= 0 && p[1]! <= height - 1; }\n });\n return ok;\n };\n if (!fits(1)) {\n let lo = 0.5, hi = 1;\n for (let i = 0; i < 52; i++) { const mid = (lo + hi) / 2; if (fits(mid)) lo = mid; else hi = mid; }\n c.fill = lo;\n }\n return c;\n}\n\n/**\n * @internal The developed pixels (a rectangle of the whole frame) that the corrected pixels of `region` read: the bounding box of\n * the region border's sources over the three channels, with their bilinear neighbours, one pixel of margin, clipped to the frame.\n */\nexport function developedLensCorrectionSourceRect(c: DevelopedLensCorrection, region: Rect): Rect {\n const p = new Float64Array(2);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n border(region, (x, y) => {\n for (let k = 0; k < 3; k++) {\n sourceOf(c, c.fill, x, y, k, p);\n x0 = Math.min(x0, p[0]!); x1 = Math.max(x1, p[0]!); y0 = Math.min(y0, p[1]!); y1 = Math.max(y1, p[1]!);\n }\n });\n const left = Math.max(0, Math.floor(x0) - 1), top = Math.max(0, Math.floor(y0) - 1);\n return { x: left, y: top, width: Math.min(c.width - 1, Math.floor(x1) + 2) - left + 1, height: Math.min(c.height - 1, Math.floor(y1) + 2) - top + 1 };\n}\n\n/**\n * @internal Corrects developed linear RGB (`rgb`, interleaved, the frame's pixels in `source`) into the pixels of `region`, both\n * rectangles of the whole frame. `cornerStops` is the lens's vignetting at the capture's f-number, or null to leave it.\n * Positions outside the frame (none after the fill, up to rounding) are clamped to the edge pixel centres.\n */\nexport function applyDevelopedLensCorrection(c: DevelopedLensCorrection, rgb: Float64Array, source: Rect, region: Rect, cornerStops: number | null): Float64Array {\n const out = new Float64Array(region.width * region.height * 3), W = c.width, H = c.height, cx = W / 2 - 0.5, cy = H / 2 - 0.5, r2 = c.radius * c.radius;\n const b = cornerStops === null ? 0 : 1 - 2 ** (-cornerStops / 2), k1 = c.k1, lensK1 = c.lensK1;\n for (let y = 0, o = 0; y < region.height; y++) for (let x = 0; x < region.width; x++) for (let k = 0; k < 3; k++, o++) {\n // sourceOf, inlined (a call per sample is about 5 times slower here): the same arithmetic, so the region bounds hold.\n const a = c.fill * c.scales[k]!, px = (region.x + x + 0.5 - W / 2) * a, py = (H / 2 - (region.y + y) - 0.5) * a, f = 1 + k1 * (px * px + py * py) / r2;\n const su = px * f + W / 2 - 0.5, sv = H / 2 - 0.5 - py * f;\n const u = Math.min(W - 1, Math.max(0, su)), v = Math.min(H - 1, Math.max(0, sv));\n const i = Math.floor(u), j = Math.floor(v), dx = u - i, dy = v - j, i1 = dx === 0 ? i : i + 1, j1 = dy === 0 ? j : j + 1;\n if (i < source.x || j < source.y || i1 >= source.x + source.width || j1 >= source.y + source.height)\n throw new InvalidScientificResultError(\"Lens correction read outside the developed rectangle.\");\n const row = (j - source.y) * source.width, row1 = (j1 - source.y) * source.width, a0 = i - source.x, a1 = i1 - source.x;\n let value = (1 - dy) * ((1 - dx) * rgb[(row + a0) * 3 + k]! + dx * rgb[(row + a1) * 3 + k]!) + dy * ((1 - dx) * rgb[(row1 + a0) * 3 + k]! + dx * rgb[(row1 + a1) * 3 + k]!);\n if (b !== 0) {\n // ρ_s², then ρ_i from ρ_s = ρ_i(1 + k1ρ_i²) by Newton's method (monotonic over the frame; converged to rounding in 4 steps).\n const s2 = ((su - cx) * (su - cx) + (sv - cy) * (sv - cy)) / r2, rs = Math.sqrt(s2), e = 1 - b * s2;\n let ri = rs;\n for (let n = 0; n < 4; n++) ri -= (ri * (1 + lensK1 * ri * ri) - rs) / (1 + 3 * lensK1 * ri * ri);\n value *= (1 + lensK1 * ri * ri) * (1 + 3 * lensK1 * ri * ri) / (e * e);\n }\n out[o] = value;\n }\n return out;\n}\n"]}
1
+ {"version":3,"file":"developed-lens-corrections.js","sourceRoot":"","sources":["../../src/output/developed-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * @internal `output.lensCorrections` (#570): a catalogue lens's generic correction profile (`createGenericLensCorrectionProfile`)\n * applied to the developed linear RGB of `simulateCapture`, after demosaic, colour and white balance and before the tone curve.\n * The RAW and the DNG are never corrected.\n *\n * For an output pixel at p (pixels from the frame centre, +y up) and channel c, the source point is read by inverse mapping,\n * as the profile composes it (lateral colour, then distortion):\n * q = m · a_c · p, s = q · (1 + k1·|q|²/R²),\n * with a_c = 1 + L·D(λ_c) the channel's lateral-colour scale, k1 the lens's corner distortion, R the half-diagonal and m ≤ 1 the\n * fill factor (the largest that keeps every output pixel centre's source inside the frame for every channel, so the output keeps\n * its size with no missing pixels; the image is magnified by 1/m). The channel is sampled bilinearly at s.\n *\n * Vignetting is corrected to a flat field: the sample is multiplied by det J(ρ_i) / E(ρ_s), the inverse of the relative\n * illumination the capture delivered at s: the lens's E(ρ) = (1 − bρ²)² at the sensor point and its distortion's area factor\n * 1/det J, det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal point ρ_i that the lens images to ρ_s (the capture's\n * \"inverse-distortion-jacobian\"; a relative illumination measured on a flat field contains it too). From profile 1.1.0 (#584) the\n * profile's vignetting component carries the same area factor (`distortionAreaFactor`), and its gain equals this kernel's.\n * Every position is computed in whole-frame pixel coordinates, so a region renders the same values as the whole frame.\n */\n\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport type { LensCorrectionChannel } from \"./lens-corrections.js\";\nimport { genericLensDispersion } from \"../equipment/generic-lens-catalogue.js\";\n\n/**\n * Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where\n * a camera's channel responses peak, not a measured camera. The default of `createGenericLensCorrectionProfile` without a\n * `channelDispersion`; `simulateCapture`'s `output.lensCorrections` uses the capture's own channels instead (#589).\n */\nexport const GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = /* @__PURE__ */ Object.freeze({ red: 610, green: 540, blue: 465 });\n\n/** A normalized dispersion per channel (`genericLensDispersion` units: 0 at the d line, F minus C = 1). */\nexport type ChannelDispersion = Readonly<Record<LensCorrectionChannel, number>>;\n\n/**\n * @internal #589: each channel's effective dispersion D̄_c = Σ_n w_c,n·D(λ_n) / Σ_n w_c,n, w_c,n = response_c(λ_n)·illuminant(λ_n), the\n * dispersion a neutral surface's image in that channel carries (its centroid moves by L·r·D̄_c under lateral colour L·r·D(λ)).\n * `response` holds the red, green and blue values at the nodes `wavelengthsNm`; each channel needs a positive total weight.\n */\nexport function channelEffectiveDispersion(wavelengthsNm: readonly number[], response: readonly ArrayLike<number>[], illuminant: ArrayLike<number>): ChannelDispersion {\n const [red, green, blue] = response.map((r) => {\n let weighted = 0, total = 0;\n wavelengthsNm.forEach((nm, n) => { const w = r[n]! * illuminant[n]!; weighted += w * genericLensDispersion(nm); total += w; });\n return weighted / total;\n }) as [number, number, number];\n return { red, green, blue };\n}\n\n/**\n * @internal The lateral-colour scale of one channel, 1 + L·D̄ with D̄ the channel's effective dispersion, or D(λ_channel) at\n * {@link GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM} without one (`D` the catalogue's normalized dispersion).\n */\nexport function genericLensChannelScale(fraction: number, channel: LensCorrectionChannel, channels?: ChannelDispersion): number {\n return 1 + fraction * (channels ? channels[channel] : genericLensDispersion(GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM[channel]));\n}\n\n/** @internal Which corrections run. */\nexport interface DevelopedLensCorrectionSelection { distortion: boolean; lateralColor: boolean; vignetting: boolean }\n/** @internal A prepared correction for one frame (pixel units). */\nexport interface DevelopedLensCorrection {\n width: number; height: number;\n /** Half-diagonal R in pixels. */\n radius: number;\n /** k1 (0 when distortion is off), the lens's own k1, and the red, green and blue scales a_c (1 when lateral colour is off). */\n k1: number; lensK1: number; scales: readonly [number, number, number];\n /** Fill factor m ≤ 1 (1/m is the magnification). */\n fill: number;\n}\ntype Rect = { x: number; y: number; width: number; height: number };\n\n/** Source position (pixel-centre coordinates of the whole frame, unclamped) of output pixel (x, y), channel c. */\nfunction sourceOf(c: DevelopedLensCorrection, m: number, x: number, y: number, channel: number, out: Float64Array): void {\n const a = m * c.scales[channel]!, px = (x + 0.5 - c.width / 2) * a, py = (c.height / 2 - y - 0.5) * a;\n const s = 1 + c.k1 * (px * px + py * py) / (c.radius * c.radius);\n out[0] = px * s + c.width / 2 - 0.5; out[1] = c.height / 2 - 0.5 - py * s;\n}\n/** Calls `visit` for every pixel centre on the border of `r`. */\nfunction border(r: Rect, visit: (x: number, y: number) => void): void {\n for (let x = r.x; x < r.x + r.width; x++) { visit(x, r.y); visit(x, r.y + r.height - 1); }\n for (let y = r.y; y < r.y + r.height; y++) { visit(r.x, y); visit(r.x + r.width - 1, y); }\n}\n\n/**\n * @internal Prepares the correction of a W × H frame. `lens` is the resolved catalogue lens (`resolveGenericLens`) on the\n * frame; `channels` the channels' effective dispersion (#589). Without it the scales span every channel's possible dispersion\n * (red and green at D(780 nm), blue at D(380 nm), the spectral range's ends): `simulateCapture` prepares that first, so the\n * developed rectangle a region reads (`developedLensCorrectionSourceRect`) holds the sources of any channel scales. The mapping's source radius grows with the output radius (the catalogue's\n * distortion is monotonic over the frame), so the sources of the border pixel centres bound every source, and the fill factor\n * is found by bisection on them.\n */\nexport function prepareDevelopedLensCorrection(lens: { halfDiagonalMm: number; character: { distortion: { corner: number }; lateralColor: { fraction: number } } },\n selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number,\n channels: ChannelDispersion = { red: genericLensDispersion(780), green: genericLensDispersion(780), blue: genericLensDispersion(380) }): DevelopedLensCorrection {\n const fraction = selection.lateralColor ? lens.character.lateralColor.fraction : 0, scale = (c: LensCorrectionChannel): number => genericLensChannelScale(fraction, c, channels);\n const c: DevelopedLensCorrection = { width, height, radius: lens.halfDiagonalMm / pitchMm,\n k1: selection.distortion ? lens.character.distortion.corner : 0, lensK1: lens.character.distortion.corner, scales: [scale(\"red\"), scale(\"green\"), scale(\"blue\")], fill: 1 };\n const p = new Float64Array(2), frame = { x: 0, y: 0, width, height };\n const fits = (m: number): boolean => {\n let ok = true;\n border(frame, (x, y) => {\n for (let k = 0; k < 3 && ok; k++) { sourceOf(c, m, x, y, k, p); ok = p[0]! >= 0 && p[0]! <= width - 1 && p[1]! >= 0 && p[1]! <= height - 1; }\n });\n return ok;\n };\n if (!fits(1)) {\n let lo = 0.5, hi = 1;\n for (let i = 0; i < 52; i++) { const mid = (lo + hi) / 2; if (fits(mid)) lo = mid; else hi = mid; }\n c.fill = lo;\n }\n return c;\n}\n\n/**\n * @internal The developed pixels (a rectangle of the whole frame) that the corrected pixels of `region` read: the bounding box of\n * the region border's sources over the three channels, with their bilinear neighbours, one pixel of margin, clipped to the frame.\n */\nexport function developedLensCorrectionSourceRect(c: DevelopedLensCorrection, region: Rect): Rect {\n const p = new Float64Array(2);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n border(region, (x, y) => {\n for (let k = 0; k < 3; k++) {\n sourceOf(c, c.fill, x, y, k, p);\n x0 = Math.min(x0, p[0]!); x1 = Math.max(x1, p[0]!); y0 = Math.min(y0, p[1]!); y1 = Math.max(y1, p[1]!);\n }\n });\n const left = Math.max(0, Math.floor(x0) - 1), top = Math.max(0, Math.floor(y0) - 1);\n return { x: left, y: top, width: Math.min(c.width - 1, Math.floor(x1) + 2) - left + 1, height: Math.min(c.height - 1, Math.floor(y1) + 2) - top + 1 };\n}\n\n/**\n * @internal Corrects developed linear RGB (`rgb`, interleaved, the frame's pixels in `source`) into the pixels of `region`, both\n * rectangles of the whole frame. `cornerStops` is the lens's vignetting at the capture's f-number, or null to leave it.\n * Positions outside the frame (none after the fill, up to rounding) are clamped to the edge pixel centres.\n */\nexport function applyDevelopedLensCorrection(c: DevelopedLensCorrection, rgb: Float64Array, source: Rect, region: Rect, cornerStops: number | null): Float64Array {\n const out = new Float64Array(region.width * region.height * 3), W = c.width, H = c.height, cx = W / 2 - 0.5, cy = H / 2 - 0.5, r2 = c.radius * c.radius;\n const b = cornerStops === null ? 0 : 1 - 2 ** (-cornerStops / 2), k1 = c.k1, lensK1 = c.lensK1;\n for (let y = 0, o = 0; y < region.height; y++) for (let x = 0; x < region.width; x++) for (let k = 0; k < 3; k++, o++) {\n // sourceOf, inlined (a call per sample is about 5 times slower here): the same arithmetic, so the region bounds hold.\n const a = c.fill * c.scales[k]!, px = (region.x + x + 0.5 - W / 2) * a, py = (H / 2 - (region.y + y) - 0.5) * a, f = 1 + k1 * (px * px + py * py) / r2;\n const su = px * f + W / 2 - 0.5, sv = H / 2 - 0.5 - py * f;\n const u = Math.min(W - 1, Math.max(0, su)), v = Math.min(H - 1, Math.max(0, sv));\n const i = Math.floor(u), j = Math.floor(v), dx = u - i, dy = v - j, i1 = dx === 0 ? i : i + 1, j1 = dy === 0 ? j : j + 1;\n if (i < source.x || j < source.y || i1 >= source.x + source.width || j1 >= source.y + source.height)\n throw new InvalidScientificResultError(\"Lens correction read outside the developed rectangle.\");\n const row = (j - source.y) * source.width, row1 = (j1 - source.y) * source.width, a0 = i - source.x, a1 = i1 - source.x;\n let value = (1 - dy) * ((1 - dx) * rgb[(row + a0) * 3 + k]! + dx * rgb[(row + a1) * 3 + k]!) + dy * ((1 - dx) * rgb[(row1 + a0) * 3 + k]! + dx * rgb[(row1 + a1) * 3 + k]!);\n if (b !== 0) {\n // ρ_s², then ρ_i from ρ_s = ρ_i(1 + k1ρ_i²) by Newton's method (monotonic over the frame; converged to rounding in 4 steps).\n const s2 = ((su - cx) * (su - cx) + (sv - cy) * (sv - cy)) / r2, rs = Math.sqrt(s2), e = 1 - b * s2;\n let ri = rs;\n for (let n = 0; n < 4; n++) ri -= (ri * (1 + lensK1 * ri * ri) - rs) / (1 + 3 * lensK1 * ri * ri);\n value *= (1 + lensK1 * ri * ri) * (1 + 3 * lensK1 * ri * ri) / (e * e);\n }\n out[o] = value;\n }\n return out;\n}\n"]}
@@ -1,6 +1,36 @@
1
+ /**
2
+ * The generic correction profile of a catalogue lens (#549), for the post-capture lens corrections
3
+ * (`resolveLensCorrectionPlan`, `calculateLensCorrectedCapture`): a distortion map, a lateral-colour map per channel and a
4
+ * peripheral-illumination gain, each from the lens's own character (`resolveGenericLens`). Consumer lenses rely on it more:
5
+ * their uncorrected distortion and vignetting are larger, so the same profile corrects more. Like every catalogue number it is
6
+ * a labelled approximation, bound to one exact camera state (`OpticalProfileState`).
7
+ */
8
+ import { type CalculationResult } from "../core/calculation-result.js";
1
9
  import { type OpticalProfileState } from "../optics/profile-contract.js";
2
10
  import { type GenericLensCorrectionProfile } from "./lens-corrections.js";
3
- export { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM } from "./developed-lens-corrections.js";
11
+ import { type ChannelDispersion } from "./developed-lens-corrections.js";
12
+ export { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM, type ChannelDispersion } from "./developed-lens-corrections.js";
13
+ export interface CalculateChannelEffectiveDispersionInput {
14
+ /** Spectral nodes, nm, strictly increasing, within 300–1100 nm. */
15
+ wavelengthsNm: readonly number[];
16
+ /** Each channel's response at the nodes (any common linear unit, ≥ 0; for example electrons per unit spectral radiance). */
17
+ channelResponse: {
18
+ red: readonly number[];
19
+ green: readonly number[];
20
+ blue: readonly number[];
21
+ };
22
+ /** The illuminant's relative spectral power at the nodes (≥ 0; the scale cancels). */
23
+ illuminant: readonly number[];
24
+ }
25
+ /**
26
+ * #589: each channel's effective dispersion for the catalogue lenses' lateral colour, D̄_c = Σ w·D(λ) / Σ w with
27
+ * w = response_c(λ)·illuminant(λ) at the nodes and D the normalised Cauchy dispersion (`genericLensDispersion`: 0 at the
28
+ * Fraunhofer d line, F minus C = 1). Under lateral colour L·r·D(λ) a neutral surface's image in channel c moves by L·r·D̄_c, so
29
+ * blue minus red is L·r·(D̄_blue − D̄_red). Pass the result as `channelDispersion` to `createGenericLensCorrectionProfile`.
30
+ * `simulateCapture`'s `output.lensCorrections` computes it from the capture's own channels and reports it in its assumption.
31
+ * An approximation: a weighted mean over a neutral surface; a colored surface weights the band differently.
32
+ */
33
+ export declare function calculateChannelEffectiveDispersion(input: CalculateChannelEffectiveDispersionInput): CalculationResult<ChannelDispersion>;
4
34
  export interface CreateGenericLensCorrectionProfileInput {
5
35
  /** A catalogue id (`GENERIC_LENS_IDS`); `state.lensId` must be the same id. */
6
36
  lensId: string;
@@ -11,12 +41,18 @@ export interface CreateGenericLensCorrectionProfileInput {
11
41
  };
12
42
  /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */
13
43
  state: OpticalProfileState;
44
+ /**
45
+ * #589: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light).
46
+ * Default: D at {@link GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM} (610, 540 and 465 nm), a generic approximation.
47
+ */
48
+ channelDispersion?: ChannelDispersion;
14
49
  }
15
50
  /**
16
51
  * Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
17
- * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
18
- * relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
19
- * (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
20
- * sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
52
+ * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D̄_channel (`channelDispersion`,
53
+ * by default D at the generic channel wavelengths), and the relative-illumination gain at the state's aperture (strength 1). From
54
+ * profile version 1.1.0 (#584) the vignetting component (version 1.1.0) carries the distortion's area factor
55
+ * (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's sensor point, so it flattens a flat-field capture. The
56
+ * result passes `parseGenericLensCorrectionProfile`.
21
57
  */
22
58
  export declare function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -6,23 +6,67 @@
6
6
  * their uncorrected distortion and vignetting are larger, so the same profile corrects more. Like every catalogue number it is
7
7
  * a labelled approximation, bound to one exact camera state (`OpticalProfileState`).
8
8
  */
9
+ import { approximationResult } from "../core/calculation-result.js";
9
10
  import { InvalidScientificInputError } from "../core/validation.js";
10
11
  import { requireInputKeys, requireInputObject } from "../core/record-validation.js";
11
12
  import { parseOpticalProfileState } from "../optics/profile-contract.js";
12
- import { GENERIC_LENS_EVIDENCE, genericLensDispersion, resolveGenericLens } from "../equipment/generic-lens-catalogue.js";
13
+ import { GENERIC_LENS_EVIDENCE, resolveGenericLens } from "../equipment/generic-lens-catalogue.js";
13
14
  import { parseGenericLensCorrectionProfile } from "./lens-corrections.js";
14
- import { genericLensChannelScale } from "./developed-lens-corrections.js";
15
+ import { channelEffectiveDispersion, genericLensChannelScale } from "./developed-lens-corrections.js";
15
16
  export { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM } from "./developed-lens-corrections.js";
17
+ /**
18
+ * #589: each channel's effective dispersion for the catalogue lenses' lateral colour, D̄_c = Σ w·D(λ) / Σ w with
19
+ * w = response_c(λ)·illuminant(λ) at the nodes and D the normalised Cauchy dispersion (`genericLensDispersion`: 0 at the
20
+ * Fraunhofer d line, F minus C = 1). Under lateral colour L·r·D(λ) a neutral surface's image in channel c moves by L·r·D̄_c, so
21
+ * blue minus red is L·r·(D̄_blue − D̄_red). Pass the result as `channelDispersion` to `createGenericLensCorrectionProfile`.
22
+ * `simulateCapture`'s `output.lensCorrections` computes it from the capture's own channels and reports it in its assumption.
23
+ * An approximation: a weighted mean over a neutral surface; a colored surface weights the band differently.
24
+ */
25
+ export function calculateChannelEffectiveDispersion(input) {
26
+ requireInputObject(input, "calculateChannelEffectiveDispersion input");
27
+ requireInputKeys(input, "calculateChannelEffectiveDispersion", ["wavelengthsNm", "channelResponse", "illuminant"]);
28
+ const nodes = input.wavelengthsNm, response = input.channelResponse;
29
+ if (!Array.isArray(nodes) || nodes.length === 0 || nodes.some((nm, n) => typeof nm !== "number" || !(nm >= 300 && nm <= 1100) || (n > 0 && !(nm > nodes[n - 1]))))
30
+ throw new InvalidScientificInputError("wavelengthsNm must be strictly increasing numbers within 300–1100 nm.");
31
+ requireInputObject(response, "channelResponse");
32
+ requireInputKeys(response, "channelResponse", ["red", "green", "blue"]);
33
+ const series = (values, name) => {
34
+ if (!Array.isArray(values) || values.length !== nodes.length || values.some((v) => typeof v !== "number" || !Number.isFinite(v) || v < 0))
35
+ throw new InvalidScientificInputError(name + " must hold one finite value ≥ 0 per wavelength node.");
36
+ return values;
37
+ };
38
+ const illuminant = series(input.illuminant, "illuminant"), channels = ["red", "green", "blue"].map((c) => series(response[c], "channelResponse." + c));
39
+ if (channels.some((r) => !(r.reduce((sum, v, n) => sum + v * illuminant[n], 0) > 0)))
40
+ throw new InvalidScientificInputError("Every channel needs a positive response under the illuminant.");
41
+ const value = channelEffectiveDispersion(nodes, channels, illuminant);
42
+ return approximationResult(value, "generic-lens-channel-effective-dispersion", "1.0.0", [
43
+ "D̄_c = Σ response_c·illuminant·D(λ) / Σ response_c·illuminant over the given nodes, for a neutral (spectrally flat) surface.",
44
+ "D(λ) is the catalogue lenses' normalised Cauchy dispersion (Fraunhofer d, F and C lines)."
45
+ ]);
46
+ }
16
47
  /**
17
48
  * Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
18
- * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
19
- * relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
20
- * (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
21
- * sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
49
+ * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D̄_channel (`channelDispersion`,
50
+ * by default D at the generic channel wavelengths), and the relative-illumination gain at the state's aperture (strength 1). From
51
+ * profile version 1.1.0 (#584) the vignetting component (version 1.1.0) carries the distortion's area factor
52
+ * (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's sensor point, so it flattens a flat-field capture. The
53
+ * result passes `parseGenericLensCorrectionProfile`.
22
54
  */
23
55
  export function createGenericLensCorrectionProfile(input) {
24
56
  requireInputObject(input, "createGenericLensCorrectionProfile input");
25
- requireInputKeys(input, "createGenericLensCorrectionProfile", ["lensId", "activeImagingArea", "state"]);
57
+ requireInputKeys(input, "createGenericLensCorrectionProfile", ["lensId", "activeImagingArea", "state", "channelDispersion"]);
58
+ let channels;
59
+ if (input.channelDispersion !== undefined) {
60
+ requireInputObject(input.channelDispersion, "channelDispersion");
61
+ requireInputKeys(input.channelDispersion, "channelDispersion", ["red", "green", "blue"]);
62
+ for (const c of ["red", "green", "blue"]) {
63
+ const v = input.channelDispersion[c];
64
+ // D spans −1.08 (1100 nm) to 4.30 (300 nm).
65
+ if (typeof v !== "number" || !(v >= -1.1 && v <= 4.4))
66
+ throw new InvalidScientificInputError("channelDispersion." + c + " must be a number in [−1.1, 4.4], D between 1100 and 300 nm.");
67
+ }
68
+ channels = input.channelDispersion;
69
+ }
26
70
  const state = parseOpticalProfileState(input.state);
27
71
  const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: state.aperture });
28
72
  if (state.lensId !== lens.lensId)
@@ -31,7 +75,7 @@ export function createGenericLensCorrectionProfile(input) {
31
75
  throw new InvalidScientificInputError("state.focalLengthMm must be the " + lens.name + "'s focal length on this sensor, " + lens.focalLengthMm + " mm.");
32
76
  const c = lens.character, domain = "reconstructed-linear";
33
77
  const identity = { version: "1.0.0", domain, availability: "toggle", defaultEnabled: true, dependencies: [], requiredByStabilizationModes: [] };
34
- const scale = (channel) => genericLensChannelScale(c.lateralColor.fraction, genericLensDispersion, channel);
78
+ const scale = (channel) => genericLensChannelScale(c.lateralColor.fraction, channel, channels);
35
79
  const affine = (channel) => ({ id: lens.lensId + ":lateral-ca:" + channel, version: "1.0.0", domain, purpose: "lateral-ca", kind: "affine",
36
80
  matrix: [scale(channel), 0, 0, scale(channel)], offsetMm: { x: 0, y: 0 } });
37
81
  return parseGenericLensCorrectionProfile({ schemaVersion: "0.1.0", id: "photivra-" + lens.lensId + ":corrections", version: "1.1.0", state,
@@ -1 +1 @@
1
- {"version":3,"file":"generic-lens-corrections.js","sourceRoot":"","sources":["../../src/output/generic-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The generic correction profile of a catalogue lens (#549), for the post-capture lens corrections\n * (`resolveLensCorrectionPlan`, `calculateLensCorrectedCapture`): a distortion map, a lateral-colour map per channel and a\n * peripheral-illumination gain, each from the lens's own character (`resolveGenericLens`). Consumer lenses rely on it more:\n * their uncorrected distortion and vignetting are larger, so the same profile corrects more. Like every catalogue number it is\n * a labelled approximation, bound to one exact camera state (`OpticalProfileState`).\n */\n\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { parseOpticalProfileState, type OpticalProfileState } from \"../optics/profile-contract.js\";\nimport { GENERIC_LENS_EVIDENCE, genericLensDispersion, resolveGenericLens } from \"../equipment/generic-lens-catalogue.js\";\nimport { parseGenericLensCorrectionProfile, type GenericLensCorrectionProfile, type LensCorrectionChannel } from \"./lens-corrections.js\";\nimport type { AffineGeometricTransform } from \"./geometric-transforms.js\";\nimport { genericLensChannelScale } from \"./developed-lens-corrections.js\";\n\nexport { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM } from \"./developed-lens-corrections.js\";\n\nexport interface CreateGenericLensCorrectionProfileInput {\n /** A catalogue id (`GENERIC_LENS_IDS`); `state.lensId` must be the same id. */\n lensId: string;\n /** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */\n activeImagingArea: { widthMm: number; heightMm: number };\n /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */\n state: OpticalProfileState;\n}\n\n/**\n * Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own\n * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the\n * relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component\n * (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's\n * sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.\n */\nexport function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile {\n requireInputObject(input, \"createGenericLensCorrectionProfile input\");\n requireInputKeys(input, \"createGenericLensCorrectionProfile\", [\"lensId\", \"activeImagingArea\", \"state\"]);\n const state = parseOpticalProfileState(input.state);\n const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: state.aperture });\n if (state.lensId !== lens.lensId) throw new InvalidScientificInputError(\"state.lensId must be the catalogue id \" + lens.lensId + \".\");\n if (Math.abs(state.focalLengthMm - lens.focalLengthMm) > 1e-9 * lens.focalLengthMm)\n throw new InvalidScientificInputError(\"state.focalLengthMm must be the \" + lens.name + \"'s focal length on this sensor, \" + lens.focalLengthMm + \" mm.\");\n const c = lens.character, domain = \"reconstructed-linear\" as const;\n const identity = { version: \"1.0.0\", domain, availability: \"toggle\" as const, defaultEnabled: true, dependencies: [] as string[], requiredByStabilizationModes: [] as string[] };\n const scale = (channel: LensCorrectionChannel): number => genericLensChannelScale(c.lateralColor.fraction, genericLensDispersion, channel);\n const affine = (channel: LensCorrectionChannel): AffineGeometricTransform => ({ id: lens.lensId + \":lateral-ca:\" + channel, version: \"1.0.0\", domain, purpose: \"lateral-ca\" as const, kind: \"affine\" as const,\n matrix: [scale(channel), 0, 0, scale(channel)] as [number, number, number, number], offsetMm: { x: 0, y: 0 } });\n return parseGenericLensCorrectionProfile({ schemaVersion: \"0.1.0\", id: \"photivra-\" + lens.lensId + \":corrections\", version: \"1.1.0\", state,\n evidence: { kind: \"generic-parametric\", basis: \"Generic catalogue lens \" + lens.name + \" (#549): the inverse of its own distortion, lateral colour and vignetting.\",\n residualNote: \"Generic approximation, not a measured lens; axial colour, coma and the other aberrations stay.\", sources: [...GENERIC_LENS_EVIDENCE] },\n components: [\n { ...identity, id: \"distortion\", kind: \"geometry\", residualNote: \"Corrects the lens's radial distortion r_d = r(1 + k1ρ²); the stretched corners lose resolution.\",\n transform: { id: lens.lensId + \":distortion\", version: \"1.0.0\", domain, purpose: \"distortion\", kind: \"radial\",\n profile: { normalizationRadiusMm: lens.halfDiagonalMm, maximumNormalizedRadius: 1.05, coefficients: { k1: c.distortion.corner, k2: 0, k3: 0 } } } },\n { ...identity, id: \"lateral-ca\", kind: \"lateral-ca\", residualNote: \"Registers the channels at one effective wavelength each; colour inside a channel's band and axial colour stay.\",\n transforms: { red: affine(\"red\"), green: affine(\"green\"), blue: affine(\"blue\") } },\n // #584 (component 1.1.0): the gain also undoes the distortion's area factor, so a flat field comes out flat.\n { ...identity, version: \"1.1.0\", id: \"vignetting\", kind: \"peripheral-illumination\",\n residualNote: \"Gain det J/E at the capture's sensor point and the profile's aperture (E the lens's relative illumination, 1/det J its distortion's area factor); it raises corner noise with the signal.\",\n profile: { ...c.vignetting.profile, maximumNormalizedRadius: 1.05 }, strength: 1, distortionAreaFactor: { ...c.distortion.profile, coefficients: { ...c.distortion.profile.coefficients } } }\n ] });\n}\n"]}
1
+ {"version":3,"file":"generic-lens-corrections.js","sourceRoot":"","sources":["../../src/output/generic-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The generic correction profile of a catalogue lens (#549), for the post-capture lens corrections\n * (`resolveLensCorrectionPlan`, `calculateLensCorrectedCapture`): a distortion map, a lateral-colour map per channel and a\n * peripheral-illumination gain, each from the lens's own character (`resolveGenericLens`). Consumer lenses rely on it more:\n * their uncorrected distortion and vignetting are larger, so the same profile corrects more. Like every catalogue number it is\n * a labelled approximation, bound to one exact camera state (`OpticalProfileState`).\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { parseOpticalProfileState, type OpticalProfileState } from \"../optics/profile-contract.js\";\nimport { GENERIC_LENS_EVIDENCE, resolveGenericLens } from \"../equipment/generic-lens-catalogue.js\";\nimport { parseGenericLensCorrectionProfile, type GenericLensCorrectionProfile, type LensCorrectionChannel } from \"./lens-corrections.js\";\nimport type { AffineGeometricTransform } from \"./geometric-transforms.js\";\nimport { channelEffectiveDispersion, genericLensChannelScale, type ChannelDispersion } from \"./developed-lens-corrections.js\";\n\nexport { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM, type ChannelDispersion } from \"./developed-lens-corrections.js\";\n\nexport interface CalculateChannelEffectiveDispersionInput {\n /** Spectral nodes, nm, strictly increasing, within 300–1100 nm. */\n wavelengthsNm: readonly number[];\n /** Each channel's response at the nodes (any common linear unit, ≥ 0; for example electrons per unit spectral radiance). */\n channelResponse: { red: readonly number[]; green: readonly number[]; blue: readonly number[] };\n /** The illuminant's relative spectral power at the nodes (≥ 0; the scale cancels). */\n illuminant: readonly number[];\n}\n\n/**\n * #589: each channel's effective dispersion for the catalogue lenses' lateral colour, D̄_c = Σ w·D(λ) / Σ w with\n * w = response_c(λ)·illuminant(λ) at the nodes and D the normalised Cauchy dispersion (`genericLensDispersion`: 0 at the\n * Fraunhofer d line, F minus C = 1). Under lateral colour L·r·D(λ) a neutral surface's image in channel c moves by L·r·D̄_c, so\n * blue minus red is L·r·(D̄_blue − D̄_red). Pass the result as `channelDispersion` to `createGenericLensCorrectionProfile`.\n * `simulateCapture`'s `output.lensCorrections` computes it from the capture's own channels and reports it in its assumption.\n * An approximation: a weighted mean over a neutral surface; a colored surface weights the band differently.\n */\nexport function calculateChannelEffectiveDispersion(input: CalculateChannelEffectiveDispersionInput): CalculationResult<ChannelDispersion> {\n requireInputObject(input, \"calculateChannelEffectiveDispersion input\");\n requireInputKeys(input, \"calculateChannelEffectiveDispersion\", [\"wavelengthsNm\", \"channelResponse\", \"illuminant\"]);\n const nodes = input.wavelengthsNm, response = input.channelResponse;\n if (!Array.isArray(nodes) || nodes.length === 0 || nodes.some((nm, n) => typeof nm !== \"number\" || !(nm >= 300 && nm <= 1100) || (n > 0 && !(nm > nodes[n - 1]!))))\n throw new InvalidScientificInputError(\"wavelengthsNm must be strictly increasing numbers within 300–1100 nm.\");\n requireInputObject(response, \"channelResponse\");\n requireInputKeys(response, \"channelResponse\", [\"red\", \"green\", \"blue\"]);\n const series = (values: unknown, name: string): readonly number[] => {\n if (!Array.isArray(values) || values.length !== nodes.length || values.some((v) => typeof v !== \"number\" || !Number.isFinite(v) || v < 0))\n throw new InvalidScientificInputError(name + \" must hold one finite value ≥ 0 per wavelength node.\");\n return values as number[];\n };\n const illuminant = series(input.illuminant, \"illuminant\"), channels = ([\"red\", \"green\", \"blue\"] as const).map((c) => series(response[c], \"channelResponse.\" + c));\n if (channels.some((r) => !(r.reduce((sum, v, n) => sum + v * illuminant[n]!, 0) > 0))) throw new InvalidScientificInputError(\"Every channel needs a positive response under the illuminant.\");\n const value = channelEffectiveDispersion(nodes, channels, illuminant);\n return approximationResult(value, \"generic-lens-channel-effective-dispersion\", \"1.0.0\", [\n \"D̄_c = Σ response_c·illuminant·D(λ) / Σ response_c·illuminant over the given nodes, for a neutral (spectrally flat) surface.\",\n \"D(λ) is the catalogue lenses' normalised Cauchy dispersion (Fraunhofer d, F and C lines).\"]);\n}\n\nexport interface CreateGenericLensCorrectionProfileInput {\n /** A catalogue id (`GENERIC_LENS_IDS`); `state.lensId` must be the same id. */\n lensId: string;\n /** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */\n activeImagingArea: { widthMm: number; heightMm: number };\n /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */\n state: OpticalProfileState;\n /**\n * #589: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light).\n * Default: D at {@link GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM} (610, 540 and 465 nm), a generic approximation.\n */\n channelDispersion?: ChannelDispersion;\n}\n\n/**\n * Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own\n * r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D̄_channel (`channelDispersion`,\n * by default D at the generic channel wavelengths), and the relative-illumination gain at the state's aperture (strength 1). From\n * profile version 1.1.0 (#584) the vignetting component (version 1.1.0) carries the distortion's area factor\n * (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's sensor point, so it flattens a flat-field capture. The\n * result passes `parseGenericLensCorrectionProfile`.\n */\nexport function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile {\n requireInputObject(input, \"createGenericLensCorrectionProfile input\");\n requireInputKeys(input, \"createGenericLensCorrectionProfile\", [\"lensId\", \"activeImagingArea\", \"state\", \"channelDispersion\"]);\n let channels: ChannelDispersion | undefined;\n if (input.channelDispersion !== undefined) {\n requireInputObject(input.channelDispersion, \"channelDispersion\");\n requireInputKeys(input.channelDispersion, \"channelDispersion\", [\"red\", \"green\", \"blue\"]);\n for (const c of [\"red\", \"green\", \"blue\"] as const) {\n const v = input.channelDispersion[c];\n // D spans −1.08 (1100 nm) to 4.30 (300 nm).\n if (typeof v !== \"number\" || !(v >= -1.1 && v <= 4.4)) throw new InvalidScientificInputError(\"channelDispersion.\" + c + \" must be a number in [−1.1, 4.4], D between 1100 and 300 nm.\");\n }\n channels = input.channelDispersion;\n }\n const state = parseOpticalProfileState(input.state);\n const lens = resolveGenericLens({ lensId: input.lensId, activeImagingArea: input.activeImagingArea, fNumber: state.aperture });\n if (state.lensId !== lens.lensId) throw new InvalidScientificInputError(\"state.lensId must be the catalogue id \" + lens.lensId + \".\");\n if (Math.abs(state.focalLengthMm - lens.focalLengthMm) > 1e-9 * lens.focalLengthMm)\n throw new InvalidScientificInputError(\"state.focalLengthMm must be the \" + lens.name + \"'s focal length on this sensor, \" + lens.focalLengthMm + \" mm.\");\n const c = lens.character, domain = \"reconstructed-linear\" as const;\n const identity = { version: \"1.0.0\", domain, availability: \"toggle\" as const, defaultEnabled: true, dependencies: [] as string[], requiredByStabilizationModes: [] as string[] };\n const scale = (channel: LensCorrectionChannel): number => genericLensChannelScale(c.lateralColor.fraction, channel, channels);\n const affine = (channel: LensCorrectionChannel): AffineGeometricTransform => ({ id: lens.lensId + \":lateral-ca:\" + channel, version: \"1.0.0\", domain, purpose: \"lateral-ca\" as const, kind: \"affine\" as const,\n matrix: [scale(channel), 0, 0, scale(channel)] as [number, number, number, number], offsetMm: { x: 0, y: 0 } });\n return parseGenericLensCorrectionProfile({ schemaVersion: \"0.1.0\", id: \"photivra-\" + lens.lensId + \":corrections\", version: \"1.1.0\", state,\n evidence: { kind: \"generic-parametric\", basis: \"Generic catalogue lens \" + lens.name + \" (#549): the inverse of its own distortion, lateral colour and vignetting.\",\n residualNote: \"Generic approximation, not a measured lens; axial colour, coma and the other aberrations stay.\", sources: [...GENERIC_LENS_EVIDENCE] },\n components: [\n { ...identity, id: \"distortion\", kind: \"geometry\", residualNote: \"Corrects the lens's radial distortion r_d = r(1 + k1ρ²); the stretched corners lose resolution.\",\n transform: { id: lens.lensId + \":distortion\", version: \"1.0.0\", domain, purpose: \"distortion\", kind: \"radial\",\n profile: { normalizationRadiusMm: lens.halfDiagonalMm, maximumNormalizedRadius: 1.05, coefficients: { k1: c.distortion.corner, k2: 0, k3: 0 } } } },\n { ...identity, id: \"lateral-ca\", kind: \"lateral-ca\", residualNote: \"Registers the channels at one effective wavelength each; colour inside a channel's band and axial colour stay.\",\n transforms: { red: affine(\"red\"), green: affine(\"green\"), blue: affine(\"blue\") } },\n // #584 (component 1.1.0): the gain also undoes the distortion's area factor, so a flat field comes out flat.\n { ...identity, version: \"1.1.0\", id: \"vignetting\", kind: \"peripheral-illumination\",\n residualNote: \"Gain det J/E at the capture's sensor point and the profile's aperture (E the lens's relative illumination, 1/det J its distortion's area factor); it raises corner noise with the signal.\",\n profile: { ...c.vignetting.profile, maximumNormalizedRadius: 1.05 }, strength: 1, distortionAreaFactor: { ...c.distortion.profile, coefficients: { ...c.distortion.profile.coefficients } } }\n ] });\n}\n"]}
@@ -10,7 +10,9 @@ export interface PrintLength {
10
10
  value: number;
11
11
  unit: "mm" | "cm" | "inches" | "m";
12
12
  }
13
- /** The flat printed image area. It excludes paper margins, borders and canvas wraps. */
13
+ /** The flat printed image area. It excludes paper margins, borders and canvas wraps.
14
+ * With a `contain` fit it is instead the paper, or the printable area of it, that the image is fitted inside.
15
+ */
14
16
  export interface PrintedImageSize {
15
17
  width: number;
16
18
  height: number;
@@ -48,16 +50,43 @@ export interface PrintProviderConstraints {
48
50
  minimumPpi?: number;
49
51
  exactPpi?: number;
50
52
  }
53
+ /** How the printed image relates to `printedImage`.
54
+ * - `confirmed-native-aspect`: `printedImage` is the printed image itself and must already have the native aspect ratio,
55
+ * within the stated relative error.
56
+ * - `contain`: `printedImage` is the paper. The image keeps the native aspect ratio and is scaled to the largest size that
57
+ * fits inside it, centered; the rest is unprinted border. `match-image` turns the paper a quarter turn when its
58
+ * orientation (portrait or landscape) differs from the image's; `as-given` keeps it. Layout only: nothing is cropped or resampled.
59
+ */
60
+ export type PrintFit = {
61
+ kind: "confirmed-native-aspect";
62
+ maximumRelativeAspectError: number;
63
+ } | {
64
+ kind: "contain";
65
+ paperOrientation: "as-given" | "match-image";
66
+ };
67
+ /** The layout of a `contain` fit, in millimetres. The printed image itself is the plan's `printedImageMm`. */
68
+ export interface PrintContainLayout {
69
+ /** The paper as placed: after the quarter turn when `paperRotated`. */
70
+ paperMm: {
71
+ width: number;
72
+ height: number;
73
+ };
74
+ paperRotated: boolean;
75
+ /** Unprinted border on each side of the centered image; zero when the paper has the image's aspect ratio. */
76
+ bordersMm: {
77
+ left: number;
78
+ right: number;
79
+ top: number;
80
+ bottom: number;
81
+ };
82
+ }
51
83
  /** Plan input. The output keeps the exact native aspect ratio; the allowed physical aspect error is explicit and reported. */
52
84
  export interface PrintPlanInput {
53
85
  source: PrintNativeSource;
54
86
  printedImage: PrintedImageSize;
55
87
  viewingDistance: PrintLength;
56
88
  sampling: PrintSamplingCriterion;
57
- fit: {
58
- kind: "confirmed-native-aspect";
59
- maximumRelativeAspectError: number;
60
- };
89
+ fit: PrintFit;
61
90
  provider?: PrintProviderConstraints;
62
91
  }
63
92
  /** Pixel density and angular pixel pitch for each axis. The angles are pure geometry, not a measure of visual acuity. */
@@ -106,6 +135,8 @@ export interface PrintPlan {
106
135
  } | null;
107
136
  capturedDetailAssessment: "unassessed";
108
137
  deliveryAssessment: "unassessed";
138
+ /** Present only with a `contain` fit; null when the native raster is unknown. */
139
+ contain?: PrintContainLayout | null;
109
140
  }
110
141
  /** Validates and copies an untrusted print request. It never assumes the source is native if that is not declared. */
111
142
  export declare function parsePrintPlanInput(value: unknown): PrintPlanInput;
@@ -135,6 +166,11 @@ export interface PrintSizeLimit {
135
166
  } | null;
136
167
  reasons: string[];
137
168
  referencePlan: PrintPlan;
169
+ /** Present only with a `contain` fit: the paper that holds `maximumImageMm` with the same layout (borders scale with it). */
170
+ maximumPaperMm?: {
171
+ width: number;
172
+ height: number;
173
+ } | null;
138
174
  }
139
175
  /** Calculates the largest print size under the stated conditions. Lab constraints need individual plans,
140
176
  * so this size query reports them as unsupported rather than ignoring them.
@@ -118,10 +118,7 @@ export function parsePrintPlanInput(value) {
118
118
  else
119
119
  throw new InvalidConfigurationError("Invalid native source kind.");
120
120
  const image = record(r.printedImage, ["width", "height", "unit"]);
121
- const fit = record(r.fit, ["kind", "maximumRelativeAspectError"]);
122
- if (fit.kind !== "confirmed-native-aspect" || typeof fit.maximumRelativeAspectError !== "number" || !Number.isFinite(fit.maximumRelativeAspectError) || fit.maximumRelativeAspectError < 0 || fit.maximumRelativeAspectError > 0.01)
123
- throw new InvalidConfigurationError("Declare a native-aspect fit tolerance in [0, 0.01].");
124
- const parsed = { source: parsedSource, printedImage: { width: number(image.width, "printedImage.width"), height: number(image.height, "printedImage.height"), unit: unit(image.unit) }, viewingDistance: length(r.viewingDistance), sampling: sampling(r.sampling), fit: { kind: "confirmed-native-aspect", maximumRelativeAspectError: fit.maximumRelativeAspectError } };
121
+ const parsed = { source: parsedSource, printedImage: { width: number(image.width, "printedImage.width"), height: number(image.height, "printedImage.height"), unit: unit(image.unit) }, viewingDistance: length(r.viewingDistance), sampling: sampling(r.sampling), fit: parseFit(r.fit) };
125
122
  if (r.provider !== undefined) {
126
123
  const p = record(r.provider, ["minimumRaster", "exactRaster", "minimumPpi", "exactPpi"]);
127
124
  parsed.provider = {
@@ -133,6 +130,30 @@ export function parsePrintPlanInput(value) {
133
130
  }
134
131
  return parsed;
135
132
  }
133
+ function parseFit(value) {
134
+ const fit = record(value, ["kind", "maximumRelativeAspectError", "paperOrientation"]);
135
+ if (fit.kind === "contain") {
136
+ record(value, ["kind", "paperOrientation"]);
137
+ if (fit.paperOrientation !== "as-given" && fit.paperOrientation !== "match-image")
138
+ throw new InvalidConfigurationError("Declare a contain-fit paper orientation: as-given or match-image.");
139
+ return { kind: "contain", paperOrientation: fit.paperOrientation };
140
+ }
141
+ if (fit.kind !== "confirmed-native-aspect" || fit.paperOrientation !== undefined || typeof fit.maximumRelativeAspectError !== "number" || !Number.isFinite(fit.maximumRelativeAspectError) || fit.maximumRelativeAspectError < 0 || fit.maximumRelativeAspectError > 0.01)
142
+ throw new InvalidConfigurationError("Declare a native-aspect fit tolerance in [0, 0.01].");
143
+ return { kind: "confirmed-native-aspect", maximumRelativeAspectError: fit.maximumRelativeAspectError };
144
+ }
145
+ /** The largest image at the native aspect ratio inside the paper, in the paper's unit, and its centered layout.
146
+ * The binding axis equals the paper exactly; the other is clamped so rounding never makes a negative border.
147
+ */
148
+ function containImage(native, paperWidth, paperHeight, orientation) {
149
+ const imageLandscape = native.pixelWidth > native.pixelHeight, imagePortrait = native.pixelWidth < native.pixelHeight;
150
+ const paperRotated = orientation === "match-image" && ((imageLandscape && paperWidth < paperHeight) || (imagePortrait && paperWidth > paperHeight));
151
+ const pw = paperRotated ? paperHeight : paperWidth, ph = paperRotated ? paperWidth : paperHeight;
152
+ const widthBinds = native.pixelWidth * ph >= native.pixelHeight * pw;
153
+ const width = widthBinds ? pw : Math.min(pw, ph * native.pixelWidth / native.pixelHeight);
154
+ const height = widthBinds ? Math.min(ph, pw * native.pixelHeight / native.pixelWidth) : ph;
155
+ return { width, height, paperWidth: pw, paperHeight: ph, paperRotated };
156
+ }
136
157
  function gcd(a, b) { while (b !== 0) {
137
158
  const next = a % b;
138
159
  a = b;
@@ -171,12 +192,22 @@ function diagnostics(raster, width, height, distance) {
171
192
  export function calculatePrintPlan(value) {
172
193
  const input = parsePrintPlanInput(value);
173
194
  const plan = { engineApiVersion: ENGINE_API_VERSION, input, status: "cannot-assess", nativePixelSufficiency: "cannot-assess", reasons: [], printedImageMm: null, viewingDistanceMm: null, guidance: null, relativeAspectError: null, native: null, minimumSamplingRaster: null, recommended: null, resamplingScale: null, alternatives: null, capturedDetailAssessment: "unassessed", deliveryAssessment: "unassessed" };
174
- const finish = () => calculatedResult(plan, "native-only-print-plan", "0.1.0", ["Declared native authority; flat image, normal viewing at image center.", "Output preserves the exact retained native aspect ratio; no upscale.", "Angular sampling is conditional guidance, not captured-detail or print-quality certification."]);
195
+ const contain = input.fit.kind === "contain" ? input.fit : null;
196
+ if (contain !== null)
197
+ plan.contain = null;
198
+ const finish = () => calculatedResult(plan, "native-only-print-plan", "0.1.0", ["Declared native authority; flat image, normal viewing at image center.", "Output preserves the exact retained native aspect ratio; no upscale.", "Angular sampling is conditional guidance, not captured-detail or print-quality certification.", ...(contain === null ? [] : ["Contain fit: the image is centered inside the paper; the borders are unprinted and the paper is not a print area."])]);
175
199
  const unsupported = () => { plan.status = "unsupported"; plan.nativePixelSufficiency = "cannot-assess"; plan.reasons.push("unsupported-numeric-range"); return finish(); };
176
- const width = input.printedImage.width * UNIT_MM[input.printedImage.unit], height = input.printedImage.height * UNIT_MM[input.printedImage.unit], distance = input.viewingDistance.value * UNIT_MM[input.viewingDistance.unit];
200
+ const unitMm = UNIT_MM[input.printedImage.unit], distance = input.viewingDistance.value * UNIT_MM[input.viewingDistance.unit];
201
+ // A contain fit needs the native aspect ratio before the printed image size is known.
202
+ const resolved = input.source.kind === "native-retained" ? resolveCaptureGeometry(input.source.geometry).value : null;
203
+ const native = resolved === null ? null : { pixelWidth: resolved.output.cropRect.width, pixelHeight: resolved.output.cropRect.height };
204
+ const fitted = contain === null || native === null ? null : containImage(native, input.printedImage.width, input.printedImage.height, contain.paperOrientation);
205
+ const imageWidth = fitted?.width ?? input.printedImage.width, imageHeight = fitted?.height ?? input.printedImage.height;
206
+ const width = imageWidth * unitMm, height = imageHeight * unitMm;
177
207
  if (!positiveSupported(width, height, distance))
178
208
  return unsupported();
179
- plan.printedImageMm = { width, height };
209
+ if (contain === null || fitted !== null)
210
+ plan.printedImageMm = { width, height };
180
211
  plan.viewingDistanceMm = distance;
181
212
  const angle = input.sampling.kind === "manual-ppi" ? null : angularLimit(input.sampling);
182
213
  const pitchFactor = angle === null ? null : 2 * Math.tan((angle * RAD_PER_ARCMIN) / 2);
@@ -186,12 +217,20 @@ export function calculatePrintPlan(value) {
186
217
  return unsupported();
187
218
  const guidance = { derivedPixelsPerInch: ppi, maximumPixelPitchMm: pitch, maximumArcminutesPerPixel: angle };
188
219
  plan.guidance = angle === null ? calculatedResult(guidance, "manual-print-density", "0.1.0", ["Explicit manual PPI plan; no angular criterion is certified."]) : approximationResult(guidance, "declared-angular-print-sampling", "0.1.0", ["Exact centered pixel angle uses 2 atan(pitch / (2 distance)); no small-angle substitution.", "Declared angular sampling convention is an uncalibrated usage assumption."]);
189
- if (input.source.kind === "unavailable") {
190
- plan.reasons.push(input.source.reason);
220
+ if (input.source.kind === "unavailable" || native === null) {
221
+ if (input.source.kind === "unavailable")
222
+ plan.reasons.push(input.source.reason);
191
223
  return finish();
192
224
  }
193
- const resolved = resolveCaptureGeometry(input.source.geometry).value;
194
- const native = { pixelWidth: resolved.output.cropRect.width, pixelHeight: resolved.output.cropRect.height };
225
+ if (fitted !== null) {
226
+ const paperWidth = fitted.paperWidth * unitMm, paperHeight = fitted.paperHeight * unitMm;
227
+ // Borders are taken in the paper's unit, then converted, so whole-unit borders stay exact.
228
+ const side = (paper, image) => Math.max(0, (paper - image) / 2) * unitMm;
229
+ const x = side(fitted.paperWidth, imageWidth), y = side(fitted.paperHeight, imageHeight);
230
+ if (!positiveSupported(paperWidth, paperHeight) || !Number.isFinite(x) || !Number.isFinite(y))
231
+ return unsupported();
232
+ plan.contain = { paperMm: { width: paperWidth, height: paperHeight }, paperRotated: fitted.paperRotated, bordersMm: { left: x, right: x, top: y, bottom: y } };
233
+ }
195
234
  const aspectError = Math.abs((width / height) / (native.pixelWidth / native.pixelHeight) - 1);
196
235
  if (!Number.isFinite(aspectError))
197
236
  return unsupported();
@@ -200,13 +239,14 @@ export function calculatePrintPlan(value) {
200
239
  if (!positiveSupported(nativeDiagnostics.pixelsPerInch.x, nativeDiagnostics.pixelsPerInch.y, nativeDiagnostics.angularPixelPitchArcminutes.x, nativeDiagnostics.angularPixelPitchArcminutes.y))
201
240
  return unsupported();
202
241
  plan.native = nativeDiagnostics;
203
- if (aspectError > input.fit.maximumRelativeAspectError) {
242
+ // A contain fit has the native aspect ratio by construction; its rounding error is reported, not checked.
243
+ if (input.fit.kind === "confirmed-native-aspect" && aspectError > input.fit.maximumRelativeAspectError) {
204
244
  plan.status = "crop-confirmation-required";
205
245
  plan.reasons.push("printed-image-and-confirmed-crop-aspect-mismatch");
206
246
  return finish();
207
247
  }
208
- const widthInches = input.printedImage.unit === "inches" ? input.printedImage.width : width / 25.4;
209
- const heightInches = input.printedImage.unit === "inches" ? input.printedImage.height : height / 25.4;
248
+ const widthInches = input.printedImage.unit === "inches" ? imageWidth : width / 25.4;
249
+ const heightInches = input.printedImage.unit === "inches" ? imageHeight : height / 25.4;
210
250
  const required = input.sampling.kind === "manual-ppi"
211
251
  ? minimumRaster(native, widthInches * ppi, heightInches * ppi)
212
252
  : minimumRaster(native, width / pitch, height / pitch);
@@ -308,6 +348,11 @@ export function calculatePrintSizeLimit(value) {
308
348
  result.maximumImageMm = referencePlan.alternatives.maximumImageMmAtCurrentDistance;
309
349
  }
310
350
  }
351
+ if (referencePlan.input.fit.kind === "contain") {
352
+ const layout = referencePlan.contain, image = referencePlan.printedImageMm, maximum = result.maximumImageMm;
353
+ const scale = layout && image && maximum ? maximum.width / image.width : null;
354
+ result.maximumPaperMm = layout && scale !== null && positiveSupported(layout.paperMm.width * scale, layout.paperMm.height * scale) ? { width: layout.paperMm.width * scale, height: layout.paperMm.height * scale } : null;
355
+ }
311
356
  return calculatedResult(result, "conditional-native-print-size-limit", "0.1.0", ["Native sampling capacity only; no perceived-quality or physical-printer bound.", "Proportional distance and image scaling preserve angular pixel pitch."]);
312
357
  }
313
358
  //# sourceMappingURL=print-plan.js.map