CaptureMoment 0.1.0
Free and open-source non-destructive photo editor
Loading...
Searching...
No Matches
image_controller_base.cpp
Go to the documentation of this file.
1
8
15
16#include <QMetaObject>
17#include <algorithm>
18
19#include <spdlog/spdlog.h>
20
22
24 : QObject(parent)
25{
26 spdlog::debug("[ImageControllerBase::ImageControllerBase]: Constructing ImageControllerBase");
27
28 m_operation_state_manager = std::make_unique<Managers::OperationStateManager>();
29 spdlog::info("[ImageControllerBase::ImageControllerBase]: Initialized OperationStateManager");
30
31 m_operation_model_manager = std::make_unique<Models::Manager::OperationModelManager>();
32 spdlog::info("[ImageControllerBase::ImageControllerBase]: Initialized OperationModelManager");
33
34 if (!m_operation_model_manager->createBasicAdjustmentModels())
35 {
36 spdlog::critical("[ImageControllerBase::ImageControllerBase]: Failed to create basic adjustment models.");
37 throw std::runtime_error("[ImageControllerBase::ImageControllerBase]: Critical failure during model creation.");
38 }
39
40 m_display_manager = std::make_unique<Display::DisplayManager>(this);
41 spdlog::info("[ImageControllerBase::ImageControllerBase]: Initialized DisplayManager");
42
43 m_engine = std::make_shared<Core::Engine::PhotoEngine>();
44 spdlog::info("[ImageControllerBase::ImageControllerBase]: Initialized PhotoEngine");
45
47 spdlog::debug("[ImageControllerBase::ImageControllerBase]: Completed construction");
48}
49
51{
52 spdlog::debug("[ImageControllerBase::~ImageControllerBase]: Destroying ImageControllerBase");
53 m_worker_thread.quit();
54 m_worker_thread.wait();
55 spdlog::debug("[ImageControllerBase::~ImageControllerBase]: Worker thread stopped and destroyed");
56}
57
59{
60 if (!model) {
61 spdlog::warn("[ImageControllerBase::registerModel]: Attempting to register nullptr");
62 return;
63 }
64
65 if (std::find(m_registered_models.begin(), m_registered_models.end(), model) != m_registered_models.end()) {
66 spdlog::debug("[ImageControllerBase::registerModel]: Model already registered");
67 return;
68 }
69
70 m_registered_models.push_back(model);
71 spdlog::debug("[ImageControllerBase::registerModel]: Model registered. Total models: {}", m_registered_models.size());
72}
73
74void ImageControllerBase::loadImage(const QString& file_path)
75{
76 if (file_path.isEmpty()) {
77 spdlog::warn("[ImageControllerBase::loadImage]: Empty file path provided");
78 emit imageLoadFailed("Empty file path");
79 return;
80 }
81
82 spdlog::info("[ImageControllerBase::loadImage]: Calling method-thread doLoadImage() Loading {}", file_path.toStdString());
83
84 // Run on worker thread to avoid blocking UI
85 QMetaObject::invokeMethod(this, [this, file_path]() {
86 doLoadImage(file_path);
87 }, Qt::QueuedConnection);
88}
89
90void ImageControllerBase::loadImageFromUrl(const QUrl& file_url)
91{
92 if (file_url.isEmpty()) {
93 spdlog::warn("[ImageControllerBase::loadImageFromUrl]: Empty file URL received");
94 emit imageLoadFailed("Empty file URL");
95 return;
96 }
97
98 QString native_path = file_url.toLocalFile();
99
100 if (native_path.isEmpty()) {
101 spdlog::warn("[ImageControllerBase::loadImageFromUrl]: Failed to convert URL to local file path: {}", file_url.toString().toStdString());
102
103 if (!file_url.isLocalFile()) {
104 spdlog::warn("[ImageControllerBase::loadImageFromUrl]: Selected URL is not a local file: {}", file_url.toString().toStdString());
105 emit imageLoadFailed("Selected URL is not a local file");
106 return;
107 } else {
108 spdlog::warn("[ImageControllerBase::loadImageFromUrl]: Failed to convert URL to local file path: {}", file_url.toString().toStdString());
109 emit imageLoadFailed("Failed to convert URL to local file path");
110 return;
111 }
112 }
113
114 spdlog::info("[ImageControllerBase::loadImageFromUrl]: Converted URL to native path: {}", native_path.toStdString());
115
116 loadImage(native_path);
117}
118
119void ImageControllerBase::applyOperations(std::vector<Core::Operations::OperationDescriptor> operations)
120{
121 if (!m_engine)
122 {
123 spdlog::warn("[ImageControllerBase::applyOperations]: Engine not available");
124 emit operationFailed("No image loaded");
125 return;
126 }
127
128 if (operations.empty())
129 {
130 spdlog::warn("[ImageControllerBase::applyOperations]: Empty operation list provided");
131 emit operationFailed("No operations specified");
132 return;
133 }
134
135 spdlog::info("[ImageControllerBase::applyOperations]: Applying {} operation(s)", operations.size());
136
137 if (m_operation_state_manager)
138 {
139 QMetaObject::invokeMethod(this, [this, ops = std::move(operations)]() mutable {
140 doApplyOperations(std::move(ops));
141 }, Qt::QueuedConnection);
142 } else {
143 spdlog::error("[ImageControllerBase::applyOperations]: OperationStateManager is null during legacy applyOperations call!");
144 emit operationFailed("Internal error: OperationStateManager not initialized");
145 }
146}
147
148void ImageControllerBase::doLoadImage(const QString& file_path)
149{
150 spdlog::info("[ImageControllerBase::doLoadImage]: Starting load on worker thread");
151
152 // 1. Load image
153 auto load_result = m_engine->loadImage(file_path.toStdString());
154
155 if (!load_result) {
156 spdlog::error("[ImageControllerBase::doLoadImage]: Load failed for {}", file_path.toStdString());
157 auto err = load_result.error();
158 QString error_msg = QString::fromStdString(
159 std::format("CoreError [{}]: {}", static_cast<int>(err), Core::ErrorHandling::to_string(err))
160 );
161 onImageLoadResult(false, error_msg);
162 return;
163 }
164
165 // 2. Get Metadata
166 m_image_width = m_engine->width();
167 m_image_height = m_engine->height();
168
169 spdlog::info("[ImageControllerBase::doLoadImage]: Image loaded {}x{}", m_image_width, m_image_height);
170
171 // 3. Notify DisplayManager of source size
172 if (m_display_manager) {
174 static_cast<int>(m_image_width),
175 static_cast<int>(m_image_height)
176 );
177 }
178
179 // 4. Get display size from DisplayManager
180 QSize downsample_size { m_display_manager->downsampleSize() };
181
182 if (downsample_size.isEmpty()) {
183 spdlog::error("[ImageControllerBase::doLoadImage]: Invalid downsample size");
184 onImageLoadResult(false, "Invalid downsample size");
185 return;
186 }
187
188 // 5. Get downsampled image directly (GPU → small ImageRegion)
189 spdlog::debug("[ImageControllerBase::doLoadImage]: Requesting downsampled image {}x{}",
190 downsample_size.width(), downsample_size.height());
191
192 auto display_image_result = m_engine->getDownsampledDisplayImage(
193 static_cast<Core::Common::ImageDim>(downsample_size.width()),
194 static_cast<Core::Common::ImageDim>(downsample_size.height())
195 );
196
197 if (!display_image_result) {
198 spdlog::error("[ImageControllerBase::doLoadImage]: Failed to get downsampled image: {}",
199 Core::ErrorHandling::to_string(display_image_result.error()));
200 onImageLoadResult(false, "Failed to get display image");
201 return;
202 }
203
204 spdlog::debug("[ImageControllerBase::doLoadImage]: Got downsampled image successfully");
205
206 // 6. Update DisplayManager with const& (no copy, no shared_ptr)
207 if (m_display_manager) {
208 m_display_manager->createDisplayImage(std::move(display_image_result.value()));
209 spdlog::info("[ImageControllerBase::doLoadImage]: DisplayManager updated");
210 } else {
211 spdlog::error("[ImageControllerBase::doLoadImage]: No DisplayManager available!");
212 }
213
214 onImageLoadResult(true, "");
215}
216
217void ImageControllerBase::doApplyOperations(std::vector<Core::Operations::OperationDescriptor>&& operations)
218{
219 spdlog::debug("[ImageControllerBase::doApplyOperations]: Starting operation processing with {} operations", operations.size());
220
221 if (!m_engine) {
222 spdlog::error("[ImageControllerBase::doApplyOperations]: No engine available");
223 onOperationResult(false, "No engine available");
224 return;
225 }
226
227 // 1. Trigger Core Processing and wait for completion
228 // The core uses a deferred future: the continuation that updates the working image
229 // and clears the "update in progress" flag only runs when .get() is called.
230 spdlog::debug("[ImageControllerBase::doApplyOperations]: Applying operations via PhotoEngine");
231 auto apply_future = m_engine->applyOperations(std::move(operations));
232 if (!apply_future.valid()) {
233 onOperationResult(false, "Failed to start operation");
234 return;
235 }
236 bool apply_ok = apply_future.get();
237 if (!apply_ok) {
238 onOperationResult(false, "Operation processing failed");
239 return;
240 }
241
242 // 2. Get display size from DisplayManager
243 QSize downsample_size { m_display_manager->downsampleSize() };
244
245 if (downsample_size.isEmpty()) {
246 spdlog::error("[ImageControllerBase::doApplyOperations]: Invalid downsample size");
247 onOperationResult(false, "Invalid downsample size");
248 return;
249 }
250
251 // 3. Get downsampled image directly (GPU → small ImageRegion)
252 spdlog::debug("[ImageControllerBase::doApplyOperations]: Requesting downsampled image {}x{}",
253 downsample_size.width(), downsample_size.height());
254
255 auto display_image_result = m_engine->getDownsampledDisplayImage(
256 static_cast<Core::Common::ImageDim>(downsample_size.width()),
257 static_cast<Core::Common::ImageDim>(downsample_size.height())
258 );
259
260 if (!display_image_result) {
261 spdlog::error("[ImageControllerBase::doApplyOperations]: Failed to get downsampled display image: {}",
262 Core::ErrorHandling::to_string(display_image_result.error()));
263 onOperationResult(false, "Failed to get display image");
264 return;
265 }
266
267 spdlog::debug("[ImageControllerBase::doApplyOperations]: Got downsampled image successfully");
268
269 // 4. Update DisplayManager with const& (no copy, no shared_ptr)
270 if (m_display_manager) {
271 m_display_manager->createDisplayImage(std::move(display_image_result.value()));
272 spdlog::info("[ImageControllerBase::doApplyOperations]: Display updated");
273 } else {
274 spdlog::warn("[ImageControllerBase::doApplyOperations]: No DisplayManager");
275 }
276
277 onOperationResult(true, "");
278}
279
280void ImageControllerBase::onImageLoadResult(bool success, const QString& error_msg)
281{
282 spdlog::debug("[ImageControllerBase::onImageLoadResult]: success={}", success);
283
284 if (success) {
285 spdlog::info("[ImageControllerBase::onImageLoadResult]: Image loaded successfully ({}x{})",
287 emit imageSizeChanged();
289 } else {
290 spdlog::error("[ImageControllerBase::onImageLoadResult]: Image load failed - {}", error_msg.toStdString());
291 emit imageLoadFailed(error_msg);
292 }
293}
294
295void ImageControllerBase::onOperationResult(bool success, const QString& error_msg)
296{
297 spdlog::debug("[ImageControllerBase::onOperationResult]: success={}", success);
298
299 if (success) {
300 spdlog::info("[ImageControllerBase::onOperationResult]: Operation completed successfully");
301 emit operationCompleted();
302 } else {
303 spdlog::error("[ImageControllerBase::onOperationResult]: Operation failed - {}", error_msg.toStdString());
304 emit operationFailed(error_msg);
305 }
306}
307
309{
310 spdlog::debug("[ImageControllerBase::connectModelsToStateManager]: Starting model connections");
311
312 if (!m_operation_model_manager || !m_operation_state_manager) {
313 spdlog::critical("[ImageControllerBase::connectModelsToStateManager]: ModelManager or StateManager is null. Cannot proceed.");
314 throw std::runtime_error("ImageControllerBase: Critical failure during model-to-state-manager connection setup.");
315 }
316
317 // Use the specific list of BaseAdjustmentModel (SAFE, NO CAST NEEDED)
318 const auto& models = m_operation_model_manager->getBaseAdjustmentModels();
319 for (const auto& model : models) { // model is std::shared_ptr<UI::Models::Operations::BaseAdjustmentModel>
320 if (model) {
321 // Connect the EXISTING valueChanged signal from BaseAdjustmentModel
322 // The type is known at compile time, no cast needed here.
324 [this, model /* Capture the shared_ptr to the specific model */ ](float new_value) {
325 spdlog::debug("[ImageControllerBase::connectModelsToStateManager]: Value changed signal received for model {}", model->name().toStdString());
326
327 // This lambda is called when the specific BaseAdjustmentModel's value changes.
328
329 // 1. Update the OperationStateManager with the CURRENT state of this specific model
330 if (m_operation_state_manager) {
331 auto descriptor = model->getDescriptor(); // Get the descriptor for the specific model that changed
332 m_operation_state_manager->addOrUpdateOperation(descriptor);
333 spdlog::debug("[ImageControllerBase::connectModelsToStateManager]: Operation '{}' updated in StateManager via valueChanged signal.", descriptor.name);
334 } else {
335 spdlog::warn("[ImageControllerBase::connectModelsToStateManager]: Received valueChanged signal, but StateManager is null.");
336 return; // Exit if StateManager is not available
337 }
338
339 // 2. Retrieve the full list of active operations from OperationStateManager
340 auto active_ops = m_operation_state_manager->getActiveOperations();
341
342 // 3. Trigger the applyOperations workflow with the full list of active operations (Move semantics)
343 applyOperations(std::move(active_ops));
344
345
346 });
347 spdlog::debug("[ImageControllerBase::connectModelsToStateManager]: Connected model {}", model->name().toStdString());
348 }
349 }
350 spdlog::info("[ImageControllerBase::connectModelsToStateManager]: All {} BaseAdjustment models connected to StateManager via valueChanged signal.", models.size());
351}
352
353} // namespace CaptureMoment::UI::Controller
Generic Qt base class for single-value adjustment operations (e.g., Brightness, Contrast,...
std::unique_ptr< CaptureMoment::UI::Display::DisplayManager > m_display_manager
The unique displaymanager instance for managing display updates.
Q_INVOKABLE void loadImageFromUrl(const QUrl &file_url)
Loads an image from a QUrl (typically from QML FileDialog). Converts the QUrl to a native file path a...
void imageSizeChanged()
Emitted when image size changes.
void doApplyOperations(std::vector< Core::Operations::OperationDescriptor > &&operations)
Perform actual operations (runs on worker thread).
ImageControllerBase(QObject *parent=nullptr)
Constructs ImageController.
void applyOperations(std::vector< Core::Operations::OperationDescriptor > operations)
Apply operation with parameters (non-blocking) Note: This slot is designed to be called from QML or o...
void registerModel(IOperationModel *model)
Register an operation model for notifications.
void connectModelsToStateManager()
Connects the operationRequested signal from all created models to the OperationStateManager....
void onImageLoadResult(bool success, const QString &error_msg)
Internal: Handle load image result.
void onOperationResult(bool success, const QString &error_msg)
Internal: Handle operation result.
~ImageControllerBase()
Destructor - cleans up worker thread.
std::shared_ptr< Core::Engine::PhotoEngine > m_engine
Core processing engine.
void operationCompleted()
Emitted when operation completes.
void loadImage(const QString &file_path)
Load image from file path (non-blocking)
void operationFailed(QString error)
Emitted when operation fails.
void imageLoadFailed(QString error)
Emitted when image load fails.
void doLoadImage(const QString &file_path)
Perform actual image load (runs on worker thread). This method contains the common logic for loading ...
void imageLoaded(int width, int height)
Emitted when image loaded successfully.
void createDisplayImage(std::unique_ptr< Core::Common::ImageRegion > source_image)
Creates the display buffer from a downsampled source image.
Q_INVOKABLE void setSourceImageSize(int width, int height)
Sets the source image size metadata.
Pure interface for operation models.
std::vector< Core::Operations::OperationDescriptor > getActiveOperations() const
Gets the current list of active operation descriptors.
bool createBasicAdjustmentModels()
Creates instances of basic adjustment operation models. This method typically creates models like Bri...
const std::vector< std::shared_ptr< UI::Models::Operations::BaseAdjustmentModel > > & getBaseAdjustmentModels() const
Gets a const reference to the vector of created BaseAdjustmentModel instances. Provides access to the...
void valueChanged(float value)
Signal emitted when the adjustment value changes. QML can connect to this signal to react to value up...
Central error handling system for CaptureMoment Core library.
Manages image display with downsampling (with OIIO), zoom, and pan.
Qt bridge between Core (PhotoEngine) and Qt UI.
Common type definitions for all image types in CaptureMoment.
Manages the state of active image operations.
std::size_t ImageDim
Type for image dimensions (width, height).
Definition image_types.h:20
constexpr std::string_view to_string(CoreError code)
Converts a CoreError code to a human-readable string.
Definition core_error.h:197
CaptureMoment — footer