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:
@@ -0,0 +1,70 @@
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "cli/options.hpp"
|
||||
#include "core/effect.hpp"
|
||||
#include "core/image.hpp"
|
||||
#include "core/registry.hpp"
|
||||
#include "core/rng.hpp"
|
||||
#include "gui/app.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
int renderHeadless(const effector::Options& options) {
|
||||
const auto effect = effector::effectByName(options.effect);
|
||||
|
||||
std::vector<effector::LoadFailure> failures;
|
||||
auto sources = effector::loadImages(options.images, failures);
|
||||
for (const auto& failure : failures) {
|
||||
std::cerr << "effector: skipping " << failure.path << ": " << failure.message << "\n";
|
||||
}
|
||||
if (sources.empty()) {
|
||||
std::cerr << "effector: no usable source images\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
const std::uint64_t seed = options.seed.value_or(effector::Rng::randomSeed());
|
||||
effector::Rng rng(seed);
|
||||
|
||||
auto values = effect->defaults();
|
||||
for (const auto& [key, value] : options.params) values[key] = value;
|
||||
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
const auto image = effect->render(sources, options.width, options.height, rng, values);
|
||||
const auto renderMillis =
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
image.save(options.output);
|
||||
|
||||
std::cout << options.output << " " << options.width << "x" << options.height
|
||||
<< " effect=" << effect->name() << " seed=" << seed << " render="
|
||||
<< std::fixed << std::setprecision(1) << renderMillis << " ms\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
const auto options = effector::parseOptions(argc, argv);
|
||||
|
||||
if (options.showHelp) {
|
||||
std::cout << effector::usage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (options.listEffects) {
|
||||
for (const auto& effect : effector::effects()) {
|
||||
std::cout << effect->name() << "\t" << effect->title() << " - "
|
||||
<< effect->description() << "\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!options.output.empty()) return renderHeadless(options);
|
||||
|
||||
return effector::runGui(options, argc, argv);
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "effector: " << error.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user