Let effects declare whether they need source images

Whether an effect works from the loaded photographs is now part of the
declarative plug-in contract, next to params(): Effect::needsSources()
defaults to true, and blur — which only softens what it is handed — says
no and blurs an empty canvas when given nothing.

Only the first stage of a cascade ever sees the sources, so
chainNeedsSources() just asks the first stage. Both front-ends gate on
that instead of on "are there any images": the CLI only refuses an empty
batch when the chain wants one, and the interface renders a blur-led
chain with no images loaded, saying whichever thing is actually missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Si
2026-09-18 15:49:16 +02:00
co-authored by Claude Opus 5
parent a82fba7886
commit 4739d39a51
10 changed files with 112 additions and 48 deletions
+29 -7
View File
@@ -194,13 +194,12 @@ int main() {
const auto pinnedAgain = renderBlur(sources, 300, 200, 6, {{"first_source", true}});
check(sameBytes(pinned, pinnedAgain), "first_source is reproducible");
bool threw = false;
try {
renderBlur({}, 100, 100, 1, {});
} catch (const EffectError&) {
threw = true;
}
check(threw, "no sources throws");
// Blur declares that it needs no sources, so with none it softens the
// empty canvas it starts from instead of refusing to run.
const auto blank = renderBlur({}, 100, 100, 1, {});
bool allBlack = blank.width() == 100 && blank.height() == 100;
for (std::size_t i = 0; i < blank.byteSize(); ++i) allBlack = allBlack && blank.data()[i] == 0;
check(allBlack, "without sources blur renders an empty canvas");
}
std::cout << "chain\n";
@@ -241,6 +240,29 @@ int main() {
threw = true;
}
check(threw, "an empty chain throws");
// Sources are an effect's own business: scanlines cuts strips out of
// them, blur only softens what it is handed and runs without any.
check(effectByName("scanlines")->needsSources(), "scanlines needs source images");
check(!effectByName("blur")->needsSources(), "blur needs no source images");
check(chainNeedsSources({scanlines, blur}), "a chain led by scanlines needs sources");
check(!chainNeedsSources({blur, scanlines}),
"a chain led by blur needs none, later stages see the stage before them");
check(!chainNeedsSources({}), "an empty chain needs no sources");
Rng bare(5);
const auto sourceless = renderChain({blur}, {}, 200, 150, bare);
check(sourceless.width() == 200 && sourceless.height() == 150,
"blur renders a canvas without sources");
bool scanlinesThrew = false;
try {
Rng rng(1);
renderChain({scanlines}, {}, 100, 100, rng);
} catch (const EffectError&) {
scanlinesThrew = true;
}
check(scanlinesThrew, "scanlines still refuses to run without sources");
}
std::cout << "project\n";