Commit Graph
154 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 23e181283c Release 0.13.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:06:09 -04:00
Brandon ThomasandClaude Opus 5.5 479a05a0be Release 0.12.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:39:28 -04:00
Dylan M. Taylor bfb23db2ca Use chrono instead of hand-rolled UTC conversion (#2457)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month table with chrono as a workspace dependency:
- apps/lightcraft/src/logging.rs: timestamp (log line time)
- crates/catalog/src/dates.rs: civil + weekday
- crates/catalog/src/stacks.rs: iso_seconds (date -> seconds)
- crates/engine/src/cmd/face_recognize.rs: capture_secs (native only)
- crates/meta/src/datetime.rs: DateTime::unix_seconds and the valid() day check
Output formats and semantics are unchanged (pre-epoch clamp, day rollover that
normalize_iso relies on, leap-second tolerance, EXIF/XMP parsing). chrono uses
only std, so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock as the epoch, keeping the old pre-epoch clamp.

timestamp and catalog's civil write years past 9999 with {:04} like the old
{y:04} did, because %Y would add a '+'. Tests cover the out-of-range and
5-digit-year cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01, changing output the old hand-rolled
Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was
318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).

- apps/lightcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/catalog/src/dates.rs: `civil` cycle-based (the callers stay inside
  0000-9999, but a far clock no longer resets to the epoch).

Pinned old outputs in tests:
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
          10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  civil 10_000_000_000_000 -> 318857-05-20T17:46:40,
        -10_000_000_000_000 -> -314918-08-13T06:13:20, i64 extremes

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:36 -04:00
Brandon ThomasandClaude Opus 5.5 79d916c149 Release 0.11.0
The web release now builds with craft-fonts too (web-craft-fonts), as VectorCraft's
does, so the published web app ships the Chinese and Japanese faces docs/releasing.md
promises.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 14:05:43 -04:00
Brandon ThomasandClaude Opus 5.5 a2a41383c4 Release 0.10.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 22:19:04 -04:00
Brandon Thomas 0a0cb19011 Merge PR #1893: JPEG XL: check dimensions during loading & bound tracked allocations (#1517) 2026-10-18 09:19:27 -04:00
Brandon ThomasandClaude Opus 5.5 4f0b110bcb Release 0.9.1: the release web build installs the wasm-bindgen CLI
Since the web zip moved from trunk to cargo xtask web (aeaf4154), packaging/web/package.sh needs the
wasm-bindgen CLI at Cargo.lock's version, and the shared release workflow installs only trunk, so
0.9.0's Web (wasm) job failed. In CI, install it first (as FilmCraft and EffectCraft do).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:16:37 -04:00
Brandon ThomasandClaude Opus 5.5 20ccedf56e Release 0.9.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:42:57 -04:00
skylar taylor-barrick 2b173f0bee Build: take craft-docpath from the merged craft-libs rev
craft-libs#169 is merged, so the fork pin is no longer needed. Pinned to
storytold/craft-libs 82a24b006eefa036b43c09a67fb9a3fbd17ffe63 (main after the merge) rather than the older 12281d07,
which predates resolve_absolute and the Windows colon refusal.
2026-10-17 12:03:10 -06:00
adrdsouzaandCodex 9c4fce176f Record CI-generated codecs test dependency lockfile
Co-authored-by: Codex <noreply@openai.com>
2026-10-17 20:50:59 +05:30
aiongg 62eb6627a6 Build: use craft-docpath from craft-libs 2026-10-17 02:21:02 +00:00
aiongg 346681561a Lightroom import: follow catalog paths only on this computer or the catalog's share 2026-10-16 17:44:52 +00:00
Brandon Thomas 9ddb8d1824 Merge PR #1663 (rebased)
# Conflicts:
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
2026-10-15 03:40:17 -04:00
Brandon ThomasandClaude Opus 5.5 2179473536 Merge PR #1663
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:24:51 -04:00
Brandon Thomas 9ca113defa Merge PR #1135 (rebased)
# Conflicts:
#	xtask/src/main.rs
2026-10-15 00:53:45 -04:00
Brandon Thomas f613aeba59 Merge PR #1176
# Conflicts:
#	crates/raw/src/vendor/arw.rs
#	docs/parity-checklist.md
#	docs/sony-lens-corrections.md
2026-10-14 23:54:06 -04:00
Brandon Thomas 9ca3cd0926 Merge PR #1135
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	README.md
#	crates/raw/Cargo.toml
#	crates/ui-egui/src/theme.rs
#	xtask/src/main.rs
2026-10-14 23:23:18 -04:00
Brandon ThomasandClaude Opus 5.5 cfd14ea7c1 Batch 36: the Polish format catalog (#1100) lists the strings this batch's other PRs add, in English until translated; lockfile at 0.8.0 for the slideshow crate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:05:23 -04:00
Brandon Thomas 618b4c723a Merge PR #821
# Conflicts:
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
2026-10-14 21:54:47 -04:00
Brandon ThomasandClaude Opus 5.5 16dbb90499 Release 0.8.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:34:55 -04:00
Alex ChambersandClaude Opus 5.5 ac677a2624 LR-EDIT-OPTICS-PROFILE: decode Sony shading, CA and distortion tables with settings and provenance; apply nothing new
Sony ARWs record shading, lateral chromatic aberration and distortion as a camera setting plus a radial
table in the raw image IFD (0x7031-0x7037), with copies in the encrypted SR2SubIFD (0x797c-0x7982).
They are now decoded for every body that records them and carried through RawInfo and RawImage as
`lens_metadata.sony` (types in crates/raw/src/lens_metadata.rs, decoder in
crates/raw/src/vendor/arw_lens.rs):

- Raw-IFD fields are the ones the TIFF reader parsed, which is what the image itself uses. A scan of
  the directory adds diagnostics only: repeated tags, and why an entry the reader skipped was dropped
  (values outside the file, an unknown type, past the reader's limits). SR2SubIFD fields are read
  from their first entry. Only the exact layouts are accepted: SHORT[1] or non-negative SSHORT[1]
  settings, SSHORT[17] headed by 16 for shading and distortion, SSHORT[33] headed by 32 for CA.
  Nothing is allocated from a count in the file.
- The raw IFD's copy wins. The SR2SubIFD copy fills in only for a tag the raw IFD lacks, never for a
  malformed one, and the two copies' agreement is recorded.
- Settings keep their number and their documented meaning, Unknown(value) otherwise (never Off).
- Framing evidence (stored frame, Sony and default crops, reference rectangle, coding, 3:2) and a
  per-component status. Only Applied components become opcodes.

Applied corrections are unchanged: the ILCE-7RM4A distortion conversion receives exactly the table it
received before, and no other body or component gains a correction. The ILCE-7CR distortion misses its
camera JPEG at 24 mm, and shading and CA semantics are not validated on any body; see
docs/sony-lens-corrections.md.

Locating the SR2SubIFD, now done for every ARW that has one, selects the SR2Private offset, length and
key with the TIFF reader's own rules, which lightcraft-tiff now exposes and builds its reader on
(directory_entries, entry_span, ParseOptions::budget_for, decode_value). Only each field's first value
is decoded, so the directory's other entries cost no memory sized by the file: a 16 MiB file whose
SR2Private holds eight fields aliasing it peaks at 37 kB. DirectoryEntries keeps its layout private,
and its positions() iterator uses checked arithmetic.

Tests: synthetic decoder tests; a differential test of the SR2Private selection against the TIFF
reader's in both byte orders (0 to 8,193 filler entries, unreadable and unusual first occurrences of
each field, a used-up value budget); a dhat peak-heap test; public-corpus checks against the values
ExifTool prints, with pinned and anchored SHA-256s (LIGHTCRAFT_CORPUS_REQUIRED=1 makes absent files
fail); and an opt-in local inventory check (crates/raw/tests/sony_lens_inventory.rs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:19:21 +02:00
Andrew FrahnandClaude Opus 5.5 0242a75b5a Faces: run the networks' convolutions on the GPU (wgpu) when it agrees with the CPU and is faster
An accelerator hook in the face interpreter takes Conv nodes; lightcraft-gpu implements it with a WGSL
implicit-GEMM convolution (any stride, padding, grouping; split-K for small maps; a direct kernel for depthwise
layers), each model's weights resident on a device of its own. After loading, detection and recognition check
the GPU in the background (agreement within 1e-3 and faster than the CPU) and otherwise stay on the CPU, which
remains the reference; the first GPU error drops it. Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 12:22:50 +11:00
Jason Baldwin a021ad2c56 Merge slideshow-playback into slideshow-composer (origin/main: batches 32–33) 2026-10-13 16:55:11 +02:00
Brandon Thomas 68bacba288 Merge PR #1113 2026-10-13 02:04:31 -04:00
Jason Baldwin 2faee15203 Merge slideshow-playback into slideshow-composer (origin/main: batch 32; toast clear of the slideshow controls) 2026-10-13 06:55:02 +02:00
Brandon ThomasandClaude Opus 5.5 701f78ea2e Release 0.7.1: fix the macOS build
eframe 0.36's App::on_exit takes the glow context as a second argument.
The macOS-only menu-bar Quit path still called on_exit(self), so the
0.7.0 macOS release job failed with E0061. Pass None; our on_exit ignores
it. macOS isn't built in CI, so only the release build caught it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:13:08 -04:00
Brandon ThomasandClaude Opus 5.5 25ef65c81e Release prep: 0.7.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:44:33 -04:00
Chad ElliottandClaude Opus 5.5 3e42f2e05c feat(testkit): #1037 tests record why they skip
libtest has no skipped status and hides the output of passing tests, so a
test that returns early without a GPU, a corpus file, a craft-fonts face or
a model passed silently. `lightcraft-testkit` (the testkit slot the layering
table already reserves, dev-dependency only, std only) adds
`skip!(Need, "reason")`: it prints the note as before and, when
LIGHTCRAFT_SKIP_LOG names a file, appends one JSON line (need, reason, test,
binary, file:line). Plain `cargo test` is unchanged. The 61 existing skip
sites use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:33:54 -05:00
s17sh70x2jfl031ag 465601b76c HEIF: move the heic-rs pin from 0.1.1 to 0.1.4
heic-rs 0.1.2 to 0.1.4 fix six panics on malformed files, found by fuzzing:
four in release builds (a uuid box too small for its extended type, a
block starting right of the picture, a conformance window larger than the
picture, and a reference row below the picture) and two with overflow
checks on (a PCM bit depth above the sample bit depth, a slice QP out of
range). The API, MSRV and output are unchanged.

Mirrors the same change in PhotoCraft's photocraft-heif.
2026-10-11 12:46:22 -07:00
Jason Baldwin 2138175826 Merge slideshow-playback into slideshow-composer (origin/main: batch 31; locales merged keeping main's values, whats-new keeps main's Reliability entry, parity summary refreshed) 2026-10-11 17:11:12 +02:00
Jason Baldwin efaa39b8b5 Merge slideshow-playback into slideshow-composer (origin/main: geocode + slideshow crates side by side in the manifests, locale keys from both, tracker summary refreshed) 2026-10-11 16:22:50 +02:00
Jason Baldwin ee8b56f1db Merge slideshow-playback into slideshow-composer (origin/main: integration batch 26, #975; Cargo.lock: lightcraft-slideshow 0.6.0) 2026-10-11 15:05:04 +02:00
Claude 4a0d04b994 Merge PR #780
JPEG XL export via the pure-Rust jixel crate (robmdunn; fixes #247).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BKWB9TCnvzJHV6xMLAj2J4
2026-10-11 12:40:55 +00:00
Claude 37b26c56a3 Merge batch 29b (#946, #947, #953, #950)
# Conflicts:
#	Cargo.toml
#	crates/engine/src/rename.rs
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
#	crates/ui-egui/src/icons.rs
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/menubar.rs
#	crates/ui-egui/src/menus.rs
#	crates/ui-egui/src/panels/left.rs
#	docs/whats-new.md
2026-10-11 12:08:44 +00:00
Claude 10ebeccdc8 Merge PR #950
# Conflicts:
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panels/dialogs.rs
#	crates/ui-egui/src/state.rs
#	docs/parity-checklist.md
2026-10-11 12:06:31 +00:00
Claude a0e422ce4a Merge PR #752
# Conflicts:
#	crates/engine/src/cmd/develop.rs
#	crates/engine/src/files.rs
#	docs/mcp.md
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-11 12:02:46 +00:00
Claude c53b61732d Merge PR #738
On merge: the XWayland default goes in front of the catch_unwind-wrapped launch (#951), and the
README's Linux section names the LIGHTCRAFT_WAYLAND=1 opt-out.
2026-10-11 12:01:41 +00:00
Claude 76fc27a7bc Merge PR #791
# Conflicts:
#	Cargo.lock
#	apps/lightcraft/src/main.rs
#	docs/parity-checklist.md
#	docs/whats-new.md
2026-10-11 12:01:19 +00:00
Claude 11f41cbe97 Merge PR #813 2026-10-11 12:00:21 +00:00
Claude 17dfa9709e Merge PR #926
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	crates/ui-egui/locales/de.json
#	crates/ui-egui/locales/es.json
#	crates/ui-egui/locales/fr.json
#	crates/ui-egui/locales/ja.json
#	crates/ui-egui/locales/pt-br.json
#	crates/ui-egui/locales/ru.json
#	crates/ui-egui/locales/uk.json
#	crates/ui-egui/locales/zh-hans.json
#	crates/ui-egui/locales/zh-hant.json
#	xtask/src/layers.rs
2026-10-11 11:57:45 +00:00
Claude b8d39063db Merge PR #930 2026-10-11 11:53:12 +00:00
Claude 5861591307 Merge PR #948
On merge: metadata.downloadPlaceNames accepts plain http only for the loopback address itself
(http://127.0.0.1.example.com/ and http://127.0.0.1@host/ are refused; test
download_base_is_https_or_loopback_only).
2026-10-11 11:52:57 +00:00
Andrew FrahnandClaude Opus 5.5 88e4188e78 Fix #812: the window's renderer can't kill the start-up (DX12 shader errors)
On some Windows machines FXC fails to compile egui's own shaders on DX12
("The parameter is incorrect. (0x80070057)" in egui_pipeline) and wgpu's
default error handler panicked. Each renderer (DX12, Vulkan, GL, glow) is
now probed with error scopes and a logging error handler before the window
opens; the one that works is remembered; a start that dies leaves a marker
and relaunches with the next renderer. LIGHTCRAFT_GPU_BACKEND=glow added.

Sources: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 21:45:02 +11:00
Andrew FrahnandClaude Opus 5.5 b28b05c475 Reverse geocoding: fill City / State / Country from GPS (offline GeoNames)
New crate lightcraft-geocode (L1): parses the GeoNames cities dump and finds the
nearest place within 50 km through a 1-degree cell index; NaN / out-of-range
positions and malformed lines find nothing. Engine commands
metadata.reverseGeocode (Photo menu, Info panel "Fill Location"; fills only
empty fields unless overwrite, one undo step, activity row for batches),
metadata.downloadPlaceNames (background download of the CC-BY 4.0 GeoNames
files into the settings folder, cancellable activity row) and
metadata.placeNamesStatus. No data is bundled; NOTICE records the licence.

Fixes #582.

Sources: GeoNames dump readme (prose); haversine (textbook); PKWARE APPNOTE
(ZIP, prose); cell index own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:49:36 +11:00
lloydandClaude Opus 5.5 9815ca1310 Merge upstream/main into fix-export-saturation
Brings the branch up to date with main (#869 shared docking and the docs it
added); no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:40:04 +02:00
Andrew FrahnandClaude Opus 5.5 5f35b5af17 Cargo.lock: version for lightcraft-lensdb
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:21:42 +11:00
Andrew FrahnandClaude Opus 5.5 e142075998 NOTICE: credit PhotoCraft for the ported inpaint code; Cargo.lock version for lightcraft-inpaint
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 20:02:17 +11:00
Andrew FrahnandClaude Opus 5.5 fe6d7b0192 Content-aware Remove: inpaint crate (PatchMatch completion) and a Fill spot mode
New `inpaint` crate (L1): multi-scale patch-based completion with a PatchMatch
nearest-neighbour field, seeded by a membrane fill. Deterministic, no panics on
hostile sizes, cancellable. Ported from PhotoCraft (MIT OR Apache-2.0).

Remove tool: a fourth spot mode, `fill` ("Content-Aware"), rebuilds the spot
from patches of its surroundings instead of one source; coarse-to-fine, so the
structure is chosen at a scale set by the spot size and preview and export agree
(tested). spot.add leaves its source unset.

Sources: C. Barnes et al., PatchMatch (SIGGRAPH 2009); Y. Wexler, E. Shechtman,
M. Irani, Space-Time Completion of Video (TPAMI 2007); P. Perez et al., Poisson
Image Editing (SIGGRAPH 2003) for the membrane fill; code ported from PhotoCraft
crates/algo/src/inpaint.rs + poisson.rs (a680ed22).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:42 +11:00
Andrew FrahnandClaude Opus 5.5 80e58ed55b engine: lens profile corrections from a lensfun-format database
Raws without an embedded lens correction look their EXIF lens up in the
user-supplied database; the profile goes through the existing Enable Profile
Corrections path (amount sliders, CPU/GPU geometry stage, DNG export).
Synthetic-grid test renders a distorted chart through the pipeline.

Sources: own design (see lightcraft-lensdb for the model sources).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:18 +11:00
Andrew FrahnandClaude Opus 5.5 00ea386f7f lensdb: lens distortion (poly3/poly5/PTLens), TCA (linear/poly3) and vignetting (pa) profiles, lensfun-format loader
Profiles are interpolated in focal length (vignetting also in aperture and
focus distance), scaled by crop factor, and converted by least squares to the
DNG WarpRectilinear / FixVignetteRadial polynomials the pipeline already
applies. The lensfun database (CC-BY-SA) is only read from a directory the
user supplies; nothing is bundled.

Sources: lensfun's published database format and lens calibration
documentation (no lensfun code read), PanoTools wiki (a, b, c distortion
model), DNG specification 1.6 (WarpRectilinear, FixVignetteRadial);
interpolation and fitting: own design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 19:30:18 +11:00