🧱 UI Core Architecture¶
🧱 Overview¶
This architecture manages the Qt/C++ abstraction layer located between the central engine (Core) and the QML user interface. It manages communication logic, the worker thread, operation state, image display, and acts as a bridge for file-based serialization/deserialization of operations initiated from the UI layer.
Main Objective: Provide a stable and responsive Qt/C++ backend for the QML UI, orchestrating communication with the Core processing engine and managing UI-specific state and services.
🧱 Key Components¶
1. ImageControllerBase (and derivatives like ImageControllerSGS, ImageControllerRHI, ImageControllerPainted)¶
- Role: Main orchestrator. Manages communication between QML,
PhotoEngine(Core),OperationStateManager,DisplayManager, andSerializerController. Each derivative (SGS,RHI,Painted) handles a specific rendering path. - Responsibilities:
- Create and manage
PhotoEngine,DisplayManager,OperationStateManager,OperationModelManager. - Manage a worker thread for non-blocking operations (loading, applying operations).
- Connect
valueChangedsignals from operation models (viaOperationModelManager) toOperationStateManager. - Expose QML slots (like
loadImage,applyOperations) and emit QML signals for results. - Interact with
DisplayManagerfor display updates. - Does not directly manage serialization.
- Post-commit
26bf511: Callsm_display_manager->setSourceImageSize(...)after loading the source image. Usesm_engine->getDownsampledDisplayImage(target_width, target_height)to obtain the display-ready image directly from the Core, and passes it (viastd::move) tom_display_manager->createDisplayImage(...)orm_display_manager->updateDisplayTile(...). - Post-commit
features/viewport: ModifieddoLoadImageanddoApplyOperationsto retrieve the target downsampled size usingm_display_manager->downsampleSize()instead ofm_display_manager->displayImageSize(). IndoApplyOperations, the call tom_display_managerwas changed fromupdateDisplayTile(...)tocreateDisplayImage(...)after receiving the new downsampled image from the Core.
- Create and manage
2. OperationModelManager¶
- Role: Centralized management of creation, storage, and QML registration of operation models (like
BrightnessModel,ContrastModel). - Responsibilities:
- Create instances of operation models.
- Provide typed lists (e.g.,
std::vector<std::shared_ptr<BaseAdjustmentModel>>) for efficient use. - Register models into the QML context (e.g.,
brightnessControl,contrastControl).
3. OperationStateManager¶
- Role: Management of the cumulative state of active operations.
- Responsibilities:
- Maintain the full list of active operations (as
OperationDescriptor). - Provide thread-safe methods to add, update, remove operations in this state.
- Provide a method to retrieve the full list of active operations.
- Does not communicate directly with
PhotoEngineor handle serialization.
- Maintain the full list of active operations (as
4. BaseAdjustmentModel (and derivatives like BrightnessModel)¶
- Role: C++ models for single-parameter adjustable operations (e.g., brightness, contrast).
- Responsibilities:
- Inherit from
OperationProviderfor Qt infrastructure. - Expose QML properties (
value,minimum,maximum,name,active). - Implement
IOperationModelto provide anOperationDescriptor. - Emit the
valueChangedsignal when the value is modified viasetValue.
- Inherit from
5. DisplayManager¶
- Role: Manage display-side logic: viewport-aware downsampling, zoom, pan, tile updates.
- Responsibilities:
- No longer performs local downsampling. Delegates this responsibility entirely to the Core (
IWorkingImageHardware::downsample). Manages zoom and pan state. Sends display-ready images (received fromImageControllerBase) to the appropriate Qt Quick rendering item (RHIImageItem,SGSImageItem, etc.). Notifies the Core layer of the source image size viasetSourceImageSize. - ViewportManager Integration: Integrates
ViewportManager(m_viewport_manager). The initialize method configures theViewportManagerwith screen and viewport details. ThesetSourceImageSizemethod usesm_viewport_manager->calculateDisplay(...)to determine the required downsampled size, emittingdisplayImageRequestif downsampling is needed. ThedownsampleSizeandmaxDownsampleare exposed as QML properties. ThefitToViewandsetViewportSizemethods utilize calculations from theViewportManager. A new methodsetQualityMarginis added to control the quality vs. performance trade-off via theViewportManager. - Logical Coordinate Space Management:
fitToView()andconstrainPan()calculate zoom and pan offsets strictly relative tom_display_image_size(the QML item's local dimensions), rather than the global window viewport. This is critical because the QML item sizes itself todisplayImageSizeand usesanchors.centerIn: parentto handle the global centering. Calculating pan against the global viewport causes a double-offset bug, shifting portrait images off-center. - 🟦 SEE VIEWPORT_MANAGEMENT.md.
- No longer performs local downsampling. Delegates this responsibility entirely to the Core (
6. SerializerController¶
- Role: UI-layer manager for handling file-based serialization and deserialization of image operations using XMP metadata.
- Responsibilities:
- Acts as a Qt/QML-friendly wrapper around the core
FileSerializerManager. - Provides Qt slots (
saveOperations,loadOperations) callable from QML. - Emits Qt signals (
operationsSaved,operationsLoaded,operationsLoadFailed, etc.) to notify the UI layer about the outcome of serialization operations. - Does not handle the core logic of serialization or manage the image processing state.
- Does not communicate directly with
PhotoEngine.
- Acts as a Qt/QML-friendly wrapper around the core
7. Rendering Components (IRenderingItemBase, BaseImageItem, RHIImageItem, SGSImageItem, PaintedImageItem)¶
- Role: Qt Quick items responsible for the final display of the image on screen.
- Responsibilities:
IRenderingItemBase: Abstract interface defining common methods (setImage,updateTile,setZoom,setPan) and state. Post-commit533b85c: No longer holds state members likem_full_image,m_zoom,m_pan, etc. Signature ofsetImageandupdateTilechanged to acceptstd::unique_ptr<Core::Common::ImageRegion>.getFullImage()now returns a raw pointerconst ImageRegion*.zoom(),pan(),getImageMutex()are now pure virtual.BaseImageItem: Provides common implementations forimageWidth,imageHeight,isImageValid,zoom(),pan(),getImageMutex(),getFullImage(), inheriting fromIRenderingItemBase. Post-commit058c558: Holds common state members (m_full_image,m_image_mutex,m_zoom,m_pan) asprotected. Implements virtual handlersonZoomChanged,onPanChanged,onImageChangedfor derived classes.RHIImageItem: UsesQQuickRhiItemto leverageQRhi(Vulkan, Metal, DirectX12). IntegratesRHIImageItemRendererfor direct manipulation of the render pipeline.-
- Memory Management & Signatures: Signatures updated for
std::unique_ptr. ImplementsonZoomChanged,onPanChanged,onImageChangedto emit signals and trigger updates.RHIImageItemRendereris declared as a friend.
- Memory Management & Signatures: Signatures updated for
-
- High-DPI (DPR) Matrix Handling: Because
QQuickRhiItemoperates at a lower level than the Scene Graph, the renderer does not automatically handle logical-to-physical pixel scaling for custom matrices. To prevent the image from rendering in the top-left corner and being cropped on High-DPI screens, theRHIImageItemRenderermust retrieve the item's logical dimensions duringsynchronize()(stored asm_item_width,m_item_height) and use them to build the orthographic projection matrix and centering offsets. It must mirror the exact mathematical logic used bySGSImageItem(x_pos = (logical_w - display_w) / 2.0f + pan.x()). Using physical render target sizes (renderTarget()->pixelSize()) for transformation math is strictly prohibited as it breaks DPR alignment.
- High-DPI (DPR) Matrix Handling: Because
SGSImageItem: UsesQQuickItemandQSGSimpleTextureNodevia the Scene Graph.-
- Color Space Handling: When updating the paint node, the internal linear F32 image data (from
m_full_image) is interpreted usingQImage::Format_RGBA32FPx4, assigned theQColorSpace::SRgbLinearcolor space, and then converted toQImage::Format_RGBA8888with the standardQColorSpace::SRgbfor display, leveraging Qt's color space conversion utilities.
- Color Space Handling: When updating the paint node, the internal linear F32 image data (from
-
- Coordinate Space Handling: Implements zoom and pan positioning by calculating the final geometry (
setRect) strictly in logical pixels (using the item'swidth()andheight()), seamlessly delegating High-DPI (DPR) scaling to Qt's Scene Graph abstraction.
- Coordinate Space Handling: Implements zoom and pan positioning by calculating the final geometry (
PaintedImageItem: UsesQQuickPaintedItemandQPainter. Post-commit7bebceb: Signatures updated forstd::unique_ptr. ImplementsonZoomChanged,onPanChanged,onImageChanged. Optimized tile update logic (treated as full replacement). Post-commitfeatures/raws(106b57d6): TheconvertImageRegionToQImageutility method now interprets the linear F32 image data (fromImageRegion) usingQImage::Format_RGBA32FPx4, assigns theQColorSpace::SRgbLinearcolor space, and then converts it toQImage::Format_RGBA8888with the standardQColorSpace::SRgbfor drawing withQPainter, leveraging Qt's color space conversion utilities.- All concrete items inherit from their specific Qt Quick base (
QQuickRhiItem,QQuickItem,QQuickPaintedItem) and fromBaseImageItemto get common state and logic. - Receive the updated image from
DisplayManagerand display it. - Implement zoom and pan logic.
8. QmlContextSetup¶
- Role: Entry point for QML context configuration.
- Responsibilities:
- Create
ImageControllerBase. - Optionally receive and hold an instance of
SerializerController(typically created and injected by a higher-level application module). - Call
OperationModelManager::registerModelsToQml(viaImageControllerBase's getter). - Register
ImageControllerBaseandSerializerControllerin the QML context (ascontrollerandserializerControllerrespectively).
- Create
🔗 Dependencies¶
- Core Library:
core(forPhotoEngine,FileSerializerManager,OperationDescriptor, etc.). - Qt Libraries:
Qt6::Core,Qt6::Quick,Qt6::Gui,Qt6::GuiPrivate,Qt6::ShaderTools. - UI Desktop Layer:
ui_desktop(orui_qml) depends on thisui_corelibrary.
🧩 Flow Diagrams¶
A. Image Processing Flow (updated for hardware abstraction, fused pipelines, viewport management, and Qt color space handling, post features/raws)¶
- Initialization:
QmlContextSetupcreatesImageControllerBase, which creates its dependencies (OperationStateManager,OperationModelManager,DisplayManager, etc.).OperationModelManagercreates the models, andImageControllerBaseconnects them toOperationStateManager.DisplayManageris initialized (e.g., viaDisplayManager::initialize) with viewport and screen information, which configures its internalViewportManager. - QML Interaction: A user moves a slider (e.g., Brightness). This calls
brightnessControl.setValue(newValue)in QML. - Model Update:
BaseAdjustmentModel::setValueupdatesm_params.valueand emitsvalueChanged(newValue). - Connection and State Update: The lambda in
ImageControllerBase::connectModelsToStateManagerconnected tovalueChangedis executed. It callsOperationStateManager::addOrUpdateOperation(brightness_descriptor). - Operation Application: The lambda retrieves the full list of active operations via
OperationStateManager::getActiveOperations()and callsQMetaObject::invokeMethod(ImageControllerBase::doApplyOperations, ...)on the worker thread. - Core Call:
ImageControllerBase::doApplyOperationscallsPhotoEngine::applyOperations(full_list_of_operations). - Processing in Core:
PhotoEnginedelegates toStateImageManager(Core) which usesOperationPipelineBuilderto create anOperationPipelineExecutor. This executor applies the list of operations via fused Halide pipeline execution using the hardware-agnosticIWorkingImageHardwareabstraction (WorkingImageCPU_HalideorWorkingImageGPU_Halide). The dynamic input buffer binding ensures the correct image data is processed by the fused pipeline. - Display Update (Core -> UI):
ImageControllerBase::doApplyOperations(ordoLoadImage) callsm_engine->getDownsampledDisplayImage(target_width, target_height). Thetarget_widthandtarget_heightare now obtained fromm_display_manager->downsampleSize(), which was calculated by theDisplayManager'sViewportManagerbased on the source image size and viewport characteristics. - Display Update (UI Propagation):
ImageControllerBasereceives the downsampled image (asstd::unique_ptr<Core::Common::ImageRegion) and passes it (viastd::move) tom_display_manager->createDisplayImage(...)(orupdateDisplayTile(...)depending on the flow, thoughcreateDisplayImageis used indoApplyOperationsas per the latest changes). - Display Update (UI Rendering):
DisplayManagerreceives the image and passes it (viastd::move) to the Qt Quick rendering item (e.g.,RHIImageItem::setImage) for display. Post-commitfeatures/raws: Items likeSGSImageItemandPaintedImageItemnow use Qt'sQColorSpaceutilities to correctly convert the linear F32 image data received from the Core to the standard sRGB color space expected for display on screen.
B. Serialization Flow (unchanged)¶
- Initialization:
QmlContextSetupreceives an instance ofSerializerController(created elsewhere) and registers it to the QML context asserializerController. - QML Interaction: A user triggers a save/load action (e.g., clicking a "Save XMP" button). QML calls
serializerController.saveOperations(imagePath, operationList)orserializerController.loadOperations(imagePath). - Serialization Call:
SerializerControllerreceives the call and delegates the work to the underlying coreFileSerializerManager. - Signal Notification: Upon completion,
SerializerControlleremits a Qt signal (e.g.,operationsSaved,operationsLoaded(loadedOperations),operationsLoadFailed). - QML Response: QML listens to these signals and updates the UI accordingly (e.g., show a success message, apply loaded operations to the
OperationStateManager).