#include #include #include #include #include #include #include #include "cli/options.hpp" #include "core/chain.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 stages = effector::buildChain(options); std::vector failures; // Decoding a batch of photographs takes seconds; on a terminal keep a // single line updated so the wait is not silent. Redirected output stays // clean, since the line would only clutter a log. effector::LoadProgress progress; std::mutex progressMutex; const bool showProgress = isatty(fileno(stderr)) != 0 && options.images.size() > 1; if (showProgress) { progress.onFile = [&](std::size_t done, std::size_t total, const std::string& path) { std::lock_guard lock(progressMutex); std::cerr << "\r\033[Kreading " << done << "/" << total << " " << std::filesystem::path(path).filename().string() << std::flush; }; } auto sources = effector::loadImages(options.images, failures, showProgress ? &progress : nullptr); if (showProgress) std::cerr << "\r\033[K" << std::flush; for (const auto& failure : failures) { std::cerr << "effector: skipping " << failure.path << ": " << failure.message << "\n"; } if (sources.empty() && effector::chainNeedsSources(stages)) { 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); const auto start = std::chrono::steady_clock::now(); const auto image = effector::renderChain(stages, sources, options.width, options.height, rng); const auto renderMillis = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); image.save(options.output); std::cout << options.output << " " << options.width << "x" << options.height << " effect=" << effector::chainSlug(stages) << " 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; } }