Files
craft-libs/crates/aep/tests/it/container/matte.rs
Dylan M. Taylorandvangel76 4a7024cd69 craft-aep: flat read-only tree index and the Pard decoder (ports of effectcraft#3302 and #3319) (#948)
* craft-aep: a flat read-only tree index, borrowed text documents, fewer allocations

The container part of storytold/effectcraft#3302:

- `TreeIndex`: the chunk tree as one flat array in depth-first order,
  borrowed from the input, for readers that only look. Same chunks counted
  against the same limits and the same errors as `parse`. An `.aepx` is
  indexed by `index_aepx` into one buffer. The typed view reads through
  either tree (`Node`, `Root`, `Project::from_index`).
- Chunk parser: each list of siblings is counted, then reserved once.
  `.aepx`: hex decoded into an output sized once, attribute values without
  an entity borrowed. `write` reserves the exact output size.
- Text documents are parsed into a tree that borrows from the body; the
  style and paragraph defaults are built once.
- Layer markers and Source Text are read where they are stored instead of
  searched for.
- Container accessors that only tests used are removed.

What the importer and exporter in EffectCraft reach across the crate
boundary is public here: `Root`, `Draft`, `AepxDrafts`, `index_aepx`,
`TreeIndex::from_drafts`, `Project::of`, `ProjectSettings::of`,
`Node::marker_list`, `Root::file_version` and `saved_by_version`,
`StyleRun::SAMPLED`. `TimeBase::NTSC` holds the rates as plain
numerator and denominator instead of an EffectCraft type.

* craft-aep: one decoder for an effect parameter definition (`Pard`)

The container part of storytold/effectcraft#3319: the fields of a `pard`
record (type code, name, default, valid range, slider range, popup item
count) are read by one type, `Pard`, which `EffectParam` now reads
through. It is public so that an exporter's tests can read back what
they encode instead of keeping a second decoder.

The rest of that PR (import and export bounds, loss reports) is in
EffectCraft's importer and exporter.

---------

Co-authored-by: vangel76 <vangel1976@googlemail.com>
2026-11-11 19:01:12 -05:00

83 lines
3.1 KiB
Rust

//! Track mattes (cases 402-408): mode and matte layer in the layer header.
use crate::container::helpers::{first_comp, open};
use craft_aep::{MatteMode, Project, TrackMatte};
/// `(name, video switch, track matte)` of every layer of comp `C`, top first.
fn layers(name: &str) -> Option<Vec<(String, bool, Option<TrackMatte>, u32)>> {
Some({
let bytes = crate::common::try_fixture(name)?;
let file = open(&bytes);
let project = Project::new(&file).expect("project");
let comp = first_comp(&project);
comp.layers()
.map(|l| {
let h = l.header().expect("header");
(l.name().or_else(|| project.layer_source(&l)?.display_name()).unwrap_or_default(), h.is_video_enabled(), l.track_matte(), h.id)
})
.collect()
})
}
#[test]
fn the_four_matte_modes_and_the_matte_layer_id() {
for (case, mode) in [
("403-matte-L-alpha-M.aep", MatteMode::Alpha),
("404-matte-L-alphainv-M.aep", MatteMode::AlphaInverted),
("405-matte-L-luma-M.aep", MatteMode::Luma),
("406-matte-L-lumainv-M.aep", MatteMode::LumaInverted),
] {
let l = skip!(layers(case));
assert_eq!(l.len(), 2, "{case}");
let (m_name, m_video, m_matte, m_id) = &l[0];
let (l_name, l_video, l_matte, l_id) = &l[1];
assert_eq!((m_name.as_str(), l_name.as_str()), ("M", "L"), "{case}");
// The matte layer M has an id of its own (0x1d in the single-case files).
assert_ne!(m_id, l_id, "{case}");
assert_eq!(*m_matte, None, "{case}: the matte layer has no matte itself");
assert_eq!(*l_matte, Some(TrackMatte { mode, layer: Some(*m_id) }), "{case}");
// After Effects switches the matte layer's video off by itself.
assert!(!m_video, "{case}");
assert!(l_video, "{case}");
}
}
#[test]
fn a_matte_layer_below_its_target() {
let l = skip!(layers("407-matte-M-alpha-L-below.aep"));
assert_eq!(l[0].2, Some(TrackMatte { mode: MatteMode::Alpha, layer: Some(l[1].3) }));
// The matte is L (0x1b in the single-case file).
assert_eq!(l[1].0, "L");
assert!(l[0].1, "M stays visible");
assert!(!l[1].1, "L, the matte, is switched off");
assert_eq!(l[1].2, None);
}
#[test]
fn a_matte_layer_whose_video_was_switched_back_on() {
let l = skip!(layers("408-matte-L-alpha-M-visible.aep"));
assert!(l[0].1, "M visible");
assert_eq!(l[1].2, Some(TrackMatte { mode: MatteMode::Alpha, layer: Some(l[0].3) }));
}
#[test]
fn no_layer_without_a_matte_reads_one() {
for name in crate::container::helpers::FIXTURES.iter().filter(|n| !n.contains("-matte-")) {
let bytes = fixture!(name);
let file = open(&bytes);
let Some(project) = Project::new(&file) else { continue };
for item in project.all_items() {
let Some(comp) = item.comp() else { continue };
for layer in comp.layers() {
assert_eq!(layer.track_matte(), None, "{name}");
}
}
}
}
#[test]
fn modes_from_codes() {
assert_eq!(MatteMode::from_code(0), None);
assert_eq!(MatteMode::from_code(9), Some(MatteMode::Other(9)));
}