Add Strip roll, a video effect that rolls the picture in Scanlines' strips
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2095,6 +2095,99 @@ int main() {
|
||||
check(alone, "a tile swap clip's frame is exactly that frame rendered on its own");
|
||||
}
|
||||
|
||||
// Strip roll builds its frames out of the sources too, and rolls the
|
||||
// picture in each strip through its image.
|
||||
{
|
||||
const auto roll = effectByName("striproll");
|
||||
check(roll->video() && roll->needsSources(),
|
||||
"strip roll is a video effect built from the sources");
|
||||
ChainStage rollStage = stage("striproll", {{"roll_height", 6L}, {"roll_speed", 40L}});
|
||||
ChainStage scanStage = stage("scanlines");
|
||||
effector::Rng withRoll(3);
|
||||
effector::Rng withoutRoll(3);
|
||||
check(sameBytes(renderChain({rollStage, scanStage}, sources, 80, 48, withRoll),
|
||||
renderChain({scanStage}, sources, 80, 48, withoutRoll)),
|
||||
"a still skips strip roll as though it were not there");
|
||||
bool refused = false;
|
||||
try {
|
||||
effector::Rng rng(1);
|
||||
renderChain({rollStage}, {}, 80, 48, rng, {}, FrameTime{0, 8, 8});
|
||||
} catch (const EffectError&) {
|
||||
refused = true;
|
||||
}
|
||||
check(refused, "strip roll with no sources is refused");
|
||||
|
||||
const std::vector<effector::Image> photos = {gradient(60, 40, 20), gradient(50, 30, 150)};
|
||||
auto frameOf = [&](const ChainStage& at, int index) {
|
||||
effector::Rng rng(11);
|
||||
return renderChain({at}, photos, 80, 48, rng, {}, FrameTime{index, 8, 8});
|
||||
};
|
||||
effector::Rng rollA(9);
|
||||
effector::Rng rollB(9);
|
||||
roll->renderFrame(photos, 80, 48, rollA, rollStage.values, FrameTime{3, 8, 8});
|
||||
roll->renderFrame(photos, 80, 48, rollB, rollStage.values, FrameTime{5, 8, 8});
|
||||
check(rollA.next() == rollB.next(), "strip roll leaves the shared rng in one state");
|
||||
|
||||
// Every row of a strip is a row of one photograph, turned round.
|
||||
auto rowsOfPhotos = [&](const effector::Image& image) {
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
bool found = false;
|
||||
for (const auto& photo : photos) {
|
||||
for (int py = 0; !found && py < photo.height(); ++py) {
|
||||
for (int shift = 0; !found && shift < photo.width(); ++shift) {
|
||||
bool same = true;
|
||||
for (int x = 0; same && x < image.width(); ++x) {
|
||||
const int px = (x + shift) % photo.width();
|
||||
same = std::equal(image.row(y) + x * 3, image.row(y) + x * 3 + 3,
|
||||
photo.row(py) + px * 3);
|
||||
}
|
||||
found = same;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
check(rowsOfPhotos(frameOf(rollStage, 0)) && rowsOfPhotos(frameOf(rollStage, 5)),
|
||||
"its strips are rows of the photographs");
|
||||
bool rolled = false;
|
||||
for (int index = 1; index < 8; ++index) {
|
||||
rolled = rolled || !sameBytes(frameOf(rollStage, 0), frameOf(rollStage, index));
|
||||
}
|
||||
check(rolled, "its strips roll over the clip");
|
||||
check(sameBytes(frameOf(rollStage, 0), frameOf(rollStage, 8)),
|
||||
"and the frame after the last is the first again");
|
||||
ChainStage sideways = rollStage;
|
||||
sideways.values["roll_axis"] = std::string("horizontal");
|
||||
sideways.values["roll_direction"] = std::string("random");
|
||||
sideways.values["roll_keep_order"] = true;
|
||||
check(rowsOfPhotos(frameOf(sideways, 3)) &&
|
||||
!sameBytes(frameOf(sideways, 0), frameOf(sideways, 3)) &&
|
||||
sameBytes(frameOf(sideways, 0), frameOf(sideways, 8)),
|
||||
"sideways, its strips slide along their rows and loop");
|
||||
ChainStage frozenRoll = rollStage;
|
||||
frozenRoll.values["roll_speed"] = 0L;
|
||||
check(sameBytes(frameOf(frozenRoll, 0), frameOf(frozenRoll, 5)),
|
||||
"at a speed of 0 it holds still");
|
||||
|
||||
const std::vector<ChainStage> rollChain = {rollStage, stage("grain")};
|
||||
std::vector<effector::Image> clip;
|
||||
effector::renderFrames(rollChain, sources, 80, 48, 21, {}, video,
|
||||
[&](int, effector::Image&& frame) {
|
||||
clip.push_back(std::move(frame));
|
||||
return true;
|
||||
});
|
||||
bool alone = clip.size() == 8;
|
||||
for (const int index : {0, 4, 7}) {
|
||||
effector::Rng rng(21);
|
||||
alone = alone && sameBytes(effector::renderChain(rollChain, sources, 80, 48, rng,
|
||||
{}, FrameTime{index, 8, 8}),
|
||||
clip[static_cast<std::size_t>(index)]);
|
||||
}
|
||||
check(alone, "a strip roll clip's frame is exactly that frame rendered on its own");
|
||||
}
|
||||
|
||||
// The video transforms made from image effects. Each gets a setting
|
||||
// that moves plenty, one without its per-frame noise (which, drawn
|
||||
// anew on every frame, is the one thing that does not loop), and one
|
||||
|
||||
Reference in New Issue
Block a user