@photivra/engine 2.12.0 → 2.14.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.
- package/CHANGELOG.md +144 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -7
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +3 -0
- package/dist/camera/lens-projection.js +2 -1
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/capture/dng-import.js +1 -1
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/export-jpeg.d.ts +1 -1
- package/dist/capture/export-jpeg.js +6 -4
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/host-yield-cadence.js +1 -1
- package/dist/capture/host-yield-cadence.js.map +1 -1
- package/dist/capture/native-environment-optical-stage.js +1 -1
- package/dist/capture/native-environment-optical-stage.js.map +1 -1
- package/dist/capture/native-raw.js +1 -1
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.js +3 -3
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.js +1 -1
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/color/capture-color.js +3 -3
- package/dist/color/capture-color.js.map +1 -1
- package/dist/color/chromatic-adaptation.js +2 -2
- package/dist/color/chromatic-adaptation.js.map +1 -1
- package/dist/color/color-matching-functions.js +1 -1
- package/dist/color/color-matching-functions.js.map +1 -1
- package/dist/color/colorimetry-math.js +2 -2
- package/dist/color/colorimetry-math.js.map +1 -1
- package/dist/color/illuminants.js +3 -3
- package/dist/color/illuminants.js.map +1 -1
- package/dist/color/rgb-color-spaces.js +1 -1
- package/dist/color/rgb-color-spaces.js.map +1 -1
- package/dist/color/sensor-color-development.js +1 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/core/record-validation.js +3 -3
- package/dist/core/record-validation.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-prime-lenses.js +3 -3
- package/dist/equipment/generic-prime-lenses.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +23 -1
- package/dist/equipment/generic-tier-assets.js +58 -30
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.d.ts +6 -3
- package/dist/equipment/generic-tier-presets.js +26 -13
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/exposure/auto-iso-slowest-shutter.d.ts +120 -0
- package/dist/exposure/auto-iso-slowest-shutter.js +217 -0
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -0
- package/dist/exposure/exposure-mode-resolver.d.ts +57 -1
- package/dist/exposure/exposure-mode-resolver.js +238 -33
- package/dist/exposure/exposure-mode-resolver.js.map +1 -1
- package/dist/exposure/flash-ttl.js +1 -1
- package/dist/exposure/flash-ttl.js.map +1 -1
- package/dist/exposure/flash.js +1 -1
- package/dist/exposure/flash.js.map +1 -1
- package/dist/facade/chart-material-layers.js +2 -2
- package/dist/facade/chart-material-layers.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +1 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/chart-scenes.d.ts +45 -8
- package/dist/facade/chart-scenes.js +101 -44
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/color-patches.d.ts +129 -0
- package/dist/facade/color-patches.js +209 -0
- package/dist/facade/color-patches.js.map +1 -0
- package/dist/facade/diffraction-provider.js +12 -13
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu.js +1 -1
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view.d.ts +27 -0
- package/dist/facade/display-view.js +234 -65
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/scene-radiance.js +2 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +139 -12
- package/dist/facade/simulate-capture.js +209 -36
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -2
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.js +2 -2
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.js +1 -1
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -1
- package/dist/gpu/display-view-quality-gpu.js +24 -14
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-execution.js +2 -2
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/temporal-sum-gpu.js +23 -23
- package/dist/gpu/webgpu-runtime.js +1 -1
- package/dist/gpu/webgpu-runtime.js.map +1 -1
- package/dist/gpu/wgsl-chart-composite-kernel.js +318 -318
- package/dist/gpu/wgsl-chart-composite-kernel.js.map +1 -1
- package/dist/gpu/wgsl-chart-kernel.js +117 -117
- package/dist/gpu/wgsl-chart-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +13 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.js +522 -488
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js +248 -248
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-kernels.js +203 -203
- package/dist/gpu/wgsl-kernels.js.map +1 -1
- package/dist/gpu/wgsl-sensor-kernel.js +302 -302
- package/dist/gpu/wgsl-sensor-kernel.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.js +1 -1
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.js +4 -4
- package/dist/optics/diffraction-psf-profile.js.map +1 -1
- package/dist/optics/front-of-lens-filter.js +1 -1
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/irradiance-convolution.d.ts +10 -0
- package/dist/optics/irradiance-convolution.js +49 -14
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.js +1 -1
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.js +2 -2
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-jpeg.js +13 -10
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/output/text-metadata.d.ts +22 -2
- package/dist/output/text-metadata.js +58 -9
- package/dist/output/text-metadata.js.map +1 -1
- package/dist/sensor/fixed-pattern-noise.js +1 -1
- package/dist/sensor/fixed-pattern-noise.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.js +1 -1
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/docs/API_REFERENCE.md +49 -1
- package/docs/QUICK_START.md +104 -9
- package/docs/USAGE.md +241 -2
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-tier-assets.md +41 -1
- package/docs/api/equipment-generic-tier-presets.md +11 -3
- package/docs/api/exports.json +172 -2
- package/docs/api/exposure-auto-iso-slowest-shutter.md +139 -0
- package/docs/api/exposure-exposure-mode-resolver.md +68 -0
- package/docs/api/facade-chart-scenes.md +36 -5
- package/docs/api/facade-color-patches.md +150 -0
- package/docs/api/facade-simulate-capture.md +140 -9
- package/docs/api/output-text-metadata.md +34 -0
- package/llms.txt +1 -0
- package/package.json +2 -2
package/docs/USAGE.md
CHANGED
|
@@ -5089,10 +5089,72 @@ The limiting constraint is reported as one of:
|
|
|
5089
5089
|
- `none`;
|
|
5090
5090
|
- `iso-minimum`;
|
|
5091
5091
|
- `iso-maximum`;
|
|
5092
|
+
- `user-iso-minimum` and `user-iso-maximum` (#505, [photographer-set limits](#photographer-set-auto-iso-limits-505));
|
|
5092
5093
|
- `iso-grid-quantization`.
|
|
5093
5094
|
|
|
5094
5095
|
If Auto ISO capability is `unsupported` or `unknown`, the resolver returns a blocked result. Unknown is not treated as supported.
|
|
5095
5096
|
|
|
5097
|
+
### Photographer-set Auto ISO limits (#505)
|
|
5098
|
+
|
|
5099
|
+
Cameras let the photographer cap Auto ISO, and some also raise its floor: the Nikon D850 has a maximum sensitivity
|
|
5100
|
+
([Auto ISO control](https://imaging.nikon.com/support/digitutor/d850/functions/autoisocontrol.html)), Fujifilm a default and a
|
|
5101
|
+
maximum sensitivity per preset ([ISO Auto setting](https://fujifilm-dsc.com/en/manual/x-pro2/menu_shooting/iso_auto_setting/index.html)),
|
|
5102
|
+
and Panasonic separate upper and lower Auto ISO limits
|
|
5103
|
+
([GH5 II manual](https://eww.pavc.panasonic.co.jp/dscoi/DC-GH5M2/html/DC-GH5M2_DVQP2464_eng/0062.html)). That is the
|
|
5104
|
+
photographer's choice, so the engine keeps it apart from what the body allows (`capabilities.iso.autoIsoMinimum` and
|
|
5105
|
+
`autoIsoMaximum`, or the ISO range when the body declares none).
|
|
5106
|
+
|
|
5107
|
+
Every Auto ISO entry point takes an optional `autoIsoLimits: { maximum, minimum? }` (`AutoIsoLimits`):
|
|
5108
|
+
|
|
5109
|
+
| Resolver | Where |
|
|
5110
|
+
| --- | --- |
|
|
5111
|
+
| `resolveManualExposureMode` | `isoControl.autoIsoLimits` (automatic ISO) |
|
|
5112
|
+
| `resolveAperturePriorityAutoIsoExposureMode` | `policy.autoIsoLimits` |
|
|
5113
|
+
| `resolveShutterPriorityExposureMode` | `isoControl.policy.autoIsoLimits` (automatic ISO) |
|
|
5114
|
+
| `resolveProgramAutoExposureMode` | `isoControl.autoIsoLimits` (automatic ISO) |
|
|
5115
|
+
| `resolveFullAutoExposureMode` | `policy.autoIsoLimits` |
|
|
5116
|
+
|
|
5117
|
+
Composition fragment: requires previously validated bindings `capabilities`, `target`. This is an integration sketch, not a standalone runnable program.
|
|
5118
|
+
|
|
5119
|
+
```ts
|
|
5120
|
+
import {
|
|
5121
|
+
resolveManualExposureMode
|
|
5122
|
+
} from "@photivra/engine";
|
|
5123
|
+
|
|
5124
|
+
const capped =
|
|
5125
|
+
resolveManualExposureMode({
|
|
5126
|
+
target,
|
|
5127
|
+
capabilities,
|
|
5128
|
+
referenceExposure: {
|
|
5129
|
+
aperture: 4,
|
|
5130
|
+
shutterSeconds: 1 / 125,
|
|
5131
|
+
iso: 100
|
|
5132
|
+
},
|
|
5133
|
+
manualAperture: 4,
|
|
5134
|
+
manualShutterSeconds: 1 / 125,
|
|
5135
|
+
isoControl: {
|
|
5136
|
+
kind: "automatic",
|
|
5137
|
+
quantizationPolicy:
|
|
5138
|
+
"nearest-log2-lower-on-tie",
|
|
5139
|
+
autoIsoLimits: { maximum: 6400 }
|
|
5140
|
+
}
|
|
5141
|
+
});
|
|
5142
|
+
```
|
|
5143
|
+
|
|
5144
|
+
The rules:
|
|
5145
|
+
|
|
5146
|
+
- Both limits are ISO settings: inside the body's Auto ISO range and, on a discrete ISO grid, one of its values, with
|
|
5147
|
+
`minimum` at most `maximum`. `minimum` defaults to the body's Auto ISO minimum. A limit outside the body's range or off
|
|
5148
|
+
the grid fails with `InvalidScientificInputError`, and a malformed record (not an object, an unknown field) with
|
|
5149
|
+
`InvalidConfigurationError`. The engine never clips a limit silently.
|
|
5150
|
+
- The ISO is chosen in the narrowed range, and the baseline of the priority, program and Full Auto policies (the lowest
|
|
5151
|
+
selectable ISO) is the first selectable ISO at or above `minimum`.
|
|
5152
|
+
- When the ISO is held at a limit strictly inside the body's range, the limiting constraint is `user-iso-maximum` or
|
|
5153
|
+
`user-iso-minimum`, with the residual to the target as usual. A limit equal to the body's bound adds nothing; the body's
|
|
5154
|
+
`iso-maximum` or `iso-minimum` is reported.
|
|
5155
|
+
- Without `autoIsoLimits` every result is unchanged.
|
|
5156
|
+
- A separate, lower cap for flash is not part of this: it belongs with flash-aware exposure.
|
|
5157
|
+
|
|
5096
5158
|
A `no-signal` meter target also blocks Auto ISO, instead of selecting maximum ISO or infinity.
|
|
5097
5159
|
|
|
5098
5160
|
### Manual ISO
|
|
@@ -5258,6 +5320,12 @@ The policy is explicit:
|
|
|
5258
5320
|
- `allow-slower-shutter`; or
|
|
5259
5321
|
- `hold-preferred-shutter` and return residual underexposure.
|
|
5260
5322
|
|
|
5323
|
+
With `policy.autoIsoLimits` ([photographer-set limits](#photographer-set-auto-iso-limits-505), #505) the maximum in step 4
|
|
5324
|
+
is the photographer's `maximum`, and both choices behave there exactly as at the body's maximum: the same settings and
|
|
5325
|
+
residual, with `user-iso-maximum` in place of `iso-maximum` when the policy holds the shutter. Shutter Priority
|
|
5326
|
+
(`isoControl.policy.autoIsoLimits`), Program Auto (`isoControl.autoIsoLimits`) and Full Auto (`policy.autoIsoLimits`) take
|
|
5327
|
+
the same limits.
|
|
5328
|
+
|
|
5261
5329
|
For discrete shutter grids, the shutter chosen before ISO is raised is never longer than the requested or preferred duration. Otherwise rounding to the grid could overexpose, and undoing that would need an ISO below the supported minimum.
|
|
5262
5330
|
|
|
5263
5331
|
### Shutter Priority + manual ISO
|
|
@@ -5320,6 +5388,107 @@ This policy:
|
|
|
5320
5388
|
|
|
5321
5389
|
That direction is deliberate. Choosing an aperture wider than the ideal at minimum ISO could overexpose, with no lower ISO left to compensate.
|
|
5322
5390
|
|
|
5391
|
+
### Automatic slowest shutter (#504)
|
|
5392
|
+
|
|
5393
|
+
Cameras let the photographer set the slowest shutter before Auto ISO raises the ISO to "Auto", chosen from the focal
|
|
5394
|
+
length, with a bias in 1-stop steps (Sony's ISO AUTO Min. SS: Faster, Fast, Standard, Slow and Slower, 1 EV apart:
|
|
5395
|
+
[help guide](https://helpguide.sony.net/ilc/2020/v1/en/contents/TP1000139958.html); Nikon and Fujifilm: see the manuals
|
|
5396
|
+
cited above). Those cameras base it on the old reciprocal rule, about 1/focal length. That is a rule of thumb, not physics,
|
|
5397
|
+
and the engine does not use it. `estimateAutoIsoSlowestShutter()` derives the threshold from the engine's own camera-shake
|
|
5398
|
+
model, `estimateCameraShakeBlur()` ([camera shake](https://github.com/Photivra/photivra/blob/main/docs/STABILIZATION.md#controlled-camera-shake)), the same physics the preview
|
|
5399
|
+
shows.
|
|
5400
|
+
|
|
5401
|
+
The camera turns at a constant angular velocity ω = (ω_yaw, ω_pitch), reduced by stabilization to r·ω with r = 2^−stops. A
|
|
5402
|
+
still point on the optical axis then moves
|
|
5403
|
+
|
|
5404
|
+
```text
|
|
5405
|
+
b(t) = v · hypot(tan(r·ω_yaw·t), tan(r·ω_pitch·t))
|
|
5406
|
+
```
|
|
5407
|
+
|
|
5408
|
+
on the sensor, where v is the focal length (or, with `focusDistanceM`, the thin-lens image distance). The derived shutter is
|
|
5409
|
+
the time t at which b(t) equals the blur budget. On one axis that is `t = atan(budget / v) / (r·|ω|)` exactly; on two axes
|
|
5410
|
+
the engine bisects, which works because b grows strictly with t. Then:
|
|
5411
|
+
|
|
5412
|
+
1. the bias: `biased = derived · 2^−fasterByStops`, with `fasterByStops` a whole number from −2 to +2 (+1 is one stop faster);
|
|
5413
|
+
2. the grid, optional: with `shutterCapability`, the biased time snaps to the body's shutter grid, never longer, held at its
|
|
5414
|
+
ends.
|
|
5415
|
+
|
|
5416
|
+
Composition fragment: requires previously validated bindings `capabilities`. This is an integration sketch, not a standalone runnable program.
|
|
5417
|
+
|
|
5418
|
+
```ts
|
|
5419
|
+
import {
|
|
5420
|
+
estimateAutoIsoSlowestShutter,
|
|
5421
|
+
GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC
|
|
5422
|
+
} from "@photivra/engine";
|
|
5423
|
+
|
|
5424
|
+
const slowest =
|
|
5425
|
+
estimateAutoIsoSlowestShutter({
|
|
5426
|
+
focalLengthMm: 50,
|
|
5427
|
+
angularVelocityRadPerSec:
|
|
5428
|
+
GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC,
|
|
5429
|
+
stabilizationStopsEquivalent: 0,
|
|
5430
|
+
blurBudget: {
|
|
5431
|
+
kind: "pixels",
|
|
5432
|
+
pixels: 1,
|
|
5433
|
+
pixelPitchMicrometers: 6
|
|
5434
|
+
},
|
|
5435
|
+
fasterByStops: 0,
|
|
5436
|
+
shutterCapability:
|
|
5437
|
+
capabilities.shutter
|
|
5438
|
+
});
|
|
5439
|
+
// slowest.value.shutterSeconds: about 1/640 s for 50 mm, 6 µm pixels and the generic hand.
|
|
5440
|
+
```
|
|
5441
|
+
|
|
5442
|
+
The result reports `derivedShutterSeconds`, `biasedShutterSeconds`, `shutterSeconds` (after the grid), the blur budget and
|
|
5443
|
+
the shake model's blur at the derived and the final shutter, the projection distance and the residual-motion factor, as an
|
|
5444
|
+
`approximation` with its assumptions. The blur budget is `{ kind: "pixels", pixels, pixelPitchMicrometers }` or
|
|
5445
|
+
`{ kind: "image-diagonal-fraction", fraction, imageWidthMm, imageHeightMm }`.
|
|
5446
|
+
|
|
5447
|
+
What the tests check: longer focal lengths give faster thresholds; each stabilization stop doubles the time exactly; the
|
|
5448
|
+
bias moves it by exact whole stops; the shake model's blur at the derived time equals the budget; nothing is NaN or
|
|
5449
|
+
infinite; invalid input fails with a domain error (no shake, a fractional or out-of-range bias, a non-positive budget, a
|
|
5450
|
+
quarter turn of motion).
|
|
5451
|
+
|
|
5452
|
+
**The hand-shake profile is an input.** No free source gives one angular velocity for every photographer and camera.
|
|
5453
|
+
`GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC` is a **labelled generic default**: 4.39 °/s (≈ 0.0766 rad/s), on yaw. It is the
|
|
5454
|
+
linear camera-motion rate F. Xiao, J. Farrell, P. Catrysse and B. Wandell use for an average user holding a typical 100 g
|
|
5455
|
+
mobile handset with two hands ("Mobile imaging: the big challenge of the small pixel", Proc. SPIE 7250, Digital
|
|
5456
|
+
Photography V, 2009, §2; [author copy](https://web.stanford.edu/~wandell/data/papers/2009-CameraShake-Xiao.pdf)). They
|
|
5457
|
+
take it from their earlier gyroscope measurements, F. Xiao et al., "Camera-motion and mobile imaging", Proc. SPIE 6502
|
|
5458
|
+
(2007), which report that the motion is close to linear for exposures under about 100 ms. It is one magnitude, so the
|
|
5459
|
+
engine puts it on yaw; for small angles the blur length does not depend on the direction. A heavier camera, another grip
|
|
5460
|
+
or another photographer moves differently, so pass your own profile when you have one.
|
|
5461
|
+
|
|
5462
|
+
**Approximations.** Constant angular velocity, blur on the optical axis only, stabilization as 2^−stops (the educational
|
|
5463
|
+
model of `estimateCameraShakeBlur()`, not a CIPA DC-011 rating), no roll, translation or subject motion. The blur budget is
|
|
5464
|
+
your choice of sharpness, not a measured threshold.
|
|
5465
|
+
|
|
5466
|
+
#### Through `simulateCapture`
|
|
5467
|
+
|
|
5468
|
+
`exposure.autoIso.slowestPreferredShutterSeconds: "auto"` (aperture priority; optional in program) calls
|
|
5469
|
+
`estimateAutoIsoSlowestShutter()` with the request's focal length, focus distance, pixel pitch and the body's shutter range.
|
|
5470
|
+
`exposure.autoIso.automaticSlowestShutter` sets `fasterByStops` (default 0), `blurBudgetPixels` (default 1, a generic
|
|
5471
|
+
pixel-level budget), `angularVelocityRadPerSec` (default the generic hand above) and `stabilizationStopsEquivalent` (default 0:
|
|
5472
|
+
the tiers declare none). Each default is recorded as a `default` assumption. The report's `exposure.autoIso.automaticSlowestShutter`
|
|
5473
|
+
gives the derived, biased and used times and the blur at the used time in pixels.
|
|
5474
|
+
|
|
5475
|
+
### Program Auto with Auto ISO: a slowest preferred shutter (#504)
|
|
5476
|
+
|
|
5477
|
+
`resolveProgramAutoExposureMode()` optionally takes
|
|
5478
|
+
`isoControl.slowestPreferredShutter: { slowestPreferredShutterSeconds, afterMaximumIso }`
|
|
5479
|
+
(`ProgramSlowestPreferredShutterPolicy`), the same two-axis policy as Aperture Priority + Auto ISO:
|
|
5480
|
+
|
|
5481
|
+
1. Follow the program line at the lowest selectable ISO.
|
|
5482
|
+
2. If the line's shutter is longer than `slowestPreferredShutterSeconds`, hold the shutter there (snapped, never longer),
|
|
5483
|
+
keep the line's aperture, and raise the ISO.
|
|
5484
|
+
3. At the Auto ISO maximum (the body's or the photographer's `autoIsoLimits`), `"allow-slower-shutter"` lengthens the shutter
|
|
5485
|
+
toward the target, but never past the line's own shutter. `"hold-preferred-shutter"` keeps the shutter there and reports
|
|
5486
|
+
the residual.
|
|
5487
|
+
|
|
5488
|
+
The result adds `slowestPreferredShutter: { ..., applied, afterMaximumIsoFallbackUsed }`; `programLineSelection` stays the
|
|
5489
|
+
line's own selection. Without the option the resolver behaves as before and reports no such field. Full Auto exposure does
|
|
5490
|
+
not take the option.
|
|
5491
|
+
|
|
5323
5492
|
### Shared boundaries
|
|
5324
5493
|
|
|
5325
5494
|
These are control policies over the same relative exposure equation. They create no new optics or sensor physics.
|
|
@@ -6882,6 +7051,31 @@ try { parseDngRawImage({ bytes: png }); } catch { rejected = true; }
|
|
|
6882
7051
|
if (!rejected) throw new Error('A PNG must not parse as a DNG');
|
|
6883
7052
|
```
|
|
6884
7053
|
|
|
7054
|
+
## Text metadata on a file you encoded
|
|
7055
|
+
|
|
7056
|
+
`addJpegTextMetadata(jpeg, metadata)` and `addPngTextMetadata(png, metadata)` (#388) add the caller's text to a JPEG
|
|
7057
|
+
or PNG that was encoded without `output.metadata`: for example a full-size file joined from `output.region` parts
|
|
7058
|
+
and encoded with `composePrintJpegStrips` or `serializePngImage`. They are the functions that `output.metadata`
|
|
7059
|
+
calls, so for the same file and text the bytes are the same as a whole-frame `simulateCapture` with
|
|
7060
|
+
`output.metadata`. The text is checked as `output.metadata` is (see
|
|
7061
|
+
[Text metadata](https://github.com/Photivra/photivra/blob/main/docs/QUICK_START.md#text-metadata-24)); invalid text or a
|
|
7062
|
+
malformed file fails with `InvalidConfigurationError`. The input array is not changed.
|
|
7063
|
+
|
|
7064
|
+
```ts
|
|
7065
|
+
import { addJpegTextMetadata, addPngTextMetadata, serializePngImage, simulateCapture } from '@photivra/engine';
|
|
7066
|
+
|
|
7067
|
+
const text = { description: 'Siemens star at f/8', software: 'My Viewer 1.0', comment: 'Test run 3' };
|
|
7068
|
+
// A PNG the caller encoded (here one sRGB pixel) gets the PNG text chunks.
|
|
7069
|
+
const png = addPngTextMetadata(serializePngImage({ width: 1, height: 1, bitDepth: 8, samples: Uint8Array.of(255, 128, 0),
|
|
7070
|
+
colourEncoding: { kind: 'srgb', renderingIntent: 'perceptual' } }), text);
|
|
7071
|
+
// A JPEG written without output.metadata and tagged afterwards has the bytes of one written with it.
|
|
7072
|
+
const plain = await simulateCapture({ scene: { preset: 'siemens-star' }, output: { width: 8, height: 8, format: 'jpeg' } });
|
|
7073
|
+
const direct = await simulateCapture({ scene: { preset: 'siemens-star' }, output: { width: 8, height: 8, format: 'jpeg', metadata: text } });
|
|
7074
|
+
const tagged = addJpegTextMetadata(plain.image.jpeg!, text);
|
|
7075
|
+
const same = tagged.length === direct.image.jpeg!.length && tagged.every((byte, i) => byte === direct.image.jpeg![i]);
|
|
7076
|
+
console.log(png.length, same);
|
|
7077
|
+
```
|
|
7078
|
+
|
|
6885
7079
|
## Native-only Print planning
|
|
6886
7080
|
|
|
6887
7081
|
- [Registered linear statistics](https://github.com/Photivra/photivra/blob/main/docs/PRINT_REGION_STATISTICS.md) tell apart changes caused by processing the same capture from variation between repeated captures at each site.
|
|
@@ -7502,17 +7696,56 @@ console.log(star.assumptions.filter((a) => a.kind === "default").map((a) => a.id
|
|
|
7502
7696
|
console.log(star.request.siteInput(0).environment.optics.nominalFNumber);
|
|
7503
7697
|
```
|
|
7504
7698
|
|
|
7699
|
+
### Color charts (2.14)
|
|
7700
|
+
|
|
7701
|
+
The engine's own [color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md) are reflectance spectra lit by the capture's illuminant. Draw the
|
|
7702
|
+
whole set with the `color-patches` preset, or use one patch wherever a chart takes a reflectance spectrum, as
|
|
7703
|
+
`{ kind: "color-patch", patchId }`. `getColorPatchSet()` gives each patch's code, name, reason for being on the chart and reference CIELAB and sRGB
|
|
7704
|
+
under D65.
|
|
7705
|
+
|
|
7706
|
+
```ts
|
|
7707
|
+
import { getColorPatchSet, simulateCapture } from "@photivra/engine";
|
|
7708
|
+
|
|
7709
|
+
const tungsten = await simulateCapture({
|
|
7710
|
+
scene: { preset: "color-patches", patchIds: ["neutral-4", "red", "skin-medium", "sky-blue"], columns: 4 },
|
|
7711
|
+
illuminant: "cie-a",
|
|
7712
|
+
sensor: { spectralStepNanometers: 5 },
|
|
7713
|
+
output: { width: 64, height: 16 }
|
|
7714
|
+
});
|
|
7715
|
+
const sky = getColorPatchSet().patches.find((p) => p.patchId === "sky-blue");
|
|
7716
|
+
console.log(tungsten.scene.parameters.patchSet, sky?.code, sky?.name, sky?.reference.cielabD65); // "PV-M1", "Midday Sky"
|
|
7717
|
+
```
|
|
7718
|
+
|
|
7505
7719
|
In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relations S = 10/H and H = 0.65·L·t/N²) through
|
|
7506
7720
|
`resolveAperturePriorityExposureMode()`.
|
|
7507
7721
|
`exposure.standardExposureDeviationStops` is the exposure used minus the standard exposure for the metered luminance L, in stops,
|
|
7508
7722
|
log₂(N²/t) − log₂(0.65·L·S/10) from the same relations: positive is darker (under), negative brighter (over). In an automatic
|
|
7509
7723
|
mode it is minus the compensation plus any limit the resolver hit; it is `null` only for a scene with no metered luminance.
|
|
7510
|
-
|
|
7724
|
+
`exposure.standardExposureIlluminanceLux` is the illuminance at which those settings give the standard exposure, E·2^Δ for the
|
|
7725
|
+
request's illuminance E (a reflective scene's metered luminance is proportional to E); `null` for a black scene or a
|
|
7726
|
+
`radiance-function` scene. `exposure.greyCardDeviationStops` (2.14) is the deviation from the standard exposure of an 18 % grey
|
|
7727
|
+
card under that illuminance, log₂(L_ref/(0.18·E/π)): incident metering, independent of the scene's reflectances, exactly 0 at
|
|
7728
|
+
the defaults. The automatic modes meter the scene by default; `exposure.metering: "grey-card"` makes them (and Auto ISO) meter
|
|
7729
|
+
that grey card instead, so they land on a grey-card deviation of 0. The display view's `exposure` carries the same values.
|
|
7730
|
+
|
|
7731
|
+
```ts
|
|
7732
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7733
|
+
|
|
7734
|
+
const chart = await simulateCapture({
|
|
7735
|
+
scene: { preset: "uniform", reflectance: 0.9 },
|
|
7736
|
+
exposure: { mode: "aperture-priority", metering: "grey-card" },
|
|
7737
|
+
output: { width: 8, height: 8 }
|
|
7738
|
+
});
|
|
7739
|
+
console.log(chart.exposure.metering, chart.exposure.settings.shutterSeconds, chart.exposure.greyCardDeviationStops);
|
|
7740
|
+
```
|
|
7511
7741
|
|
|
7512
7742
|
With `exposure.iso: "auto"` (#516) the ISO is chosen too, in every mode, by the resolvers' Auto ISO branches
|
|
7513
7743
|
([Priority modes with Auto ISO](#priority-modes-with-auto-iso)). Aperture priority then needs the slowest shutter
|
|
7514
7744
|
before the ISO rises, and takes the policy at the maximum ISO; the result reports the ISO, that it was automatic and
|
|
7515
|
-
what limited it.
|
|
7745
|
+
what limited it. `exposure.autoIso.limits: { maximum, minimum? }` (#505) sets the photographer's Auto ISO range in any
|
|
7746
|
+
mode ([photographer-set limits](#photographer-set-auto-iso-limits-505)). `slowestPreferredShutterSeconds: "auto"` (#504)
|
|
7747
|
+
derives the slowest shutter from the camera-shake model, in aperture priority and, optionally, program
|
|
7748
|
+
([automatic slowest shutter](#automatic-slowest-shutter-504)). See [Auto ISO](QUICK_START.md#auto-iso-516).
|
|
7516
7749
|
|
|
7517
7750
|
```ts
|
|
7518
7751
|
import { prepareDisplayView, simulateCapture, type SimulateCaptureOptions } from "@photivra/engine";
|
|
@@ -7575,6 +7808,12 @@ console.log(ran.backend, ran.fallbackReason, ran.assurance, ran.errorBound, ran.
|
|
|
7575
7808
|
prepared.dispose(); // releases a device the view acquired; a shared accelerator's device stays its owner's
|
|
7576
7809
|
```
|
|
7577
7810
|
|
|
7811
|
+
The GPU code (the session, the renderers and their WGSL, about 52 kB gzip) is imported only when `gpu` is `"auto"` or
|
|
7812
|
+
`"require"`, so a bundler that splits dynamic imports leaves it out of a page's first load. A build that cannot split inlines
|
|
7813
|
+
it: Vite builds workers as `iife` by default, so a worker that calls `prepareDisplayView` carries it; build workers as ES
|
|
7814
|
+
modules (`worker: { format: "es" }`, with `new Worker(url, { type: "module" })`) to keep it out. Measured sizes:
|
|
7815
|
+
[Bundle size](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#bundle-size).
|
|
7816
|
+
|
|
7578
7817
|
#### Sharing AHD correction states between workers
|
|
7579
7818
|
|
|
7580
7819
|
With `output.demosaic: "ahd"`, low-signal display pixels read correction states that each realm (a page, a Web Worker)
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.14.0" as const
|
|
15
15
|
```
|
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
|
|
4
4
|
|
|
5
|
+
## GENERIC_THIRD_STOP_ISO_SETTINGS
|
|
6
|
+
|
|
7
|
+
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
GENERIC_THIRD_STOP_ISO_SETTINGS = Object.freeze([100, 125, 160, 200, 250, 320, 400, 500, 640, 800, 1000, 1250, 1600,
|
|
11
|
+
2000, 2500, 3200, 4000, 5000, 6400, 8000, 10000, 12800, 16000, 20000, 25600] as const)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## GENERIC_THIRD_STOP_SHUTTER_SECONDS
|
|
15
|
+
|
|
16
|
+
The standard third-stop shutter times, seconds, from 1/8000 s to 30 s: the conventional markings of t = 2^(k/3) s
|
|
17
|
+
(1/8000 for 2^−13, 1/6 for 2^−8/3, 0.3 for 2^−5/3, 30 for 2^5; each within 7 % of the exact value). The generic bodies'
|
|
18
|
+
shutters are continuous over 1/8000–30 s and take any time as exact, so these are the values to offer in a third-stop
|
|
19
|
+
dial, not a restriction.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
GENERIC_THIRD_STOP_SHUTTER_SECONDS = Object.freeze([1 / 8000, 1 / 6400, 1 / 5000, 1 / 4000, 1 / 3200, 1 / 2500, 1 / 2000,
|
|
23
|
+
1 / 1600, 1 / 1250, 1 / 1000, 1 / 800, 1 / 640, 1 / 500, 1 / 400, 1 / 320, 1 / 250, 1 / 200, 1 / 160, 1 / 125, 1 / 100, 1 / 80, 1 / 60, 1 / 50, 1 / 40,
|
|
24
|
+
1 / 30, 1 / 25, 1 / 20, 1 / 15, 1 / 13, 1 / 10, 1 / 8, 1 / 6, 1 / 5, 1 / 4, 0.3, 0.4, 0.5, 0.6, 0.8, 1, 1.3, 1.6, 2, 2.5, 3.2, 4, 5, 6, 8, 10, 13,
|
|
25
|
+
15, 20, 25, 30] as const)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## GENERIC_WHOLE_STOP_ISO_SETTINGS
|
|
29
|
+
|
|
30
|
+
Whole-stop ISO settings of the 2.0.0 bodies (each body's list stops at its maximum).
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
GENERIC_WHOLE_STOP_ISO_SETTINGS = Object.freeze([100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600] as const)
|
|
34
|
+
```
|
|
35
|
+
|
|
5
36
|
## GenericEquipmentTier
|
|
6
37
|
|
|
7
38
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
@@ -18,7 +49,7 @@ The exact contract is declared below; use the domain guide for assumptions and s
|
|
|
18
49
|
export interface GenericEquipmentTierPreset {
|
|
19
50
|
tier: GenericEquipmentTier;
|
|
20
51
|
label: "Consumer" | "Prosumer" | "Professional";
|
|
21
|
-
presetId: string; presetVersion:
|
|
52
|
+
presetId: string; presetVersion: GenericTierPresetVersion;
|
|
22
53
|
scientificStatus: "approximation";
|
|
23
54
|
evidence: readonly EvidenceProvenance[];
|
|
24
55
|
body: GenericTierBodyAssets;
|
|
@@ -78,6 +109,15 @@ export interface GenericTierLensAssets {
|
|
|
78
109
|
}
|
|
79
110
|
```
|
|
80
111
|
|
|
112
|
+
## GenericTierPresetVersion
|
|
113
|
+
|
|
114
|
+
The exact versions of the generic tier presets (no "latest" alias). 2.1.0 (2.14, round 5) adds the third-stop ISO
|
|
115
|
+
settings; 2.0.0 is kept, whole-stop, so saved selections and records of it still resolve to exactly what they named.
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
export type GenericTierPresetVersion = "2.0.0" | "2.1.0";
|
|
119
|
+
```
|
|
120
|
+
|
|
81
121
|
## GenericVersionedAsset
|
|
82
122
|
|
|
83
123
|
The exact contract is declared below; use the domain guide for assumptions and staged integration.
|
|
@@ -16,10 +16,18 @@ This call is synchronous; any supplied provider must follow its explicit synchro
|
|
|
16
16
|
|
|
17
17
|
## GENERIC_EQUIPMENT_TIER_PRESET_VERSION
|
|
18
18
|
|
|
19
|
-
The
|
|
19
|
+
The current preset version (2.14: 2.1.0, third-stop ISO settings).
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
GENERIC_EQUIPMENT_TIER_PRESET_VERSION = "2.1.0" as const
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## GENERIC_EQUIPMENT_TIER_PRESET_VERSIONS
|
|
26
|
+
|
|
27
|
+
Every exact preset version that resolves, oldest first. A version is never retuned in place (`test/generic-tier-presets.test.ts`).
|
|
20
28
|
|
|
21
29
|
```ts
|
|
22
|
-
|
|
30
|
+
GENERIC_EQUIPMENT_TIER_PRESET_VERSIONS = Object.freeze(["2.0.0", "2.1.0"] as const)
|
|
23
31
|
```
|
|
24
32
|
|
|
25
33
|
## GENERIC_EQUIPMENT_TIERS
|
|
@@ -44,7 +52,7 @@ The exact contract is declared below; use the domain guide for assumptions and s
|
|
|
44
52
|
|
|
45
53
|
```ts
|
|
46
54
|
export interface GenericEquipmentTierSelection {
|
|
47
|
-
schemaVersion: "0.1.0"; presetVersion:
|
|
55
|
+
schemaVersion: "0.1.0"; presetVersion: GenericTierPresetVersion;
|
|
48
56
|
bodyTier: GenericEquipmentTier; lensTier: GenericEquipmentTier;
|
|
49
57
|
bodyPresetId: string; lensPresetId: string;
|
|
50
58
|
profileReferences: readonly GenericEquipmentTierProfileReference[];
|