mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-29 10:42:03 +01:00
* Layered TIFF: open and save Photoshop layer data in TIFF files, in either byte order (part of #215) Photoshop keeps a layered TIFF's layers in two private tags: 37724 (ImageSourceData, the same layer section a PSD has) and 34377 (the image resources). PhotoCraft now reads and writes both, so a TIFF opens with its groups, masks, layer styles, type, shapes, smart objects and adjustment layers exactly as the PSD path would, and saves them back. * photocraft-psd: a `tiff` module with `ImageSourceData` (parse/write, PSD- and PSB-sized), the 34377 resource helpers, and a field-by-field byte-order transcoder. In Intel-order TIFFs Photoshop byte-swaps the whole block structure (reversed keys, little-endian integers and doubles) while pixel samples stay big-endian; the transcoder knows the layer-info structure, masks, blending ranges and every block photocraft-io maps (descriptors, TySh, lrFX, the binary adjustments, paths, patterns, filter effects, metadata settings, linked layers, annotations...), recognizes descriptor-shaped unknown blocks, and drops the rest with a warning instead of passing byte-swapped data to other readers. The reader gained an optional little-endian mode. 23 unit tests; a fuzz target in the CI matrix. * photocraft-codecs: the two tags travel as opaque bytes on `Metadata`, read by a bounds-checked walk of IFD0 (classic and BigTIFF, both byte orders) and written back as BYTE / UNDEFINED tags. `tiff_writes_little_endian()` tells which order the encoder produces. * photocraft-io: `tiff_layers` rebuilds a PSD model from the TIFF's pixels plus the tags on import, and on export runs the PSD exporter (new `merged_matte` / `merged_compression` options), interleaves its merged planes for the TIFF image and stores the layer section in the encoder's byte order. `ExportOptions.tiff_layers` (default on). Lab and Multichannel documents save flat; extra alpha channels are not written (one transparency sample). * UI: the TIFF Options prompt ("Including layers will increase file size." / "Discard Layers and Save a Copy" / "Don't show again") behind Preferences > File Handling > Ask Before Saving Layered TIFF Files; `ExportSettings.tiff_layers`; catalogs updated. * Tests: io round trips at 8/16/32 bits in Gray/RGB/CMYK, equivalence with the PSD path, every TIFF compression, the flat fall-backs, malformed input, and files written by psdtags in both byte orders (fixtures from scripts/layered_tiff_fixtures.py); a corpus test (feature `corpus`) writes every corpus PSD as a layered TIFF and compares the result with the PSD round trip (700 files, 0 differences, 0 crashes). Codecs tests cover the tag walk on hand-made files. * Scorecard FILE-215-7 done, BUG-204-3 partial; README and book updated. Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com> * Layered TIFF: de and pt-br strings; neutral wording in the psd tiff docs The four psdtags fixtures stay out of the repository (no binary test data here): the generator now writes them to the gitignored corpus/layered-tiff/, and the tests that read them skip with a message when absent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Regenerate docs/scorecard.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: leon <claude.udder842@passmail.net> Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
166 lines
5.5 KiB
YAML
166 lines
5.5 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
workflow_dispatch:
|
|
inputs:
|
|
fuzz:
|
|
description: "Run the fuzz job"
|
|
type: boolean
|
|
default: false
|
|
fuzz_seconds:
|
|
description: "Seconds per fuzz target"
|
|
default: "600"
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUST_BACKTRACE: 1
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
name: test (${{ matrix.os }})
|
|
runs-on: ${{ matrix.os }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install Linux system dependencies (egui/winit/rfd)
|
|
if: runner.os == 'Linux'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends \
|
|
libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev \
|
|
libgl1-mesa-dev libgtk-3-dev
|
|
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
components: clippy, rustfmt
|
|
|
|
- uses: Swatinem/rust-cache@v2
|
|
|
|
- name: Format
|
|
if: runner.os == 'Linux'
|
|
run: cargo fmt --all -- --check
|
|
|
|
# `--features heif`: official builds open HEIC photos via the optional photocraft-heif crate.
|
|
- name: Test
|
|
run: cargo test --workspace --locked --features heif
|
|
|
|
# The same codecs without the optional decoder: HEIC must stay a clear "not in this build"
|
|
# error for distributors who leave it out.
|
|
- name: Test without heif
|
|
if: runner.os == 'Linux'
|
|
run: cargo test -p photocraft-heif -p photocraft-codecs -p photocraft-io --locked
|
|
|
|
# The same tests built with the optional craft-fonts input (crates/text/build.rs), so the
|
|
# Japanese font tests that skip without it run here. Linux only.
|
|
- name: Check out craft-fonts
|
|
if: runner.os == 'Linux'
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
with:
|
|
repository: storytold/craft-fonts
|
|
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
|
path: craft-fonts
|
|
|
|
- name: Test with craft-fonts
|
|
if: runner.os == 'Linux'
|
|
env:
|
|
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
|
|
CRAFT_FONTS_REQUIRED: "1"
|
|
run: cargo test --workspace --locked --features heif
|
|
|
|
- name: Clippy
|
|
run: cargo clippy --workspace --all-targets --locked --features heif -- -D warnings
|
|
|
|
- name: Dependency layering
|
|
run: cargo xtask layers
|
|
|
|
# docs/scorecard.md must match its sources (checklists, budgets, baseline, corpus floors,
|
|
# prefs audit). Static: it reads committed files only, no benchmarks run here.
|
|
- name: Scorecard up to date
|
|
if: runner.os == 'Linux'
|
|
run: cargo xtask scorecard --check
|
|
|
|
corpus:
|
|
# Real-file corpus tests (cargo feature `corpus`), on every PR and on main. The corpora are
|
|
# pinned in xtask/src/corpus_pins.rs and sha256-verified (xtask/*.sha256) after both a cache
|
|
# restore and a download; a missing corpus fails the tests. The cache key changes only when a
|
|
# pin or manifest does, so normally this restores in seconds (fork PRs read the base cache).
|
|
name: corpus tests
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
key: corpus
|
|
- name: Restore test corpora
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: corpus/
|
|
key: corpus-${{ hashFiles('xtask/src/corpus_pins.rs', 'xtask/*.sha256') }}
|
|
- name: Fetch and verify corpora
|
|
run: cargo xtask corpus --all
|
|
- name: Corpus tests
|
|
run: cargo xtask test-corpus
|
|
|
|
wasm:
|
|
name: wasm32 check
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: wasm32-unknown-unknown
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
key: wasm32
|
|
- name: Check wasm-safe crates
|
|
run: cargo xtask wasm
|
|
|
|
fuzz:
|
|
name: fuzz (${{ matrix.target.crate }}/${{ matrix.target.name }})
|
|
# Placeholder: manual trigger only until a nightly schedule is enabled.
|
|
if: github.event_name == 'workflow_dispatch' && inputs.fuzz
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
target:
|
|
- { crate: psd, name: parse }
|
|
- { crate: psd, name: descriptor }
|
|
- { crate: psd, name: image_source_data }
|
|
- { crate: codecs, name: decode_any }
|
|
- { crate: codecs, name: decode_png }
|
|
- { crate: codecs, name: decode_jpeg }
|
|
- { crate: codecs, name: decode_tiff }
|
|
- { crate: codecs, name: decode_exr }
|
|
- { crate: codecs, name: decode_heif }
|
|
- { crate: format, name: load }
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@nightly
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: crates/${{ matrix.target.crate }}/fuzz
|
|
- name: Install cargo-fuzz
|
|
run: cargo install cargo-fuzz --locked
|
|
- name: Fuzz
|
|
working-directory: crates/${{ matrix.target.crate }}
|
|
run: cargo fuzz run ${{ matrix.target.name }} -- -max_total_time=${{ inputs.fuzz_seconds }}
|
|
- name: Upload crash artifacts
|
|
if: failure()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: fuzz-${{ matrix.target.crate }}-${{ matrix.target.name }}
|
|
path: crates/${{ matrix.target.crate }}/fuzz/artifacts
|