Cascade effects through a chain
renderChain runs a list of stages in order: stage 0 works on the loaded sources, every later one on the output of the stage before it, so blur after scanlines softens the scanlines rather than the photographs. The effects themselves are untouched — none of them knows whether its input came from disk. All stages share one Rng and run in sequence, so a seed still reproduces the cascade byte for byte. The sidebar grows an "Effect chain" list: add a stage from a menu built from the registry, reorder it, remove it, and edit the selected stage's params underneath. Each stage keeps its own values. The CLI takes the same chain from repeated -e, with every flag going to the stage whose effect declares it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/chain.hpp"
|
||||
#include "core/registry.hpp"
|
||||
#include "core/rng.hpp"
|
||||
#include "effects/blur.hpp"
|
||||
@@ -200,6 +201,46 @@ int main() {
|
||||
check(threw, "no sources throws");
|
||||
}
|
||||
|
||||
std::cout << "chain\n";
|
||||
{
|
||||
std::vector<Image> sources = {gradient(1200, 800, 10), gradient(640, 480, 90)};
|
||||
|
||||
ChainStage scanlines{effectByName("scanlines"), {{"strip_height", 6L}}};
|
||||
ChainStage blur{effectByName("blur"), {{"radius", 6L}}};
|
||||
|
||||
Rng single(5);
|
||||
const auto direct = renderChain({scanlines}, sources, 400, 300, single);
|
||||
const auto alone = render(sources, 400, 300, 5, {{"strip_height", 6L}});
|
||||
check(sameBytes(direct, alone), "a one stage chain matches the effect on its own");
|
||||
|
||||
Rng a(5);
|
||||
Rng b(5);
|
||||
const auto cascaded = renderChain({scanlines, blur}, sources, 400, 300, a);
|
||||
const auto again = renderChain({scanlines, blur}, sources, 400, 300, b);
|
||||
check(cascaded.width() == 400 && cascaded.height() == 300, "the chain keeps the canvas size");
|
||||
check(sameBytes(cascaded, again), "the same seed reproduces the cascade");
|
||||
check(!sameBytes(cascaded, direct), "a second stage changes the image");
|
||||
|
||||
// Order matters: blurring scanlines is not the same as scanning a blur.
|
||||
Rng forward(5);
|
||||
Rng backward(5);
|
||||
const auto blurLast = renderChain({scanlines, blur}, sources, 400, 300, forward);
|
||||
const auto blurFirst = renderChain({blur, scanlines}, sources, 400, 300, backward);
|
||||
check(!sameBytes(blurLast, blurFirst), "the stage order changes the image");
|
||||
|
||||
check(chainTitle({scanlines, blur}) == "Scanlines \u2192 Blur", "the chain has a readable title");
|
||||
check(chainSlug({scanlines, blur}) == "scanlines-blur", "the chain has a file name slug");
|
||||
|
||||
bool threw = false;
|
||||
try {
|
||||
Rng rng(1);
|
||||
renderChain({}, sources, 100, 100, rng);
|
||||
} catch (const EffectError&) {
|
||||
threw = true;
|
||||
}
|
||||
check(threw, "an empty chain throws");
|
||||
}
|
||||
|
||||
std::cout << "image io\n";
|
||||
{
|
||||
const auto path = std::filesystem::temp_directory_path() / "effector_test_roundtrip.png";
|
||||
|
||||
Reference in New Issue
Block a user