A picture with a title, a caption and a credit meant typing the same font, size and colour into every text stage, and changing the look meant typing them all again. A project now holds named styles: a stage follows one, and editing it moves every stage that follows it at once. The front-ends learn nothing effect-specific. A ParamSpec can be marked .styled(), saying a style carries it rather than the stage, and Effect::styleKind() names the pool it comes from -- so styleSpecs(kind) is to a style what params() is to a stage, and a second text-ish effect would slot into the styles that already exist. The look is what a style carries; the words and the placement stay on the stage, which is what lets two stages share a typography in two different corners. stageValues() resolves one in a single place, and a stage keeps a full set of values of its own beside the style, so switching it to Custom, dropping the style, or opening the project in a build that does not know it renders exactly the same picture. A style moves stages together; it never holds the only copy of anything. The command line builds no styles and passes none, so it renders what it always did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
88 lines
3.2 KiB
C++
88 lines
3.2 KiB
C++
#include "core/chain.hpp"
|
|
|
|
namespace effector {
|
|
|
|
std::vector<const ChainStage*> enabledStages(const std::vector<ChainStage>& stages) {
|
|
std::vector<const ChainStage*> running;
|
|
running.reserve(stages.size());
|
|
for (const auto& stage : stages) {
|
|
if (stage.enabled && stage.effect) running.push_back(&stage);
|
|
}
|
|
return running;
|
|
}
|
|
|
|
ParamValues stageValues(const ChainStage& stage, const StyleSet& styles) {
|
|
ParamValues values = stage.effect->defaults();
|
|
for (const auto& [key, value] : stage.values) values[key] = value;
|
|
|
|
const Style* style = findStyle(styles, stage.effect->styleKind(), stage.style);
|
|
if (style != nullptr) {
|
|
// Only the params the effect marked styled(): the words and the
|
|
// placement stay the stage's own, which is what lets two stages share
|
|
// one look without sharing a position.
|
|
for (const auto& spec : stage.effect->params()) {
|
|
if (!spec.inStyle) continue;
|
|
const auto found = style->values.find(spec.key);
|
|
if (found != style->values.end()) values[spec.key] = found->second;
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
Image renderChain(const std::vector<ChainStage>& stages, const std::vector<Image>& sources,
|
|
int width, int height, Rng& rng, const StyleSet& styles) {
|
|
if (stages.empty()) throw EffectError("The effect chain is empty.");
|
|
for (const auto& stage : stages) {
|
|
if (!stage.effect) throw EffectError("The effect chain has an empty stage.");
|
|
}
|
|
|
|
// A disabled stage never reaches the render at all: it is not handed an
|
|
// image and draws nothing from the shared rng, so switching one off gives
|
|
// back exactly the picture the chain made before it was added.
|
|
const auto running = enabledStages(stages);
|
|
if (running.empty()) throw EffectError("Every effect in the chain is disabled.");
|
|
|
|
Image image;
|
|
for (std::size_t i = 0; i < running.size(); ++i) {
|
|
const auto& stage = *running[i];
|
|
|
|
const auto values = stageValues(stage, styles);
|
|
|
|
if (i == 0) {
|
|
image = stage.effect->render(sources, width, height, rng, values);
|
|
} else {
|
|
// Feeding the previous result back in as the only source is what
|
|
// cascading means here; effects need to know nothing about it.
|
|
const std::vector<Image> previous = {std::move(image)};
|
|
image = stage.effect->render(previous, width, height, rng, values);
|
|
}
|
|
}
|
|
return image;
|
|
}
|
|
|
|
bool chainNeedsSources(const std::vector<ChainStage>& stages) {
|
|
const auto running = enabledStages(stages);
|
|
if (running.empty()) return false;
|
|
return running.front()->effect->needsSources();
|
|
}
|
|
|
|
std::string chainTitle(const std::vector<ChainStage>& stages) {
|
|
std::string text;
|
|
for (const auto* stage : enabledStages(stages)) {
|
|
if (!text.empty()) text += " → ";
|
|
text += stage->effect->title();
|
|
}
|
|
return text;
|
|
}
|
|
|
|
std::string chainSlug(const std::vector<ChainStage>& stages) {
|
|
std::string text;
|
|
for (const auto* stage : enabledStages(stages)) {
|
|
if (!text.empty()) text += "-";
|
|
text += stage->effect->name();
|
|
}
|
|
return text;
|
|
}
|
|
|
|
} // namespace effector
|