Commit Graph
140 Commits
Author SHA1 Message Date
Przemyslaw Majdak 612c4a3c7f Codecs: warn when DPX or Cineon saves drop bits (#8793)
DPX at 10 or 12 bits and Cineon (always 10) are planned as U16, so
DepthReduced, which compares sample types, never fired: a 16-bit document
saved at 10 bits lost six bits per sample silently, and a float one was
told it would be reduced to 16-bit.

FidelityWarning::BitsReduced { from, bits } names the stored depth. Float
samples always get it; U16 ones only when some sample is not already a
code value at that depth, so a frame opened from a 10- or 12-bit file
saves back without a warning. U8 widens losslessly and stays silent.
2026-10-23 17:38:29 -04:00
Dylan M. Taylor a691aaf65b codecs: use u32 in the padded-truncated-JPEG test's bounds arithmetic (u64: From<i32> does not implement — E0277) 2026-10-23 15:08:50 -04:00
Dylan M. Taylor a8983a4060 security(codecs): budget truncated JPEG scans by scan data, document the animated WebP ICC charge
CodeRabbit follow-up:
- data_end counts the scan-data bytes it crosses, and decode's
  truncated-input bound is measured on those, not on bytes.len(): APP
  padding padded before the scan used to charge the budget without
  describing one more pixel.
- a regression test builds a truncated JPEG with ~256 KiB of complete
  COMMENT padding whose patched 20000x20000 frame was admissible under
  the file-length bound, and verifies padding alone cannot admit it
  (while the same file at its real size still recovers).
- Limits::max_alloc docs state the animated WebP charging, next to the
  PNG text/XMP sentence.
2026-10-23 14:44:28 -04:00
Dylan M. Taylor c9892c8f8d security(codecs): refuse implausibly short truncated JPEG scans; charge WebP frame ICC copies
A truncated JPEG scan under a large declared frame was filled out by the
backend to the full admitted size; reject it when the file cannot plausibly
account for the pixels. Animated WebP frames each keep a copy of the ICC
profile, so the aggregate admission now counts it.
2026-10-23 14:12:04 -04:00
Dylan M. Taylor 1bd922f6d1 fix(codecs): use exact minimum encoded row length for PCX preflight 2026-10-22 19:35:37 -04:00
Dylan M. Taylor 827d949af9 security: preflight PCX and XPM output allocations 2026-10-22 19:28:08 -04:00
aiongg 934eb71d84 Integration: merge main
crates/engine/src/file_cmds.rs: batch keeps the save-point closure (takes the output path) and also copies the session's authorize hook into each scratch session, as main now does.
crates/raw/src/sensor.rs: both sides cap the memory of retained tiles; kept the existing retained_segment_bytes check (checked multiplication, same limit) and dropped main's duplicate inline loop.
2026-10-22 15:39:51 +00:00
aiongg 4d1b922ddf Integration: track t02
# Conflicts:
#	contributors/people.toml
#	crates/engine/src/file_cmds/tests.rs
#	crates/engine/src/print_cmds/tests.rs
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/uk.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
2026-10-22 15:38:14 +00:00
aiongg 82cf3c828c Integration: track t01
# Conflicts:
#	crates/ui-egui/src/i18n/cs.tsv
#	crates/ui-egui/src/i18n/de.tsv
#	crates/ui-egui/src/i18n/el.tsv
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/fr.tsv
#	crates/ui-egui/src/i18n/hu.tsv
#	crates/ui-egui/src/i18n/id.tsv
#	crates/ui-egui/src/i18n/it.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/ko.tsv
#	crates/ui-egui/src/i18n/nl.tsv
#	crates/ui-egui/src/i18n/pl.tsv
#	crates/ui-egui/src/i18n/pt-br.tsv
#	crates/ui-egui/src/i18n/ru.tsv
#	crates/ui-egui/src/i18n/sr.tsv
#	crates/ui-egui/src/i18n/tr.tsv
#	crates/ui-egui/src/i18n/vi.tsv
#	crates/ui-egui/src/i18n/zh-hans.tsv
#	crates/ui-egui/src/i18n/zh-hant.tsv
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-22 15:38:14 +00:00
Dylan M. Taylor f42d7ef41a security(codecs): index FITS header keywords for O(1) lookup
Header::get scanned every card linearly, and axis resolution looks up
NAXIS1..NAXISn while find_image/data_len re-resolve the axes. Both the
commentary-card count and the NAXIS count are file-controlled, so a
crafted header multiplied them into quadratic header-processing work
without a correspondingly large decoded image.

Build a first-occurrence keyword index once in parse_header (skipping
HIERARCH cards, matching the prior filter) and use it in get().

Finding: eb2121d8-9dee-4422-bd57-f78dce60a61b (CWE-407)
2026-10-22 07:11:58 -04:00
Dylan M. Taylor 3495a4b225 security(codecs): dedup Cryptomatte keys and cache stream lists
Cryptomatte metadata enumeration had two file-driven amplifiers:
- cryptomatte_keys() tested key membership with iter().any(), O(n^2) in
  the number of distinct cryptomatte/<key>/<field> attribute groups;
- layers() called layer_streams() for every key, rescanning all channels
  and allocating a fresh owned stream list even when many keys name the
  same layer.

Use a HashSet for key membership (keeping file order) and cache
layer_streams results by resolved layer name within each header, so
repeated keys reuse the discovered streams.

Findings:
- 120f4d51-b981-4fcc-abec-26f45f3efd29 (CWE-407, quadratic key membership)
- dae6e76b-a491-4089-892f-4cf34b911f36 (CWE-400, repeated per-key stream scans)
2026-10-22 07:11:50 -04:00
Dylan M. Taylor 19e8c0d7cd security(codecs): stop quadratic rescans in XMP value scanning
xmp_values advanced its cursor only past the matched key, so the
value-closing search (find(close)) rescanned the remaining packet for
every property-like substring. Repeating text such as
' tiff:ResolutionUnit>' inside the packet produced quadratic CPU work
even when no real resolution property existed.

Advance the cursor past each located value; a missing close delimiter
means the value is unterminated, so no later value can parse and the
scan stops. Valid multi-value extraction is unchanged.

Finding: 6ce55e33-27f0-4fb9-a22f-737382e1d328 (CWE-407)
2026-10-22 07:11:45 -04:00
Dylan M. Taylor fed04ecef3 security(codecs): budget Cryptomatte per-pixel pair capacity
The decode admission counted each pixel's channel samples and vector
header but not the heap capacity of the per-pixel (id, coverage) pair
lists. A compressed Cryptomatte document could therefore return pixel
storage exceeding the caller's max_alloc budget.

Include the maximum pair capacity (one pair per rank, 8 bytes each) in
the per-pixel byte count before check_bytes.

Finding: 5482ed4a-bc95-43fc-84d1-d5bfb6373761 (CWE-400)
2026-10-22 07:10:58 -04:00
Dylan M. Taylor 867caefb5c security(codecs): admit deep-EXR output size and validate chunks first
Two deep-EXR allocation gaps:

- flatten() allocated the flattened output and the full display-window
  stage (nc f32 samples per pixel) without a limit check; the deep decode
  only admitted the data window at four bytes per pixel, before the
  channel layout was known. Pass Limits into flatten and re-check both
  buffers at the real f32 layout size.
- decode_deep built a per-pixel usize block index before validating the
  chunk's declared packed sizes. Compute the block bounds first, validate
  the payload is present, then materialize the index, so a truncated or
  lying chunk is rejected before the index is allocated.

Findings:
- feb6fcd8-58df-47e2-a04a-d4d81d5565aa (CWE-789, display-stage allocation)
- 9c53025d-65f8-4bcc-9161-143a4c5d7f46 (CWE-789, index before payload check)
2026-10-22 07:10:54 -04:00
Dylan M. Taylor 9edcb97687 security(codecs): admit HEIF coded image size, not just transformed
When transforms (orientation/clean-aperture crop) are applied, admission
checked only the transformed output size. The delegated HEVC decoder
allocates the full coded image before cropping, so a HEIF with a small
visible aperture over a large coded image bypassed the limit policy.

Always check the coded dimensions against the full limits before
delegating, in addition to the transformed size.

Note: the related SPS-driven and grid-tile allocations (findings
75048229 and 2db4c59e) occur inside the pinned heic-rs dependency from
parameter sets the container does not expose; fully bounding those needs
a decoder-side change and is tracked separately.

Finding: ec5cec31-8a62-4963-8045-e3ec9d9e4931 (CWE-400)
2026-10-22 07:10:49 -04:00
aiongg 995927592b Integration: DDS_PITCH is defined once after #5864 met the DDS writer 2026-10-22 02:36:51 +00:00
aiongg dfde38919e Merge PR #7883: Save As JPEG: JPEG Options dialog (quality 0–12, Baseline / Optimized / Progressive) (FILE-215-19)
crates/ui-egui/src/lib.rs: replay_save doc comment names both Export As and JPEG; replay_save uses the PR's FormatChoices::from_step, after main's export_dialog::replay branch.
docs/file-format-parity.md: header keeps main's text plus the JPEG Options item (last updated 2026-10-19); TIFF row is main's, JPEG row is the PR's; change-log rows once each, newest first.
docs/gaps.md: header keeps main's text plus the G10 item (last updated 2026-10-19).
2026-10-22 02:27:46 +00:00
aiongg 3db9314ca2 Merge PR #6265: Codecs: save Cineon (.cin) film frames
apps/photocraft/src/services.rs: SAVE_FILTERS keeps the DDS entry from main and the PR's Cineon entry.
crates/codecs/README.md: Cineon table row from the PR (read and write), then main's DICOM and Amiga IFF rows.
crates/codecs/src/format.rs: caps keep the PR's Cineon (read and write) and main's Dicom and Iff rows.
crates/codecs/src/lib.rs: encode keeps the PR's Cineon arm and main's Dicom and Iff arms.
docs/file-format-parity.md: header kept from main with the Cineon write item added; Cineon / DPX row from the PR, DICOM row from main; the PR's change-log line added once at the top.
docs/gaps.md: header kept from main with the Cineon write item added; Missing list without 'Cineon write'; the PR's Cineon progress line replaces main's read-only one; the PR's change-log line added once at the top.
2026-10-22 02:24:03 +00:00
aiongg 1260845533 Merge PR #8253: test(codecs): cover exact TGA packet and encoder input boundaries 2026-10-22 02:23:46 +00:00
aiongg cc390f34de Merge PR #5864: fix(codecs): respect uncompressed DDS row pitch
crates/codecs/README.md: DDS paragraph kept from main (it also describes writing) with the PR's row-pitch sentence added.
2026-10-22 02:23:35 +00:00
aiongg c76bae7f03 Merge PR #5635: Animated WebP: every frame loads into video layers (#1198)
docs/file-format-parity.md: header line kept from main with the animated WebP item added in front; BMP row from main; WebP row from main plus the PR's animation sentence; the PR's change-log line added once, above the first 2026-10-14 row.
2026-10-22 02:23:20 +00:00
aiongg 96cce9b2f9 Merge PR #4713: Codecs: open Amiga IFF/ILBM bitmaps read-only
apps/photocraft-web/src/web.rs: OPEN_EXTS kept main's list (with the DICOM extensions) and added iff, lbm, ilbm.
apps/photocraft/src/services.rs: OPEN_EXTS kept main's list (with the DICOM extensions) and added iff, lbm, ilbm.
crates/codecs/README.md: kept both the DICOM and the Amiga IFF table row and section.
crates/codecs/src/format.rs: kept both the Dicom and the Iff variants in every match, detect runs the DICOM check then the IFF check, Format::ALL length re-counted (22).
crates/codecs/src/lib.rs: one use line importing dicom and iff; both decode and encode arms kept.
docs/gaps.md: header line kept from main, with the PR's 'Cineon and Amiga IFF read' wording.
2026-10-22 02:22:54 +00:00
aiongg 8c92678a30 Merge PR #7815: PNG: correct sTER eye-order names without changing stored bytes 2026-10-22 02:22:43 +00:00
aiongg 1e5866ebaf Merge PR #4567: Codecs: PCX runs may cross plane boundaries within a scanline
contributors/people.toml: union; main's new entries kept and the PR's webdevtodayjason entry appended.
2026-10-22 02:22:17 +00:00
aiongg c97ffe307e Integration: #4315 refuse a HighBit outside the allocated bits
An out-of-range HighBit made the sample shift overflow. Adds a test with HighBit 200.
2026-10-22 02:22:10 +00:00
aiongg 7b50fbd796 Merge PR #4315: Codecs: open uncompressed DICOM single-frame images (#1048)
apps/photocraft-web/src/web.rs: OPEN_EXTS kept main's list and added dcm, dicom, dc3 after cin.
apps/photocraft/src/services.rs: OPEN_EXTS kept main's list and added dcm, dicom, dc3 after cin.
2026-10-22 02:21:52 +00:00
aiongg f97c28828d Merge PR #6873: HEIF: honour the nclx colour description; HLG/PQ HEICs open as linear BT.2020 32-bit (#6752) 2026-10-21 21:56:21 +00:00
aiongg c865e3b93b Merge PR #5928: BMP export: Indexed Color documents save as palette BMPs 2026-10-21 21:56:20 +00:00
aiongg abfb8cce96 Merge PR #8042: Codecs: Fix corrupted CMYK JPEG exports (#8008) 2026-10-21 21:56:11 +00:00
aiongg a0d6bf2dcd Merge PR #7791: Camera raw: a developed raw keeps its EXIF (RW2 included), so File Info shows its Camera Data (#6957) 2026-10-21 21:56:11 +00:00
Aditya VishwakarmaandClaude Opus 5.5 3e014505ad test(codecs): cover TGA packet boundaries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 18:51:28 +05:30
Higgs32584andClaude Opus 5.5 b0acb119d6 Merge upstream main into fix/6752-heic-hlg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:19:06 -04:00
Higgs32584andClaude Opus 5.5 5eac93bf23 Merge upstream/main into revive/4330-jpeg-options
Main moved on (integration batch #8095 and others). Two conflicts:
- docs/gaps.md revision history: both rows kept, newest first.
- docs/scorecard.md (generated): main's version taken. It needs
  `cargo xtask scorecard` again: with FILE-215-19 done, File
  compatibility reads 8 done / 5 missing rather than main's 7 / 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:47:00 -04:00
Higgs32584andClaude Opus 5.5 92d1ff42f9 Merge upstream/main into revive/4330-jpeg-options
Main added DDS saving (#5993): DDS Options, and FormatChoices carrying the
Targa and DDS answers through every save path (this branch's SaveChoices
carried Targa and JPEG). Resolved by keeping main's name and making it
the one struct for all three answers.

- lib.rs: FormatChoices { tga_bits, dds, jpeg } with from_params (strict),
  from_step (lenient JPEG read of a recorded step), apply and record;
  main's separate block is folded in. Both prompts (dds_options and
  jpeg_options) are kept everywhere: save_to asks Targa, then DDS, then
  JPEG Options; save_options_open covers all four. save_in_place takes
  main's (path, choices) signature and records the plain-Save step with
  plain_save_step for JPEG plus the Targa/DDS answers (save_in_place_with
  folded into it). journal_as_plain_save relabels a parked Targa, DDS or
  JPEG prompt and keeps tgaBits/dds*/quality/jpegFormat when dropping the
  path. replay_save reads choices with from_step.
- SAVE_OPTIONS_OPEN names all four prompts; DDS Options uses it too.
- targa_options_ui: main's plain-Save step (no path) through this
  branch's write_answered_save. JPEG Options gets main's Enter-on-a-
  focused-Cancel fix.
- view_cmds Save a Copy asks Targa, DDS, then JPEG Options; write_copy
  takes FormatChoices.
- control.rs: both app.save tests (JPEG and DDS) kept.
- engine file_cmds: SaveOpts keeps jpeg_format and dds; param docs list
  both jpegFormat and ddsCompression/ddsMipmaps. CLI: both --jpeg-format
  and --dds-compression/--dds-mipmaps. codecs re-exports JpegFormat and
  DdsCompression/DdsOptions.
- docs/book: both entries kept (control-protocol app.save lists both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:43:26 -04:00
Vikram Bhaskaran c41ca8b9f1 Merge origin/main into feat/iff-read.
Keep Amiga IFF in the gaps changelog and add an Open dialog filter for
iff/lbm/ilbm so named types still cover OPEN_EXTS.
2026-10-21 08:01:33 +11:00
Sir Starch 3bd2274aa2 docs(codecs): document JPEG metadata helpers 2026-10-20 22:47:20 +02:00
Sir Starch f10194c028 Fix: omit JFIF APP0 from CMYK JPEG (#8008) 2026-10-20 22:29:51 +02:00
aiongg c247a34bb6 Merge PR #7892: Indexed GIF opens in Indexed Color with its colour table, as in Photoshop (#7886) 2026-10-20 18:39:28 +00:00
Sir Starch 0ed0f286ce Merge upstream/main into fix/bmp-indexed-export 2026-10-20 19:10:21 +02:00
Guillem Hermida 872748fb5b Codecs: save DDS textures, uncompressed or BC1–BC3, with optional mipmaps (#1327) (#5993)
DDS textures can now be saved, not just opened: uncompressed (BGRA, BGRX, L8, A8L8; lossless) or BC1/BC2/BC3 (DXT1/3/5) from a clean-room block encoder, with an optional box-filtered mip chain, in the legacy header. Save As and Save a Copy ask with DDS Options; scripts pass `ddsCompression`/`ddsMipmaps` (file commands, control `app.save`, RPC `doc.save`, MCP `doc_save`/`doc_export`), and the CLI takes `--dds-compression`/`--dds-mipmaps`. Targa and DDS answers travel together as `FormatChoices` through the Save As flow.

Review follow-ups:
- File › Save behind DDS or Targa Options records a plain Save with its answer, and replaying `file.save` applies it (#3531).
- The block encoder finds the principal axis from every channel, so a channel that varies on its own can't hide the others' variation.
- DDS joins the shared round-trip suite; the header is built before the output buffer is allocated.
- Enter on a focused Cancel in DDS, Targa or TIFF Options cancels instead of saving.
2026-10-20 11:02:39 -06:00
Korg1ch 7296d0d8b2 fix(io): a developed camera raw keeps the raw's EXIF for File Info's Camera Data (#6957) 2026-10-20 18:28:13 +03:00
Korg1ch e92e5164a8 test(io): an indexed GIF keeps its table and transparent entry; malformed GIFs give no table (#7886) 2026-10-20 06:18:48 +03:00
Korg1ch 04a2169b44 fix(io): an indexed GIF opens in Indexed Color with its colour table (#7886) 2026-10-20 05:45:16 +03:00
e647e102f3 Save As JPEG: JPEG Options dialog (quality 0–12, Baseline / Optimized / Progressive) (FILE-215-19)
Save As and Save a Copy to a JPEG now ask for JPEG Options before writing,
as Photoshop does. Re-creates #4330 on current main and folds in the
unique parts of #5580 (#5563).

- codecs: `JpegFormat` (Baseline, Optimized, Progressive) on
  `EncodeOptions`. Progressive writes SOF2. Baseline Optimized fits the
  Huffman tables only at 4:4:4 (Gray, CMYK) or with Progressive: fitted
  tables make the baseline scans non-interleaved, which zune-jpeg 0.5.15
  decodes wrongly with 4:2:0 chroma (zune-image#419), as Save for Web
  already works around.
- engine: a `jpegFormat` save param (file.saveACopy, Batch, Image
  Processor, Layers to Files and every SaveOpts user; anything but the
  three names is BadParams before writing) and a shared 0–12 -> 1–100
  `jpeg_quality()` that maps NaN/inf to 100.
- ui-egui: `jpeg_options_ui` (Quality 0–12 with Low/Medium/High/Maximum
  presets, Format Options), parked like Targa Options. It starts at the
  opened JPEG's own quality (`file_encoding`, #4032), else the Export
  preference; left there, OK writes that quality exactly and the step
  records no `quality`. `SaveChoices` (Targa bits + JPEG) replaces the
  bare `tga_bits` through file.save / file.saveAs / app.save, replay and
  Save a Copy; automation never prompts. `write_answered_save` is shared
  with Targa Options. `park_at` + `Start` let any save seed the prompt.
  A File › Save that confirms the document's own flat JPEG is still
  journalled as a plain `file.save` (#3531).
- CLI: `--jpeg-format`.
- i18n: five new strings in all 20 catalogs (#4330's duplicate de/es/zh
  `Low`/`High` rows dropped: main has them since #4008).
- Docs: book raster-formats JPEG bullet, control protocol, file-format
  parity, gaps G10, target-app parity; scorecard FILE-215-19 done.

Co-authored-by: SckraT <70698629+SckraT@users.noreply.github.com>
Co-authored-by: PierrunoYT <95778421+PierrunoYT@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:28:38 -04:00
98d3ebc1f5 perf(codecs): Avoid redundant PNG filter passes (#7690)
* perf(codecs): Avoid redundant PNG filter passes

Keep the previous filter path as an exact byte oracle and measure the filter kernel independently of compression.

* docs(perf): measure PNG filtering on real images

* perf(codecs): stop PNG candidates at the winning cost

* Drop the local benchmark report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:19:05 -04:00
Daniel Campos RamosandOpenAI Codex 0689608619 PNG: name sTER modes by their specified eye order
Both sTER modes describe side-by-side subimages: mode 0 is cross-fuse (right eye first), and mode 1 is diverging-fuse (left eye first). Correct the enum and document comments without changing their serialized byte values. Add a labelled-pixel check against the literal mode bytes in PNG Extensions 1.4.0 section 3.6.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-10-19 14:08:44 -03:00
640873de7b PNG: keep a stereo file's sTER pairing on save (#6063) (#6285)
* PNG: keep a stereo file's sTER pairing on save (#6063)

Read the sTER chunk (PNG Extensions 1.4.0 §3.6) when a PNG opens, store it
on the document's FileEncoding, and write it back when saving, so a stereo
PNG still says it is a stereo pair. Covers the ordinary and the palette
PNG encoders, including interlaced output.

* io tests: pass the new sTER argument to encode_png_indexed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: highperfocused <highperfocused@pm.me>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:02:18 -04:00
aiongg d95fdf3181 Merge pull request #7569 from aiongg/integration-4
Integration batch: 12 PRs on main
2026-10-18 23:29:05 -04:00
Marco Arnulfo 0eb0387d53 Save for Web and Quick Export: "All" keeps the photo's EXIF (fixes #7436) (#7471) 2026-10-18 17:23:10 -04:00
aiongg fdb3d97031 Merge PR #5983: Codecs: cap what tiff_exif::read copies from overlapping EXIF values (#5982) 2026-10-18 20:33:43 +00:00