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,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <gtkmm.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "cli/options.hpp"
|
||||
#include "core/effect.hpp"
|
||||
#include "core/image.hpp"
|
||||
|
||||
namespace effector {
|
||||
|
||||
class EffectorWindow : public Gtk::ApplicationWindow {
|
||||
public:
|
||||
explicit EffectorWindow(const Options& initial);
|
||||
~EffectorWindow() override;
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
Gtk::Widget& buildSidebar();
|
||||
Gtk::Widget& buildPreview();
|
||||
void buildParamWidgets();
|
||||
ParamValues paramValues() const;
|
||||
void applyInitial(const Options& initial);
|
||||
|
||||
void requestRender(); // coalesced: restarts the short debounce timer
|
||||
void startRender(); // starts a worker, or marks the result stale
|
||||
void onAddImages();
|
||||
void onImagesChosen(const Glib::RefPtr<Gio::AsyncResult>& result,
|
||||
const Glib::RefPtr<Gtk::FileDialog>& dialog);
|
||||
void addPaths(const std::vector<std::string>& paths);
|
||||
void appendSourceRow(const std::string& path);
|
||||
void removeSource(Gtk::ListBoxRow* row);
|
||||
|
||||
void onEffectChanged();
|
||||
void onParamChanged();
|
||||
void onSeedEntered();
|
||||
void onRecreate();
|
||||
void onSave();
|
||||
void onSaveChosen(const Glib::RefPtr<Gio::AsyncResult>& result,
|
||||
const Glib::RefPtr<Gtk::FileDialog>& dialog);
|
||||
|
||||
void onRenderFinished(); // runs on the main thread via the dispatcher
|
||||
|
||||
// Sources are handed to the worker as an immutable snapshot, so adding or
|
||||
// removing an image while a render is in flight cannot race with it.
|
||||
using SourceList = std::vector<Image>;
|
||||
template <typename Mutate>
|
||||
void mutateSources(Mutate&& mutate);
|
||||
|
||||
void showError(const std::string& message);
|
||||
void clearError();
|
||||
|
||||
// Model
|
||||
std::shared_ptr<const SourceList> sources_ = std::make_shared<SourceList>();
|
||||
std::vector<EffectPtr> effects_;
|
||||
EffectPtr effect_;
|
||||
Image result_;
|
||||
std::uint64_t seed_ = 0;
|
||||
bool suspendRender_ = true;
|
||||
|
||||
// Render job
|
||||
Glib::Dispatcher renderDone_;
|
||||
std::thread worker_;
|
||||
std::mutex resultMutex_;
|
||||
bool rendering_ = false;
|
||||
bool staleResult_ = false;
|
||||
sigc::connection debounce_;
|
||||
Image pendingImage_;
|
||||
std::string pendingError_;
|
||||
double pendingMillis_ = 0.0;
|
||||
|
||||
// Widgets
|
||||
Gtk::Label banner_;
|
||||
Gtk::ListBox sourceList_;
|
||||
Gtk::SpinButton widthSpin_;
|
||||
Gtk::SpinButton heightSpin_;
|
||||
Gtk::Grid paramGrid_;
|
||||
Gtk::Entry seedEntry_;
|
||||
Gtk::DropDown effectDropdown_;
|
||||
Gtk::Button recreateButton_{"Recreate"};
|
||||
Gtk::Button saveButton_{"Save As…"};
|
||||
Gtk::Picture picture_;
|
||||
Gtk::Stack previewStack_;
|
||||
Gtk::Label placeholder_;
|
||||
Gtk::Spinner spinner_;
|
||||
Gtk::Label status_;
|
||||
|
||||
struct ParamWidget {
|
||||
ParamSpec spec;
|
||||
Gtk::Widget* widget = nullptr;
|
||||
};
|
||||
std::vector<ParamWidget> paramWidgets_;
|
||||
std::vector<std::unique_ptr<Gtk::Widget>> ownedWidgets_;
|
||||
};
|
||||
|
||||
} // namespace effector
|
||||
Reference in New Issue
Block a user