Effector: C++/CMake image effects app

Port of the Python prototype to C++20 with a CMake build:

- effector_core: GUI-free static library (effects, image IO via gdk-pixbuf,
  declarative param specs, registry, portable seeded RNG, thread helper)
- scanlines effect: plans strips from the seeded RNG, then copies pixels in
  parallel with memcpy and doubling horizontal tiling
- gtkmm 4 interface: native file dialogs, widgets generated from the param
  specs, coalesced background renders, preview as a GDK memory texture
- CLI: flags generated from the effect registry, headless rendering with no
  display required
- ctest suite covering the RNG, registry, effect geometry and image round trip

The Python prototype is kept under python-reference/.
This commit is contained in:
hans
2026-09-18 14:28:13 +02:00
commit 925a719da0
36 changed files with 2859 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
#include "core/params.hpp"
#include <algorithm>
namespace effector {
std::string ParamSpec::flag() const {
std::string flag = "--" + key;
std::replace(flag.begin(), flag.end(), '_', '-');
return flag;
}
ParamSpec ParamSpec::makeInt(std::string key, std::string label, long defaultValue, long minimum,
long maximum, std::string help, long step) {
ParamSpec spec;
spec.type = Type::Int;
spec.key = std::move(key);
spec.label = std::move(label);
spec.help = std::move(help);
spec.defaultValue = static_cast<double>(defaultValue);
spec.minimum = static_cast<double>(minimum);
spec.maximum = static_cast<double>(maximum);
spec.step = static_cast<double>(step);
return spec;
}
ParamSpec ParamSpec::makeDouble(std::string key, std::string label, double defaultValue,
double minimum, double maximum, std::string help, double step,
int digits) {
ParamSpec spec;
spec.type = Type::Double;
spec.key = std::move(key);
spec.label = std::move(label);
spec.help = std::move(help);
spec.defaultValue = defaultValue;
spec.minimum = minimum;
spec.maximum = maximum;
spec.step = step;
spec.digits = digits;
return spec;
}
ParamSpec ParamSpec::makeBool(std::string key, std::string label, bool defaultValue,
std::string help) {
ParamSpec spec;
spec.type = Type::Bool;
spec.key = std::move(key);
spec.label = std::move(label);
spec.help = std::move(help);
spec.defaultValue = defaultValue ? 1.0 : 0.0;
return spec;
}
ParamSpec ParamSpec::makeChoice(std::string key, std::string label, std::string defaultChoice,
std::vector<std::string> choices, std::string help) {
ParamSpec spec;
spec.type = Type::Choice;
spec.key = std::move(key);
spec.label = std::move(label);
spec.help = std::move(help);
spec.defaultChoice = std::move(defaultChoice);
spec.choices = std::move(choices);
return spec;
}
namespace {
const ParamValue* find(const ParamValues& values, const std::string& key) {
const auto it = values.find(key);
return it == values.end() ? nullptr : &it->second;
}
} // namespace
long paramInt(const ParamValues& values, const std::string& key, long fallback) {
if (const auto* value = find(values, key)) {
if (const auto* v = std::get_if<long>(value)) return *v;
if (const auto* v = std::get_if<double>(value)) return static_cast<long>(*v);
if (const auto* v = std::get_if<bool>(value)) return *v ? 1 : 0;
}
return fallback;
}
double paramDouble(const ParamValues& values, const std::string& key, double fallback) {
if (const auto* value = find(values, key)) {
if (const auto* v = std::get_if<double>(value)) return *v;
if (const auto* v = std::get_if<long>(value)) return static_cast<double>(*v);
}
return fallback;
}
bool paramBool(const ParamValues& values, const std::string& key, bool fallback) {
if (const auto* value = find(values, key)) {
if (const auto* v = std::get_if<bool>(value)) return *v;
if (const auto* v = std::get_if<long>(value)) return *v != 0;
}
return fallback;
}
std::string paramChoice(const ParamValues& values, const std::string& key,
const std::string& fallback) {
if (const auto* value = find(values, key)) {
if (const auto* v = std::get_if<std::string>(value)) return *v;
}
return fallback;
}
} // namespace effector