Commit Graph
448 Commits
Author SHA1 Message Date
FredApps 93bdc1f2f8 Merge pull request #3252 from vangel76/reversed-layer-audio-3239
Audio: a time-reversed layer plays its audio backwards
2026-11-14 17:27:44 +01:00
FredApps 05801ee111 Merge pull request #3210 from vangel76/grid-mask-3121
Effects: Grid, Checkerboard and Circle keep the layer's mask with Blending Mode None
2026-11-14 17:00:32 +01:00
vangel76 fb7ce6928c Merge upstream/main into colstab-sample-size-3125 2026-11-13 19:06:14 +01:00
vangel76 2aefe27278 Merge upstream/main into reversed-layer-audio-3239 2026-11-13 19:06:02 +01:00
Andrea Zorzi a70eb03fa6 #3593: render_frame and render_preview tile times into one contact sheet (#3594)
* #3593: render_frame and render_preview tile `times` into one contact sheet

* contributors: credit andreaaazo by their GitHub profile name

* #3593: contact-sheet cells default to 320 px, sheets stay under 8000 px

With `times`, `max_side` now defaults to 320 per cell instead of 960, so
64 frames of a 16:9 comp make a 2560 x 1440 sheet instead of 7680 x 4320.
`sheet::MAX_SIDE` drops from 8192 to 7680. `labels` that isn't true or
false is a bad-arguments error, and a bridged cell keeps the time asked
for when the app's reply leaves `time` out.
2026-11-13 19:40:11 +05:30
ash b9da77c63d Merge pull request #3571 from arnav-hiwarkar/fix/effect-ranges-easy-batch
Effects: 18 parameters take After Effects' valid ranges (Levels IC, 4-Color Gradient, Change Color, Noise HLS, Grid, Venetian Blinds, Radial Wipe, Scatter, Beam, Transform, Turbulent Displace)
2026-11-12 17:19:57 +05:30
arnav-hiwarkar c0387ea18a #3470, #3472, #3473, #3474, #3475, #3476, #3477, #3480, #3483, #3484, #3487-#3491: test the ends of the new ranges on the GPU
CPU / GPU comparisons at the new ends for Venetian Blinds (width 1, width
and feather 32000), Radial Wipe (feather 32000), Grid (border 2000),
Scatter (1000), Beam (4000), Change Color (1710 degrees) and Turbulent
Displace (seed 100000). The comparison counts infinity on both sides as
equal, so Levels (Individual Controls) gets a direct check that the GPU's
output is finite at the ends of its ranges. The Noise HLS test asserts the
spec's ranges, so it fails without them.
2026-11-12 16:24:17 +05:30
arnav-hiwarkar acf60eeaf3 #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): bound the curve only where it would overflow
The 1e6 bound also capped curves that were finite before (gamma 0.1 on an
HDR value 4 x Input White is 4^10, about 1.05e6). The bound is now 1e30:
1e30 times the widest output range (2e4) is still finite, and every curve
below it renders as before. A test pins 4^10.
2026-11-12 16:24:17 +05:30
arnav-hiwarkar 98d82ae761 #3470: Turbulent Displace: Random Seed takes After Effects' range
Random Seed takes 0..100000, After Effects' recorded valid range (and what
the other effects' Random Seed already takes). The CPU and GPU hash it the
same way with wrapping u32 arithmetic, so any seed costs the same.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar 74bcb30540 #3471: Transform: Scale Height and Scale Width take After Effects' range
Scale Height and Scale Width take -30000..30000 %, After Effects' recorded
valid range in the same units. The CPU and GPU share the matrix
(transform_matrix_px) and the buffer growth, which is capped at 16 Mpx
(transform_grow; the CPU test already renders 100000 %), so larger scales
stay bounded.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar 3bd7de6ad8 #3475: Beam: Starting and Ending Thickness take After Effects' range
Starting and Ending Thickness take 0..4000, After Effects' recorded valid
range in the same units (pixels). The CPU and GPU renderers draw the beam
per pixel from its distance to the segment, within the layer's buffer, so
a thicker beam costs the same.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar c4cd9f1c2e #3477: Scatter: Scatter Amount takes After Effects' range
Scatter Amount takes 0..1000, After Effects' recorded valid range in the
same units (pixels); the slider stays 0..127. The CPU and GPU renderers move
each pixel by one random offset of up to the amount, so a larger amount
costs the same; samples outside the layer are transparent.
Default and slider are unchanged.
2026-11-12 15:47:15 +05:30
arnav-hiwarkar a08240155c #3474: Radial Wipe: Feather takes After Effects' range
Feather takes 0..32000, After Effects' recorded valid range in the same
units. The CPU and GPU renderers evaluate the feather per pixel in closed
form (a Gaussian of the distance to the wedge, radial_wipe_alpha), so a
wide feather costs the same and stays finite.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 29c76bb6b4 #3472, #3473: Venetian Blinds: Width and Feather take After Effects' ranges
Width takes 1..32000 and Feather 0..32000, After Effects' recorded valid
ranges in the same units (pixels). The work stays bounded: the renderers
floor the width at 1 buffer pixel, sum at most 40 Gaussian terms per
pixel, and above 8 widths the feather is the even veil
(venetian_cover; the GPU hands that case to the CPU, as before).
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 85f47ec072 #3476: Grid: Border takes After Effects' range
Border takes 0..2000, After Effects' recorded valid range in the same units
(pixels). The CPU and GPU renderers compute the border per pixel in closed
form, so a wider border costs the same; one wider than the cells covers
them.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar ce4db78896 #3480: Change Color: Hue Transform takes After Effects' range
Hue Transform takes -1800..1800 degrees, After Effects' recorded valid range
in the same units. The CPU and GPU renderers wrap the hue (rem_euclid /
fract_euclid), so five turns render like the hue they end on, at the same
cost.
Default and slider are unchanged.
2026-11-12 15:47:14 +05:30
arnav-hiwarkar 244f9bd846 #3483, #3484: Noise HLS: amounts and Grain Size take After Effects' ranges
Hue, Lightness and Saturation go up to 1000 % and Grain Size down to 0 (it
keeps its wider maximum, 100), After Effects' recorded valid ranges in the
same units. Noise HLS Auto shares the controls and widens with them.
Defaults and sliders are unchanged.

The renderers need no change: hue wraps, lightness and saturation clamp per
pixel after the noise is added, and Grain Size is floored at 0.1 px on the
CPU and the GPU, so the new values cost the same and stay finite.

The amounts were written as "percent" in keyframe text because their range
was 0..100, and Grain Size would become "percent" at 0..100; UNITS rows keep
the units they had.
2026-11-12 15:46:48 +05:30
arnav-hiwarkar 36eecad00f #3538: a cached config's size counts the LUTs it holds 2026-11-12 15:30:14 +05:30
arnav-hiwarkar 363a800d3f #3478, #3479: 4-Color Gradient: Blend and Jitter take After Effects' ranges
Blend goes up to 10000 (it keeps its minimum of 1, below After Effects' 5)
and Jitter up to 500 %, After Effects' recorded valid ranges in the same
units. Defaults and sliders are unchanged.

The CPU and GPU renderers capped Jitter's dither at 100 %, so higher values
would be stored but render as 100. Both now take it from one function,
four_color_jitter, which keeps the dither linear up to 500 %. Blend 10000
is a finite softening (Blend squared) at the same cost.

Jitter was written as "percent" in keyframe text because its range was
0..100; a UNITS row keeps that unit.
2026-11-12 15:14:34 +05:30
arnav-hiwarkar 39a3b565d8 #3487, #3488, #3489, #3490, #3491: Levels (Individual Controls): levels and gamma take After Effects' ranges
Input/Output Black and White of every channel take -10000..10000 and Gamma
goes down to 0 (it keeps its wider maximum, 10), After Effects' recorded
valid ranges in the same units (the .aep mapping is Conv::Plain). Defaults
and the 0..1 / 0.1..3 sliders are unchanged.

Gamma 0 renders as the existing 0.01 floor, an exponent of 100, so an HDR
value a few times Input White would overflow f32 and an infinite curve
times an empty output range is NaN. The curve is now bounded at +-1e6 on
the CPU (Lv::apply) and the GPU (fxt_lv), so the output stays finite over
the whole range; values below the bound are unchanged.

The engine regression (tests/effect_ranges.rs) sets both ends of each row
through prop.set, checks undo/redo, save and reopen, and that a project
saved with the narrower ranges opens with the new ones and its values.
2026-11-12 15:11:21 +05:30
arnav-hiwarkar 4d24233505 #3538: a custom config not read yet is read for its LUT files 2026-11-12 15:07:48 +05:30
arnav-hiwarkar ef137cb2ce #3538: OCIO transform files and configs follow their files through #3493's LUT cache
Move the LUT cache that follows files by size and modification time into
effects::file_cache and use it for ocio::lut::load_file and
ocio_config::load_config. A config's entry depends on the .ocio file, the LUTs
its FileTransforms read and the search-path places it looked for missing ones.
check_lut_files and lut_generation now cover all three caches.
2026-11-12 14:43:54 +05:30
arnav-hiwarkar 0547c51e47 #3538: test that OCIO transform files and configs are read again once they change 2026-11-12 14:33:21 +05:30
arnav-hiwarkar 63520598a0 #3493: test the followed-files limit on a cache of its own
The test filled the shared cache past the limit, which could let go of a
file another test running alongside still follows.
2026-11-12 11:36:40 +05:30
arnav-hiwarkar 82c2a23f07 #3493: keep following the LUT file just read past the followed-files limit
Past 1024 followed files, the trim kept only files with a cached LUT and
dropped the one being noted (its LUT is cached right after), so a later
edit to it went unnoticed. The tests start from an empty folder.
2026-11-12 11:29:57 +05:30
arnav-hiwarkar 63aca62574 #3493: follow LUT files whose LUT left the cache; bound it by bytes too
The watcher stamped only the files whose parsed LUT was still cached, so
with more LUTs than the cache keeps, an edit to an evicted one left the
viewer's frames stale. The cache now remembers the stamp each LUT file was
last read with (bounded separately), and keeps at most 64 LUTs and 512 MiB
of tables. Frames finishing while the viewer empties its cache are refused.
2026-11-12 11:13:12 +05:30
arnav-hiwarkar d503b26e21 #3493: Apply Color LUT reads a LUT file again once it changes on disk
load_lut kept the first result for a path for the life of the process: an
edited file, or one created after a failed load, was never read again.
Each file-backed entry now keeps the file's size and modification time and
is read again once they move; inline .cube text stays cached by content.
The cache keeps at most 32 LUTs (least recently used goes first).
2026-11-12 10:43:41 +05:30
Dylan M. Taylor e339a5b375 Effects: cap Camera Lens Blur and median radii (#2951)
* Effects: cap Camera Lens Blur and median radii

Camera Lens Blur built its iris table (one row per pixel of radius) from an
uncapped Blur Radius before any size check, and the median window is filled
(2r + 1)^2 per row from an uncapped Radius, on the CPU and the GPU. The
radius is now capped where the work is built: 2000 buffer pixels for the
lens iris (UI max 500) and 512 for the median window (UI max 255), on both
paths.

* address review: cap camera lens reach with the spans
2026-11-11 15:53:34 -05:00
Dylan M. Taylor 70a05be71a Simulations: cap the steps a layer time can ask for and bound checkpoints as they are made (#2942)
* Simulations: cap the steps a layer time can ask for and bound checkpoints as they are made

* address review: keep the requested sim state when trimming checkpoints

* address review: drop only the excess sim checkpoints when trimming
2026-11-11 14:38:53 -05:00
Dylan M. Taylor 9aa679f6ad Roto Brush, Object Matte: decode frozen mattes only at the layer's full-resolution grid (#2941)
* Roto Brush, Object Matte: decode frozen mattes only at the layer's full-resolution grid

* address review: frozen matte size mismatch falls back to computed matte

* address review: frozen-size-mismatch test compares against a render with no cache
2026-11-11 14:38:39 -05:00
Dylan M. Taylor dad07b8d5d Puppet: clamp an imported Expansion before padding the outline grid (#2940)
* Puppet: clamp an imported Expansion before padding the outline grid

* address review: non-finite expansion compares with zero expansion in test

* address review: trace over-budget puppet outlines at a coarser resolution; NaN expansion test
2026-11-11 14:38:26 -05:00
ash b03ebc4b83 Merge pull request #3455 from arnav-hiwarkar/fix/3454-lut-path-detection
Apply Color LUT loads files whose path contains LUT_ (#3454)
2026-11-11 19:18:06 +05:30
ash 4f00f11cab Merge pull request #3418 from nanox333/fix/3368-cylinder-radius
Cylinder Wrap: Radius goes up to 30000 % (#3368)
2026-11-11 19:06:30 +05:30
arnav-hiwarkar 6540c9ab44 #3454: Apply Color LUT loads files whose path contains LUT_
load_lut took any source containing "LUT_" for inline .cube text, so a
path like /luts/LUT_Rec709.cube was parsed as text, failed, and the
effect passed the image through. A .cube can only parse with a size
header and data rows on separate lines, so a newline (after trimming,
as the path already is) is what tells text from a path.
2026-11-11 17:48:49 +05:30
arnav-hiwarkarandClaude Opus 5.5 a76bbf05f7 Effects: re-pin the macOS golden hashes #2944 changed by rounding
#2944 (497a056c) normalises each box-blur mean in f64 and rounds once
(it was rounded to f32 first, then scaled), and counts repeated edge pixels
with one multiply instead of adding them one by one. Its PR states results
change only by float rounding, and it added the focused regression test the
golden-test rule asks for (box_row matches a direct sum). The golden hashes
are only enforced on aarch64 macOS, so Linux PR CI passed, and every
completed macOS CI run on main since #2944 has failed (497a056c's own run
was cancelled by a newer push).

Bisected on an M5: the golden tests pass at ed085bd4 and fail at 497a056c
with the same 52 hashes macOS CI reports. All 52 are 32 bpc frames of
effects that blur through box_plane/gauss_plane (Lightning, Advanced
Lightning, Inner/Outer Key, Caustics, Paint Bucket, Vegas); every 8 bpc frame
is unchanged. All 52 frames differ by at most 4.8e-6 per channel, with no new
non-finite values.

Only the pinned hash values change; LIBM_VARIANTS is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:41:14 +05:30
Dylan M. Taylor e2afb4b17c Timewarp: hold Block Size to its declared range and the matching window to a cap (#3024) 2026-11-10 18:00:27 -05:00
Dylan M. Taylor 497a056c07 Box blur: count repeated edge pixels instead of visiting them, and bound the radius (#2944)
* Box blur: count repeated edge pixels instead of visiting them, and bound the radius

* address review: box blur first mean ignores an unused edge term (inf * 0 gave NaN)
2026-11-10 17:58:43 -05:00
Claude 2226af65f2 Cylinder Wrap test: make the 30000 % render check fail on a 400 % clamp (#3368)
The pixel it read was inside the cylinder at 400 % too. Put the axis on the left
edge and read a pixel past the 400 % radius, with a 400 % run showing it is empty.
2026-11-10 15:51:52 +00:00
Claude c96a9b1f6e Cylinder Wrap: Radius goes up to 30000 % (#3368)
The Radius parameter was limited to 400 %, so a larger value was clamped and drew
a smaller cylinder than After Effects' CC Cylinder, whose range the report gives
as 0 to 30000 %. Raise the hard maximum; the slider's visible range is unchanged.
2026-11-10 14:36:12 +00:00
FredApps 43e82587fd #3357: avoid whole-layer cover for Drop Shadow 2026-11-09 18:10:06 +01:00
just_anshhandjustanshh 544eebf904 WebAssembly ABI 2: neighbouring frames for effect plug-ins (#2773) (#3329)
* WebAssembly ABI 2: neighbouring frames for effect plug-ins (#2773)

A wasm plug-in can export ec_api_version 2 and ec_render2 to receive the layer's previous and next frames, their layer times and validity flags. Neighbours come from PluginHost::input_at, are resampled onto the current grid, and are zeroed when absent. ABI 1 modules (ec_render) load and render unchanged; the manifest api stays independent. A layer with an ABI 2 plug-in keys the render cache on layer time; the Plug-in Library accepts ABI 1-2 releases. Docs, help text and tests updated.

* Fix ABI 2 neighbour times on NTSC frame boundaries

	ime * fps is often a few ulps off an integer at rates such as 24000/1001. The old boundary test then took the bracket branch and passed the current frame back as prev (or
ext). Treat a near-integer as a frame boundary within 1e-6 frames, and test 2400 frames at 24000/1001: every boundary must get the frames either side. Also document the loader helpers for the docstring coverage check.

---------

Co-authored-by: justanshh <294019246+justanshh@users.noreply.github.com>
2026-11-09 10:49:53 +01:00
FredApps 9893eb0d8d Naming: descriptive names for the CC effects, Lumetri, Mocha, Keylight and Mercury (#3327)
Related to #1365. Display labels only; effect, command, parameter, panel and serialized ids and the AEP/script match-name tables are unchanged.

- 69 Cycore CC effects, Key Light, Mocha shape, Lumetri Color/Scopes and the Mercury renderer labels get descriptive names; the old names stay case-insensitive lookup and search aliases.
- Backend::parse accepts the new renderer labels, so reported settings round-trip.
- Display names are unique per language in every effect catalog (new test).
- Warp Stabilizer and Roto Brush keep their names until RFC-0029 settles.
- Mapping in docs/feature-naming.md.
2026-11-09 04:51:10 +01:00
vangel76 cea96922fc Merge upstream/main into reversed-layer-audio-3239 2026-11-08 19:16:59 +01:00
vangel76 5f2d88ea5c Merge remote-tracking branch 'upstream/main' into aep-import-lumetri
# Conflicts:
#	crates/aep/src/container/effect_map.rs
#	crates/aep/src/export/effects.rs
#	crates/aep/src/import/effects.rs
#	crates/aep/tests/it/export/effects.rs
#	crates/aep/tests/it/import/effects.rs
#	docs/aep-import.md
2026-11-08 16:32:43 +01:00
vangel76 c229c19ab0 Merge remote-tracking branch 'upstream/main' into colstab-sample-size-3125
# Conflicts:
#	docs/aep-import.md
2026-11-08 16:19:21 +01:00
vangel76 677c3e40df #3125: Color Stabilizer samples a square centred on the pixel, with partial pixels
After Effects 2026 measurements (320x180 ramp, Levels, Black Point at
(80, 90), a 6 px square appearing): a square 7 px to the right or below
reacts from Sample Size 13.5, one 7 px to the left or above only from
15.5, so the area is centred half a pixel right of and below the layer
point. Diagonal squares react at 16, 26 and 38 (corner 7, 10, 14 px on
both axes), which only a square area allows. Sub-pixel Black Points move
the threshold continuously and the response grows in 0.5 steps, so
pixels count with the share the area covers.

The sampler now does that. Re-run of the importer block: the Color
Stabilizer drift above Sample Size 25 is gone.
2026-11-08 16:02:06 +01:00
vangel76 e11164ef6f #3125: Color Stabilizer's Sample Size is the full width of the sampled area
After Effects 2026 reacts to a square 7 px from the sample point at
Sample Size 15 but not at 12, at 17 px from 35 but not at 30, at any
layer size: Sample Size is the full width in layer pixels. EffectCraft
used it as the half-width. The GPU path shares the sampler.
2026-11-08 15:37:26 +01:00
vangel76 67a39d9a2b .aep import: EXtractoR (copy) reads the effect input, not the EXR plane of that name
After Effects' Channel Info kind 2 "(copy)" was imported as R, G, B or A.
EffectCraft's EXtractoR resolves those names through the footage's aux planes
first, and an OpenEXR keeps its own R/G/B/A planes, so "(copy)" read the
original file plane and dropped what an earlier effect did to the channel.

"(copy)" is now a reserved EXtractoR channel name (EXTRACTOR_COPY) that the
CPU and GPU renderers never look up in the aux planes: it is the input's own
channel of the same position. The importer writes it for kind 2.

Render regression: a modified input buffer with same-named R/G/B/A aux planes
of other values; GPU parity case with (copy) on an aux scene with R/G/B.
2026-11-08 02:32:11 +01:00
vangel76 5ae0c1d58c Merge remote-tracking branch 'upstream/main' into aep-import-lumetri 2026-11-07 19:42:21 +01:00
vangel76 998448f94b Audio: a time-reversed layer plays its audio backwards (#3239)
A layer with a negative time stretch was skipped by the mix. It now plays
its audio reversed at its stretched speed, through the same linear
resampler as forward stretch: a reversed read starts at the layer time of
the run's end and is played last to first, so comp sample k plays the
source just before layer_time(k) (at -100 % with the start at the out
point, the exact mirror Backwards gives a forward layer). Backwards on a
reversed layer plays it forwards.

Resampled runs start at the exact source position (from the layer time in
ticks) instead of the grid sample a block starts on, so mixing in frame
blocks and in export's one-second blocks gives the same samples, and they
read one more source sample so a block's last sample blends instead of
holding. Audio effects run on comp time since the layer's start, which
joins up across blocks at any stretch and equals the layer time at 100 %.

Reads are made in pieces of at most MAX_READ source frames, so memory
stays bounded for fast layers and nested fast precomps (the work for
nested 1 % precomps is not bounded; follow-up). A stretch faster than 1 %
is silent as a last-resort guard, with a tolerance so exactly 1 % plays.
A zero or non-finite stretch is skipped.

Fixes #3239
2026-11-07 16:41:19 +01:00