Commit Graph
174 Commits
Author SHA1 Message Date
aiongg bb641e918a refactor(codecs): split tiff.rs into tiff/ 2026-10-26 22:54:56 +00:00
aiongg ea6268d736 refactor(codecs): move src/codecs/mod.rs to src/codecs.rs 2026-10-26 22:13:07 +00:00
aiongg 24a11846e6 refactor(codecs): move src/codecs/vp8/mod.rs to src/codecs/vp8.rs 2026-10-26 22:13:07 +00:00
aiongg cece50d513 Merge pull request #9386 from davidmann4/integration-batch-8
Integration batch 8: 37 reviewed community PRs on main
2026-10-25 20:04:41 -04:00
Dylan M. Taylor 3877bb1dbe chore: resolve SonarCloud maintainability issues (#9044)
* chore: resolve SonarCloud maintainability issues

* fix: format imports and fix effect_entry argument type

* fix: restore test closure and wildcard import that broke compilation

* refactor: split composite_to_surface to reduce cognitive complexity
2026-10-25 19:38:31 -04:00
ae4b778258 Integration: TIFF warns when extra samples past the cap are left out (#7415)
A decode kept the first MAX_EXTRA_SAMPLES (16) extra samples and dropped
the rest without a warning, though files with up to 64 samples a pixel
open: an RGB TIFF with 20 extra samples opened with 16 and no notice, and
a save then lost the other 4. The decode now adds ExtraSamplesOverLimit
with the count left out.

Found by CodeRabbit on #9386.

Co-authored-by: Jason DeBoever <615848+jasallen@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 21:56:00 +01:00
David Mann 5939e12dfb Merge PR #7415: codecs: read and write TIFF extra samples (spot and alpha channels) 2026-10-25 16:33:53 +01:00
f71c8b4680 Integration: open TIFFs whose extra samples exceed the memory limit (#7415)
The extra-sample budget check failed the whole decode, so a TIFF whose
image fits max_alloc but whose extra channels don't (e.g. 12000x8000
RGB8 with 5 alpha channels under wasm's 1 GiB limit) no longer opened,
though main opens it. Now the image decodes without the extra samples
and carries a DecodeWarning::ExtraSamplesDropped.

ExtraSamples values above 2 are reserved (TIFF 6.0, p. 31); they now
read as 0 (unspecified), so a save never writes a reserved value back.

Co-authored-by: jasallen <615848+jasallen@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:32:08 +01:00
David MannandClaude Opus 5.5 699e909278 Integration: merge #7415's approved head (#8885)
The stack carried #7415's codecs code from before its last review. This
brings in 1c5343d5, approved by FredApps: every TIFF extra plane must be
exactly width x height x sample bytes, with its two regression tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:08:00 +01:00
David MannandClaude Opus 5.5 4365c0537d Integration: merge main, resolve conflicts (#8885)
psd_import.rs keeps main's warning for an incomplete merged extra channel
next to the PR's Photoshop spot polarity. people.toml takes main's entries
plus jasallen's own. Docs take both sides' status lines and revision rows;
docs/scorecard.md is regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:07:44 +01:00
.CooCoo 09dc629b42 Codecs: save Amiga IFF (.iff) as 24-bit ILBM (#9080)
Photoshop reads and writes IFF; PhotoCraft opened Amiga IFF/ILBM files
but had no writer. Saving as .iff now writes a FORM ILBM with a BMHD and
a ByteRun1-compressed BODY of 24 bitplanes (red, green and blue, least
significant bit first), one plane row at a time. An image with alpha
also gets a mask plane (masking 1): alpha of 128 or more is opaque, and
when any pixel is not fully opaque the export warns that alpha is
reduced to on/off transparency. Gray is expanded to RGB, 16-bit and
float images are reduced to 8 bits, and images wider or taller than
65535 pixels are refused.

The reader also opens 24-bitplane ILBMs, so a saved file reopens with
the same pixels. A transparent color index in a 24-bitplane file is
refused as unsupported.

IFF leaves ASYMMETRIC_EXCEPTIONS, Save As offers Amiga IFF, and the
codecs README, file-format parity and G10 are updated.
2026-10-25 10:20:09 -04:00
aiongg 3d0c176e80 JPEG: a craft-jpeg frame without scan data is refused before decoding 2026-10-25 00:00:01 +00:00
aiongg ae368d5a61 JPEG: lossless and 12-bit progressive files are unsupported, not damaged 2026-10-24 23:20:40 +00:00
aiongg 4198b9fa3b JPEG: follow craft-jpeg's review fixes (non_exhaustive Limits) 2026-10-24 22:06:38 +00:00
skylar taylor-barrick 1c5343d5f7 codecs: say what the second extra-plane check guarantees
The first comment claimed the check covers the Lab re-type, which happens later
in `write_image`. Say instead what the check actually adds: the planes are
validated in the sample type they now hold after `converted`, and a plane that
`Image::convert` took but could not rebuild arrives here empty rather than
retyped.
2026-10-24 15:43:33 -06:00
skylar taylor-barrick e2ecad7442 codecs: refuse TIFF extra samples that are not one sample per pixel
The public `ExtraSample` contract is one sample per pixel in the image's sample
type, but the writer only stopped when a requested slice was missing, so an
oversized plane was silently truncated and a short one was dropped mid-image.
`Image::convert` leaves a plane whose length does not match the image alone
(deliberately: importers drop such a plane with a warning), so encoding a Lab
F32 image also reinterpreted a wrong-sized plane at the file's 16 bits.

Check every plane against width * height * sample.bytes(), with checked
arithmetic, at both ends: on the source image before `converted` brings it to
the plan's sample type, and on the image actually written, which the Lab path
re-types. A plane of any other length is a CodecError::Encode naming the plane,
its length and the length the image needs.

Regressions: too long, too short and empty planes at 8 bits, and a 7- or 9-byte
plane for a two-pixel Lab F32 image, which the U16 conversion used to accept.
2026-10-24 15:32:54 -06:00
skylar taylor-barrick b6835441dc Merge main into the TIFF extra-samples branch
Resolve the contributors/people.toml append conflict by keeping both sides:
main's three new entries and this branch's entry.
2026-10-24 15:29:53 -06:00
aionggandMichael Higgins 7cf4e6eca0 JPEG: open 12-bit and arithmetic-coded files through craft-jpeg (#4934, #5003)
Arithmetic-coded frames (SOF9-SOF11) and SOF0/SOF1 frames whose precision
isn't 8 bits go to craft-jpeg; everything else stays on zune-jpeg. 12-bit
files open as 16-bit images. PhotoCraft's own marker scan still supplies
ICC, EXIF, XMP, resolution and Photoshop resources, and damaged or truncated
entropy data adds the Truncated warning. A truncated scan too short for the
frame it declares is refused, as on the zune-jpeg path.

craft-jpeg is pinned to the aiongg fork until it lands on storytold.

Co-authored-by: Michael Higgins <mhiggins217@gmail.com>
2026-10-24 21:19:48 +00:00
FinnNkandClaude Opus 5.5 def4646c0e fix(codecs): WebP export keeps the resolution (#8775)
* fix(codecs): WebP export keeps the resolution

Every WebP export with metadata warned that the resolution would be
dropped, and lost it unless the image already had EXIF with resolution
tags. WebP has no resolution field of its own, so both encoders now
write the resolution into EXIF, adding the tags (or a minimal EXIF
block when the image has none), and WebP reports DPI support.

Part of #2399

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

* fix(codecs): report WebP DPI as dropped when the source EXIF can't be parsed

That block is written as it is rather than replaced, so its metadata
survives and the lost resolution is reported. The metadata test also
checks the rest of the EXIF (Orientation) survives.

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

* test(codecs): spell the WebP metadata test's bytes as escapes

The Orientation tag and the unparseable EXIF block were raw control
bytes, which also made git keep the file's CRLF line endings; both are
escapes now and the file is LF again.

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

* fix(codecs): no EXIF block just for WebP's default 72 ppi

A WebP without EXIF already opens at 72 ppi, so writing a minimal EXIF
for the default only turned every web export into the extended (VP8X)
container (and broke the automation test expecting a simple lossy file).
EXIF the image already has still gets its resolution tags updated.

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

* fix(codecs): WebP DPI kept by XMP isn't reported as dropped

When the image's EXIF can't be parsed but its XMP records the
resolution, the WebP keeps it through XMP, which readers check first.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:41:02 -04:00
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
Jason DandClaude Opus 5.5 009329b10d codecs: allocate the TIFF extra-sample write buffers fallibly
interleave_extras and the 16-bit and float sample copies used infallible
allocations sized by the image and its extra planes, so a save too large
for memory aborted the process. They now reserve with try_reserve_exact
and return an OutOfMemory error, which the encoder reports, as the
decoder already does through `zeroed`. Raised by CodeRabbit on #8885.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:42:43 -04:00
Jason DandClaude Opus 5.5 e6fca0548f Merge current main into split/tiff-extra-samples-codecs
Conflicts: crates/codecs/src/image.rs (main's new `cicp` field next to
this branch's converted extra-sample planes in `meta`) and
contributors/people.toml (main's new entries kept, jasallen's added after
them). The codecs integration tests moved into tests/it/ on main; this
branch's test changes followed them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:43:11 -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 ffa400c754 Keep tests correct and docs current with one test binary per crate
- gpu: the test modules share one GPU_LOCK, and timing and vector_mask
  take it too, so one process drives one wgpu device at a time.
- ui-egui: the wgpu tests in pattern_preview_canvas, adjust_preview_gpu,
  marquee_autoscroll and layout_perf take gpu_lock().
- affinity compound_limits: the child process selects its test by the
  module path (compound_limits::<name>); with --exact, the bare name
  matches no test and the child passes without running the check.
- xtask scorecard reads crates/io/tests/it/corpus.rs.
- Docs and comments name the new test paths and the --test it filters.
2026-10-22 16:55:34 +00:00
aiongg a6428d3dfb Build each crate's integration tests as one binary
Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs; helper modules under tests/common move with them.

These keep their own binary (own process):
- engine panic_hunt: a process-wide panic hook counts every panic.
- engine text_gamma: changes the process-wide text gamma.
- engine smart_filter_stages, mask_cache_purge: assert on global caches.
- codecs pnm_peak_memory: reads the process's peak RSS.
- photocraft mac_menu_appkit: harness = false.
2026-10-22 16:55:34 +00: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 8e206e60b0 Integration: #5864 #5993 the DDS level cut-out test sets the level's row pitch 2026-10-22 15:39:21 +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