Split effects into image and video effects, with VHS tracking as the first video effect
Effect::video() says which kind an effect is. The Add effect menu lists the two kinds in sections of their own, each scrolling on its own, and offers video effects only in a video project; a video stage carries a film icon in the chain and, in a still, is dimmed and skipped like a stage switched off. enabledStages takes whether the render is a video and is the one place that leaves video stages out of a still. --help groups the video effects under a heading of their own, --list-effects marks them, and a video effect given for a still is skipped with a warning. VideoEffect is the base every video effect builds on, and keeps the rules a moving stage has to follow: it takes one value from the shared rng, the same on every frame, and hands the effect steady and per-frame generators and loop-rounded time through a Moment, so a subclass writes only animate(). VHS tracking is the first, and the one to copy: the colour planes sway and shake and a tracking bar rolls down the picture. That motion moves out of channel split, which is an image effect again and renders stills exactly as before; forVideo() goes with it. Version 0.7.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,7 +95,8 @@ first stage ever sees the sources, so `chainNeedsSources` (`src/core/chain.cpp`)
|
||||
just asks the first stage — a chain led by blur renders with no images loaded at
|
||||
all, and both front-ends gate on that rather than on "are there any images".
|
||||
|
||||
Adding an effect: new `src/effects/<name>.{hpp,cpp}` subclassing `Effect`, one
|
||||
Adding an effect: new `src/effects/<name>.{hpp,cpp}` subclassing `Effect` (or
|
||||
`VideoEffect` for one that moves, see "Video projects"), one
|
||||
line in the registry list in `src/core/registry.cpp`, one line in the
|
||||
`effector_core` sources in `CMakeLists.txt`. Nothing else — it shows up in the
|
||||
CLI, in the chain menu and as a cascade stage on its own.
|
||||
@@ -181,22 +182,39 @@ like `canvasFormats()` — a preset names a canvas format plus a length and rate
|
||||
and like a format it is the presentation, never the truth: the render goes by
|
||||
duration and fps. Adding a preset is a line there.
|
||||
|
||||
Time reaches an effect as a `FrameTime` (`src/core/effect.hpp`; the default,
|
||||
`fps == 0`, is a still) through `Effect::renderFrame`, which falls back to
|
||||
`render` — an effect that does not move overrides nothing. One that moves
|
||||
overrides `renderFrame` and `animated(values)`, and marks its motion params
|
||||
`.forVideo()`: the interface hides those in a still project (declaratively,
|
||||
like `.styled()`), the CLI still has their flags. A still must render byte for
|
||||
byte what it did before the effect learned to move.
|
||||
Effects are of two kinds, and `Effect::video()` says which. An *image effect*
|
||||
(the default) makes a picture and holds still in a clip; it overrides only
|
||||
`render`, which `Effect::renderFrame` falls back to. A *video effect* is motion
|
||||
and nothing else. `enabledStages(stages, video)` is where the kinds meet the
|
||||
chain: in a still, a video stage is left out exactly like a disabled one,
|
||||
drawing nothing from the rng. So `renderChain` (by `frame.video()`),
|
||||
`chainNeedsSources`, `chainTitle` and `chainSlug` all take whether the render is
|
||||
a video. The interface builds the split from `video()` alone: two sections in
|
||||
the Add effect menu, with the video section insensitive in a still, a
|
||||
`camera-video-symbolic` icon on a video stage's row, and the row dimmed while a
|
||||
still skips it. The CLI groups `--help` the same way and warns when it skips one.
|
||||
|
||||
A video effect subclasses `VideoEffect` (`src/effects/video_effect.hpp`), not
|
||||
`Effect`, and writes only `animate(input, values, const Moment& at)`. The base
|
||||
keeps the rules. It takes the input crop and exactly one value from the shared
|
||||
rng, the same on every frame, and hands out randomness through the `Moment`:
|
||||
`at.steady()` gives the same draws every frame (phases, directions),
|
||||
`at.fresh()` gives new draws per frame (shake, noise). It hands out time as
|
||||
`at.cycle(perSecond)`, rounded through `loopRate` to a whole number of cycles so
|
||||
the clip loops without a jump. `TrackingEffect` (`src/effects/tracking.cpp`,
|
||||
"VHS tracking") is the reference: copy its layout (params, a `Settings` struct,
|
||||
steady draws, time, fresh draws, one parallel pixel pass) for the next one.
|
||||
Registering it is the same one line in `registry.cpp`, after the comment that
|
||||
starts the video effects. There is no per-param video flag: motion is a video
|
||||
effect of its own, never extra params on an image effect.
|
||||
|
||||
`renderFrames` (`src/core/chain.cpp`) renders a clip. Every frame starts from
|
||||
`Rng(seed)`, so a stage that does not move draws the same values every frame
|
||||
and holds still. A moving effect must therefore leave the shared rng in the
|
||||
same state on every frame — the channel split draws its per-frame shake from a
|
||||
generator of its own, seeded by one draw — or everything after it would flicker.
|
||||
The stages before the first `animated()` one are rendered once and their image
|
||||
and rng state reused; the `video` tests assert that frame k is exactly
|
||||
`renderChain(..., FrameTime{k, ...})`, so that shortcut can never change output.
|
||||
`Rng(seed)`, so an image effect draws the same values every frame and holds
|
||||
still, and because `VideoEffect` leaves the shared rng in one state, the stages
|
||||
after a video effect hold still too. The stages before the first video effect
|
||||
are rendered once and their image and rng state reused. The `video` tests
|
||||
assert that frame k is exactly `renderChain(..., FrameTime{k, ...})`, so that
|
||||
shortcut can never change output.
|
||||
The determinism contract covers the frames; the encoded file is whatever the
|
||||
machine's ffmpeg makes of them.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user