Skip to content

Latest commit

 

History

History
512 lines (407 loc) · 37.3 KB

File metadata and controls

512 lines (407 loc) · 37.3 KB

🎨 Photon UI/UX Specification

Vision: A high-performance, native C++/Qt implementation of a modern RAW editor. Visual Reference: The UI should closely mirror the clean, dark aesthetic of RapidRAW.

Theme: "Zinc Dark" (Shadcn). Flat, borderless inputs, subtle gradients on hover, rigorous spacing.


1. 📂 Data & Project Structure

To ensure non-destructive editing and high performance, Photon manages a sidecar directory automatically.

Logic:

  • When a folder is opened, Photon checks for/creates a .PhotonData folder (hidden on Linux/Mac, standard folder on Win).
  • Edit Stack: Edits are stored as a JSON array of objects. Each object represents a complete state of the adjustment parameters.
  • Commit Logic: While a slider is moving, the GPU updates in real-time (60fps). A new state is only appended to the JSON array when the user releases the slider, provided the value is different from the previous state.
  • Cache: Stores generated thumbnails to avoid re-parsing RAW files on every launch.

Directory Tree:

/User/Pictures/Vacation2024/
├── IMG_001.ARW
├── IMG_002.ARW
└── .PhotonData/
    ├── edits/
    │   ├── IMG_001.json  # Contains [ {exposure: 0.0, ...}, {exposure: 0.5, ...} ]
    │   └── IMG_002.json
    └── cache/
        ├── thumbnails/
        │   ├── IMG_001.jpg
        │   └── IMG_002.jpg

2. 🏠 The Welcome Screen

Layout: Split View (50% / 50%).

  • Left Pane: A high-quality, random aesthetic photograph (abstract/landscape). Fills height, cropped to cover.
  • Right Pane: Minimalist interaction zone. Centered vertically.

Elements:

  1. "Continue Session" Button:
  • Condition: Only visible if a last_opened_folder exists in global config.
  • Action: Immediately loads the last directory and jumps to Library View.
  • Style: Primary (White/Bright) Shadcn Button.
  1. "Open Folder" Button:
  • Action: Opens system file picker.
  • Style: Secondary (Dark Grey/Outline) Shadcn Button.
  1. Settings Icon (⚙️):
  • Position: Top-right or below buttons.
  • Action: Opens a modal overlay with Application Preferences.

Application Settings (Phase 11)

Photon provides advanced control over performance and aesthetics:

  1. GPU Selection:
    • Finding: On Linux/NVIDIA systems, forcing the GPU requires setting QSG_RHI_DEVICE_INDEX, QT_VULKAN_DEVICE_INDEX, and MESA_VK_DEVICE_SELECT environment variables before the graphics driver initializes. For NVIDIA Prime, __NV_PRIME_RENDER_OFFLOAD=1 and __GLX_VENDOR_LIBRARY_NAME=nvidia are mandatory.
    • Dynamically detects available Vulkan-compatible physical devices.
    • Allows users to select a specific GPU for RHI rendering.
    • Changes may require an application restart.
  2. Denoising Engine:
    • Full Quality Toggle: Users can force the high-fidelity 2-step BM3D denoiser during preview (otherwise single-step is used for speed).
    • Architecture: BM3D runs on the Y (luminance) channel only; chrominance (Cb/Cr) is denoised via a Multi-Scale Guided Filter using the denoised Y as structural guide.
    • GPU Search Offload: Patch-matching is optionally offloaded to a Vulkan compute pipeline via GpuSearcher.
    • User-Tunable Parameters: Exposed via QML sliders: Search Window (9-39), Group Size (4/8/16), Chroma Radius (1-16), Chroma Denoise (0-100). Serialized in .PhotonData edit stacks.
  3. Aesthetics:
    • Theme: Toggle between "Zinc Dark" and "Zinc Light".
    • Accent Color: Choose from a predefined palette of high-contrast colors (Blue, Rose, Green, Orange).
  4. Cache Management:
    • Option to clear the .PhotonData/cache/thumbnails directory.
    • Only active when a workspace is currently loaded.
  5. Logging:
    • Integrated Logger class for system diagnostics.
    • User-definable log file location with sensible defaults.

3. 📚 Library View (Grid Mode)

Goal: Culling and organization. Layout:

  • Top Bar:

    • Rating Filter: Dropdown with operator (=, >, ≥, <, ≤) and star rating. Popup right-aligned to button.
    • Sort Dropdown: Sort by Name (default), Date, or Rating with ascending/descending toggle. Default: Name ascending. Popup right-aligned, 260px wide.
    • Home Button: Returns to Welcome View (Lucide house icon).
  • Central Grid:

    • Responsive Grid of Image Cards.
    • Each Card displays: Thumbnail, Filename, Rating Stars (overlay on hover).
  • Auto-Scan:

    • Periodically rescans workspace folder for new RAW files (configurable interval in Settings).
    • Smart diff: only appends new files to avoid flicker.
    • scanIntervalSeconds persisted via QSettings (default: 10s, range: 1–300s).
  • Interaction:

    • Hover: Shows metadata overlay (ISO, Shutter, Aperture).
    • Single Click: Selects image (Blue border).
    • Double Click: Transitions to Develop View.
    • 0-5 Keys: Sets rating for selected photo(s).

4. 🎛️ Develop View (Edit Mode)

Goal: Precision editing with a unified workflow. Layout: Unified Right-Stack Layout.

A. The Viewport (Center/Left - Dominant)

  • Content: The RawViewport (C++ Vulkan Widget).
  • Behavior:
    • Pan (Space + Drag) & Zoom (Scroll Wheel).
    • Floating toolbar at the bottom for Zoom, Undo/Redo, Restore to Original, and Before/After toggle.
    • Before/After Comparison: Press \ (backslash) or B to toggle between the edited and unprocessed original image. An eye icon button in the toolbar also toggles this mode (turns accent color when active). A floating toast ("Before"/"After") fades in at the top center of the viewport for 800ms to indicate the current state. Implemented via a showOriginal uniform in the fragment shader that bypasses all processing steps.
    • Note: The floating top navigation bar is disabled in this view to maximize vertical space.
    • High-Performance Panning: To ensure 60fps responsiveness during high-resolution RAW navigation, Photon uses a texture-caching strategy. Panning only updates the viewport geometry (quad coordinates) without re-uploading texture data to the GPU or performing CPU-side pixel conversions.
    • Denoise ROI Stability: Partial denoise ROI textures no longer overwrite logical image dimensions, preserving correct pan/zoom aspect mapping while zoomed.

B. The Tool Stack & Switcher (Right)

The right panel is divided into two parts: a Tool Stack (320px) and a Section Switcher (48px).

  1. Section Switcher (Vertical Rail):

    • A slim vertical bar on the far right containing Lucide icons for high-level mode switching.
    • Metadata (Info icon): Extensive EXIF and image info.
    • Edit (Settings icon): The primary adjustment sliders.
    • Crop (Crop icon): Crop, rotation, and geometry tools.
    • Lens (Telescope icon): Lens correction and distortion management.
    • Presets (Bookmark icon): User-saved adjustment states.
    • Export (Download icon): High-quality export options (JPEG/TIFF).
    • Library (Books icon): One-click jump back to grid mode.
    • Settings (Gear icon): Application preferences.
  2. Tool Stack (Dynamic Panel):

    • A StackLayout that displays the selected mode's controls.
    • Histogram: (Pinned at the top of the stack). Professional real-time visualization of RGB and Luma distribution. Uses P99 percentile normalization (skipping bins 0 and 255) to prevent dominant bins from compressing the display. Each channel rendered as a filled PathPolyline shape; polyline prepends (0, height) as first point to prevent auto-close diagonal artifacts.
    • Light Section: Exposure, Contrast, Highlights, Shadows, Whites, Blacks, Adaptation, AgX Tonemapping.
      • Tone-targeting math uses smoother tonal masks and a bounded highlight shoulder to reduce harsh transitions and clipping artifacts at strong positive adjustments.
    • Tone Curve Section: Interactive spline editor with 4 channels (Luminance, Red, Green, Blue):
      • Canvas-based curve display with diagonal identity reference and grid. Channel tabs with colored round buttons (white=Luma, red=R, green=G, blue=B).
      • Click to add control points, drag to move, double-click interior points to remove.
      • Endpoints draggable vertically only; interior points constrained between neighbors.
      • Monotonic cubic Hermite spline (Fritsch-Carlson) ensures smooth, non-oscillating curves.
      • LUT Texture: 65536-entry precision per channel. Curves are baked into a 256×1024 RGBA8 texture via ToneLutProvider: four stacked 256×256 planes (Luma/R/G/B), each plane storing 16-bit LUT values packed across R (high byte) and G (low byte). QML Image loads from "image://tonelut/" + version, wrapped in ShaderEffectSource with textureSize: Qt.size(256, 1024).
      • Shader Application: Tone-curve lookup decodes packed 16-bit LUT values from centered texture samples (R=high byte, G=low byte). Luma curve uses a hybrid additive/multiplicative blend to avoid noise amplification in shadows when raising the black point: mix(additive, multiplicative, smoothstep(0.0, 0.36, lumaIn)). A toneCurveActive uniform (float) guards the entire block — identity curves skip LUT sampling entirely.
      • Black-Point Sensitivity Tuning: Positive low-luma lift from the luma curve is attenuated near absolute black to make first-point adjustments less aggressive while preserving mid/high-tone behavior.
      • Debounce: QML drag uses a 30ms debounce timer (setPointsThrottled) to prevent CPU spike from cascading rebuildToneLut + histogram + signal chains. Canvas repaints immediately using pending points for smooth visual feedback; timer flushes on mouse release.
      • CPU Export Pipeline: Identical spline evaluation and shadow-blend logic duplicated as file-local function in ImageDeveloper.cpp (avoids link dependency since test binaries don't link RawEngine), using the same 65536-entry lookup precision.
    • Presence Section: Vibrance, Saturation.
    • Color Section: HSL panel (8 hue ranges × Hue/Saturation/Luminance).
      • HSL targeting uses widened/normalized hue influence with low-chroma protection to keep transitions smoother and reduce luminance blister artifacts at extremes.
    • Color Grading Section: Shadows/Midtones/Highlights color wheels, Balance, Blending.
    • Effects Section: Grain (Amount/Size/Roughness), Vignette (Amount/Midpoint/Roundness/Feather).
    • Creative Section: Clarity, Dehaze, Structure, Centre.
    • Detail Section: Sharpness, Masking (Scharr edge detection), Feather, Focus Detection.

Crop & Geometry (Phase 30–31)

Non-destructive crop and geometry transformations with a baked geometry pipeline for pixel-accurate display.

  1. Data Model (Q_PROPERTYs on RawEngine → RawViewport):

    • cropRect (QRectF, default 0,0,1,1): Normalized crop rectangle.
    • cropAspectRatio (float): -1 = free, 0 = original, >0 = locked ratio (W/H).
    • straightenAngle (float, ±45°): Fine rotation for leveling horizons.
    • orientationSteps (int, 0–3): 90° CW rotation increments.
    • flipHorizontal / flipVertical (bool): Mirror transforms.
    • geometryBaked (bool, read-only): Indicates the display buffer has geometry transforms baked in.
  2. CropPanel.qml (Sidebar, slot 2 in StackLayout):

    • Aspect Ratio: 3-column grid of presets (Free, Original, 1:1, 5:4, 4:3, 3:2, 16:9, 21:9, 65:24). Clicking the active preset toggles landscape ↔ portrait.
    • Straighten: ±45° slider + readout. Ruler icon activates the Straighten Tool (draw a reference line on the viewport; if angle ≤ 45° from horizontal → align to horizontal, else → align to vertical). Reset button.
    • Orientation: 2×2 grid — Rotate Left/Right (90° steps), Flip Horizontal/Vertical (toggle with accent highlight).
    • Commit-on-Enter: Changes are previewed live via QML transforms; only committed to the engine (and JSON sidecar) when the user presses Enter. ESC discards uncommitted changes.
  3. CropOverlay.qml (Viewport overlay):

    • Visible only when Crop panel is active (activeSidebar === 2).
    • Semi-transparent dark mask (50% opacity in crop mode) outside crop rect, white border with Rule of Thirds grid.
    • Outside crop mode, baked geometry no longer shows an additional crop mask overlay (prevents double-crop visual confusion).
    • 8 drag handles (4 corners + 4 edges) for resizing; center drag to move.
    • Aspect ratio constraint enforced during handle drag when ratio is locked.
    • Straighten tool: Canvas overlay draws a dashed reference line during drag, computes and applies correction angle on release.
    • Coordinate contract: cropRect is normalized in the rotated bounding frame (displayRect) of the current orientation/flip/straighten transform.
    • Validity domain: crop corners must remain inside the transformed image quadrilateral (not only inside [0,1] box); candidate rects are projected from the last valid rect to avoid border jumps.
    • Crop-mode viewport behavior: when entering crop mode, zoom/pan are reset and crop mode auto-fits transformed bounds to keep the full valid domain visible during selection.
  4. Baked Geometry Pipeline (Phase 31):

    • Commit (Enter): Engine re-decodes the RAW from disk → converts to QImage → applies geometry transforms (orientation → flip → straighten → crop rect) → stores result in m_geometryBuffer → sets geometryBaked = true → emits imageLoaded. The viewport renders the pre-transformed image; QML transforms are set to neutral.
    • Enter Crop Mode: Clears the geometry bake → triggers re-process of the original image → QML transforms re-enabled for live preview.
    • Exit Crop Mode (ESC): Discards uncommitted changes → if geometry is non-default, re-bakes.
    • Undo/Redo: Outside crop mode, geometry is re-baked when undo/redo changes geometry properties.
    • Reset to Defaults: Clears geometry bake alongside all other properties.
    • Transform Order (matches ImageDeveloper::develop): orientation steps (N × 90° QTransform::rotate) → flip (QTransform::scale(-1, …)) → straighten (QTransform::rotate(angle)) → crop rect extraction (QImage::copy) on the rotated frame.
    • Crop math parity rule: the same normalized cropRect from crop mode is consumed by bake/export against the rotated frame dimensions.
    • Deterministic pixel mapping:
      left = floor(x * W), top = floor(y * H), right = ceil((x+w) * W), bottom = ceil((y+h) * H), with bounds clamping.
      Output size is (right-left) × (bottom-top).
    • applyGeometryTransforms(): Static utility on RawEngine, reusable by both the bake pipeline and ImageDeveloper.
    • Dimension Sync: updatePaintNode() detects when the geometry buffer has different dimensions from the original and updates m_imageWidth/m_imageHeight so calculateTargetRect() computes the correct aspect ratio.
  5. Export Integration (ImageDeveloper::develop):

    • Applied after all color processing, in order: orientation steps → flip → straighten → crop rect extraction on the rotated frame.
    • In crop mode, the default straighten-safe crop window is initialized in the UI, and the same normalized crop rect is then consumed by bake/export.
    • Export uses the same floor/ceil crop-window rule as RawEngine::applyGeometryTransforms() to preserve aspect/focus parity.
  6. JSON Serialization: All properties stored in sidecar JSON, round-tripped through stateToJson/applyJsonToState/resetToDefaults.

Multi-Selection & Asset Management (Phase 14)

Photon supports professional asset management workflows:

  1. Selection Logic:
    • Single Click: Selects an image and clears previous selection (unless Ctrl/Shift held).
    • Ctrl + Click: Toggles selection of an individual image.
    • Shift + Click: Selects a range of images from the last selected to the current.
    • Ctrl + A: Selects all visible images in the current view.
  2. Rating:
    • Images can be assigned a rating from 0 to 5 stars.
    • Ratings are stored in the .PhotonData/edits/ JSON sidecar.
    • Key 0-5 assigns rating to ALL currently selected images.
  3. Filtering:
    • The Library View features a filter strip to show only images matching a specific rating (e.g., ">= 3 stars").

High-Quality Export (Phase 15)

Non-destructive edits are applied during the export process:

  1. Engine: A dedicated ExportManager handles background processing without blocking the UI.
  2. Pipeline: RAW -> Apply Kelvin WB -> Linear Exposure -> Processing Stack -> AgX Tonemapping -> Dithering -> Format Conversion.
  3. Formats:
    • JPEG: 8-bit, configurable quality (1-100).
    • TIFF: 8-bit or 16-bit for maximum archival quality.
  4. Batching: Multiple selected images can be exported in parallel using a worker thread pool.

Responsive Preview System (Phase 16-17)

To ensure zero-latency feedback when switching photos, Photon implements a background proxy system:

  1. Background Precomputation: Upon opening a folder, a PreviewManager scans all images and begins generating 1080p JPEG proxies in .PhotonData/cache/previews/.
  2. Instant Loading: When a photo is selected, the UI immediately displays the cached JPEG proxy (if available) while the RawEngine develops the full-resolution RAW in the background.
  3. Hybrid Rendering: Once the RAW development is complete, the viewport seamlessly swaps the proxy for the real GPU-processed image.
  4. Smart Invalidation: Previews are automatically regenerated when:
    • Edits are committed to an image (debounced via a 2-second timer to coalesce rapid edits).
    • The sidecar JSON timestamp is newer than the cached preview.
    • Thread Safety: Preview generation runs on a QThreadPool with idealThreadCount/2 workers. GPU search (GpuSearcher) is skipped in preview tasks (passes nullptr for QRhi) because QRhi is not thread-safe — concurrent access from thread pool + render thread would cause corruption. CPU-only denoise matching is used instead, which is adequate for half-size preview images.
    • Serialized Refresh Tasks: Single-image preview refreshes (triggered by edit commits) are serialized via m_refreshRunning / m_pendingRefreshPath guards. Only one refresh task runs at a time; if a new request arrives while one is in progress, it is queued and dispatched when the current task completes. This prevents concurrent ImageDeveloper::develop() + Denoiser::denoise() pipelines from competing for the global QThreadPool and exhausting memory (~150MB+ per task for a 6MP image with BM3D denoise).
    • Histogram Buffer Safety: clearProcessedImage() waits for any in-flight histogram QtConcurrent::run task to complete before freeing m_processedImage, preventing use-after-free when switching photos while the histogram is computing.

Async Preview Loading with Image Swap (Phase 17)

The preview system has been refined to eliminate visual glitches when switching photos:

  1. Immediate Clear: When switching to a new photo, the viewport immediately clears the previous image display. This prevents the old image from being visible above the new one during the loading transition.
  2. Async Loading Flow:
    • Photo selection triggers immediate display clear.
    • Preview JPEG loads asynchronously and displays as soon as available.
    • Full-resolution RAW develops in background.
    • Seamless swap from preview to full-res when RAW is ready (no flash or glitch).
  3. State Tracking: The viewport tracks whether it's currently showing a preview (m_showingPreview) vs. the full-resolution image, ensuring proper aspect ratio and dimension handling throughout the transition.
  4. Loading Indicator: While no preview is available, the viewport shows a blank/loading state rather than the previous image.

GPU Processing Pipeline (Phase 5)

To achieve professional-grade results, Photon employs a high-fidelity GPU pipeline:

  1. Linear Workflow: Input textures are converted from sRGB to Linear Space for all mathematical operations. This ensures correct light addition and blending.

  2. White Balance: Handled in linear space using a kelvin-based temperature shift and a magenta/green tint adjustment.

  3. Demosaicing: While Photon explored custom implementations of various demosaicing algorithms (including PPG and RCD), rigorous testing concluded that LibRaw's native implementation remains the superior choice. It provides the best balance of image reconstruction quality, artifact suppression, and computational performance for this project.

  4. Tonemapping: AgX Sigmoid transform is implemented to provide a filmic highlight roll-off and natural color compression, preventing "digital" clipping of bright highlights.

  5. Grain: High-quality Film Grain is implemented using a gradient noise algorithm, with controls for amount, size, and roughness. It is applied in linear-to-srgb space with a luma-based mask to protect shadows and highlights.

  6. Vignette: An Advanced Vignette system is implemented with midpoint, roundness, and feathering controls, allowing for precise artistic framing.

  7. HSL Panel: An 8-band HSL system (Red, Orange, Yellow, Green, Aqua, Blue, Purple, Magenta) is implemented in the fragment shader. It uses weighted influence curves to allow targeted Hue, Saturation, and Luminance adjustments without causing artifacts.

  8. Color Grading: A professional 3-Way Color Grading system is implemented, allowing independent tinting of Shadows, Midtones, and Highlights. It features global Balance and Blending controls to precisely manage tonal transitions.

  9. Dithering: High-quality dithering is implemented using a sine-based pseudo-random noise generator. It is applied to the final RGB output at a precision of 1/255 to mask banding artifacts and ensure smooth gradients on 8-bit displays.

  10. Denoising Pipeline (Phase 12/24/26): Photon employs a hybrid architecture for professional-grade noise reduction:

    • GPU Preview (NLM): A real-time Non-Local Means (NLM) filter runs in the fragment shader. It uses 3x3 patch comparisons within a 7x7 search window, providing high-fidelity spatial denoising at 60fps.
    • Full Quality Toggle: A user preference in settings allows forcing the high-fidelity 2-step denoiser even during the preview phase.
    • YCbCr Decoupled Processing (Phase 24): The denoiser converts RGB to YCbCr color space. BM3D operates on the Y (luminance) channel only for ~3× speed improvement, while chrominance channels (Cb/Cr) are denoised via a Multi-Scale Guided Filter using the clean Y as a structural guide. This eliminates "color blotchiness" that joint-channel BM3D often misses.
    • Multi-Scale Guided Filter: An edge-preserving smoothing operator applied at three scales (r/2, r, r×2 with base ε=1.0/4.0/10.0, scaled by chroma strength) to progressively remove fine-to-coarse chrominance noise while preserving luminance edges. Uses SIMD-optimized O(1) separable box filter on CPU or GPU-accelerated Vulkan compute path.
    • Vulkan-Native Compute Offload: Two compute pipelines run on dedicated Vulkan compute queues:
      • GpuSearcher: Offloads BM3D patch-matching (SSD using 3x3 patches) to GPU.
      • GpuChromaFilter (Phase 26): Runs the full multi-scale guided filter on GPU using two compute shaders (box_filter.comp for separable box blur, guided_ops.comp for element-wise coefficient computation). Falls back to CPU SIMD path automatically if Vulkan is unavailable.
    • CPU Transform & Filter (SIMD): All BM3D collaborative filtering, color space conversions, box filters, and guided filter coefficient computation use AVX2 and FMA instructions.
    • Sharpness Enhancement (Phase 26): The fragment shader uses a dual-radius 13-tap blur kernel (inner ring at 1.5 texels + outer ring at 3.0–4.0 texels) for effective unsharp masking even on heavily denoised images.
    • Edge-Selective Sharpening Mask (Phase 27): A Scharr-based edge detection operator computes per-pixel edge strength from the luminance channel in the fragment shader. The sharpenMask parameter (0–100) controls a threshold/gain curve that progressively isolates stronger edges: at 0 the full image is sharpened, at 100 only the strongest edges receive sharpening. The mask uses a smooth Hermite interpolation (mask² × (3 − 2×mask)) for natural transitions. An Alt+drag preview mode (showSharpenMask uniform) renders the combined mask as a grayscale overlay. The KeyTracker C++ singleton installs a global event filter to reliably detect Alt key state across all QML elements.
      • Mask Feather (0–100): Spatially smooths the edge mask by averaging the mask value at 4 cardinal neighbor positions (radius 1–6 texels). Produces gradual transitions at mask boundaries, preventing harsh sharpening cutoffs.
      • Focus Detection (0–100): Gates the sharpening mask by local luminance variance computed over 13 sparse samples in a ~12-texel radius (3 rings: center, half-radius cardinals, full-radius cardinals+diagonals). Variance = E[L²] − E[L]² measures texture density: in-focus regions have high variance, bokeh/OOF regions have near-zero variance. Quadratic strength ramp ensures subtle effect at low slider values.
    • Adaptive Proxy Scaling: Preview denoising resolution dynamically adjusts based on the viewport size and zoom level (viewport * zoom * 1.5), ensuring zero pixelation even at 400% zoom.
    • Asynchronous UX: Background tasks are managed by a QFutureWatcher. Adjustment sliders remain interactive, and tasks are automatically aborted/restarted upon photo switching or parameter refinement.
    • ROI-Driven Refinement (Phase 13): When zoomed in, the engine prioritizes high-quality re-rendering of the visible Region of Interest (ROI) before triggering the background denoiser on that specific area, ensuring maximum sharpness and speed.
    • Explicit Activation: Denoising must be explicitly enabled via a checkbox. Disabling it immediately halts any background processing.
    • Lifecycle Safety: To prevent segmentation faults during application teardown, Photon implements a strict ownership and cleanup hierarchy:
      • Thread Joining: All background workers (LibRaw, BM3D Denoiser, Thumbnail generator) use dedicated thread pools that are explicitly joined in their respective destructors.
      • Deterministic Teardown: Singletons (LogManager, AppStateManager) are parented to the QGuiApplication instance and reset their internal static pointers to nullptr upon destruction to prevent dangling pointer access during final process cleanup.
      • GPU Resource Release: RawViewport implements the releaseResources() protocol to ensure all RHI-allocated textures and buffers are freed on the render thread while the graphics context is still valid.
      • Instance Scoping: The QVulkanInstance is managed as a local variable in main() to ensure it persists until all QML-related teardown is complete but is destroyed before the application exits.

    10a. Multi-Scale Guided Filter (Chroma Denoising Algorithm)

    The guided filter is an edge-preserving smoother where the output $q$ is a linear transform of a guide image $I$ in each local window:

    • Compute mean_I, mean_p (guide and noisy chroma means via box filter).
    • Compute mean_II, mean_Ip (guide² and guide×chroma).
    • Variance: var_I = mean_II − mean_I².
    • Covariance: cov_Ip = mean_Ip − mean_I · mean_p.
    • Coefficients: a = cov_Ip / (var_I + ε), b = mean_p − a · mean_I.
    • Smooth coefficients: mean_a, mean_b via box filter.
    • Output: Chroma_out = mean_a · Guide + mean_b.

    Applied at three scales with increasing radius/epsilon to target fine-to-coarse color noise (r/2, r, r×2 with ε=1.0/4.0/10.0 scaled by chroma strength). The box filter uses a separable horizontal+vertical pass for O(1) per-pixel complexity. GPU path records all 51 dispatches (3 passes × 17 ops) in a single Vulkan command buffer with pipeline barriers.

    10b. Selective Sharpening Mask (Edge Detection Algorithm)

    The masking pipeline generates a grayscale edge mask where edges are white (full sharpening) and flat areas are black (no sharpening):

    1. Luminance extraction: Y = 0.2126R + 0.7152G + 0.0722B (Rec. 709 on linear data).
    2. Gradient detection (Scharr): 3×3 Scharr kernels for Gx/Gy, magnitude G = √(Gx² + Gy²). Preferred over Sobel for better rotational symmetry.
    3. Selective thresholding: mask = clamp((G − threshold) × gain, 0, 1) where threshold/gain scale with the Masking slider.
    4. Smoothing: Hermite interpolation mask² × (3 − 2×mask) for natural fall-off.
    5. Feathering (optional): Spatial blur via 5-point cardinal average at configurable radius.
    6. Focus gating (optional): Local luminance variance over 13 sparse samples at ~12-texel radius. Low variance = out-of-focus → gate to zero.
    7. Application: Output = mix(Original, Sharpened, finalMask) where finalMask = edgeMask × focusGate.

    Unlike Canny edge detection (binary single-pixel lines), the Scharr approach provides a grayscale gradient with natural fall-off — the center of an edge is sharpened intensely while strength tapers toward flat textures.

Accordion Sections:

  1. Light:
  • Sliders: Exposure, Contrast (UI: -100 to 100, mapped to 0.5 - 1.5 multiplier).
  • Tone: Highlights, Shadows, Whites, Blacks.
  • Divider Line
  • Presence: Vibrance, Saturation.
  1. Color (HSL):
  • 8-band selector (Red, Orange, Yellow, Green, Aqua, Blue, Purple, Magenta).
  • Targeted Hue, Saturation, and Luminance sliders.
  1. Color Grading:
  • 3-way region selector (Shadows, Midtones, Highlights).
  • Cinematic tinting (Hue, Saturation, Luminance) per region.
  • Global Balance and Blending sliders.
  1. Tone Curve:
  • Editable Parametric Curve (Highlights, Lights, Darks, Shadows) + Point Curve UI.
  1. Effects:
  • Clarity: Local contrast enhancement targeting midtones.
  • Dehaze: Atmospheric haze removal using dark channel estimation.
  • Structure: Micro-contrast adjustment for texture enhancement.
  • Centrè: Radial tonal and color boost for subject emphasis.
  1. Detail:
  • Sharpening: Edge contrast enhancement (0 to 100).
  • Masking: Edge-selective sharpening mask (0 to 100). At 0, sharpening is applied uniformly; at 100, only the strongest edges are sharpened. Hold Alt while dragging to preview the mask as a grayscale overlay.
  • Feather: Spatial smoothing of the sharpening mask (0 to 100). Higher values produce softer mask transitions.
  • Focus: Local luminance variance gating (0 to 100). Restricts sharpening to in-focus areas by measuring texture density over a wide neighborhood; out-of-focus/bokeh regions are excluded.
  • Noise Reduction: Luminance (NLM/BM3D) and Color reduction.

C. The Filmstrip (Bottom)

  • Content: Horizontal scrollable list of thumbnails from the current folder.
  • Sync: Highlighted thumbnail matches the main Viewport image.
  • Navigation: Left/Right Arrow keys move selection.
  • Sort Sync: Filmstrip sort order mirrors the Library view's sort settings (Name/Date/Rating, ascending/descending). Sort properties are shared via the window root object so changes in either view propagate immediately.

D. Presets Panel (Left - Collapsible)

  • Storage: Presets are stored as individual JSON files in the user's local data directory (QStandardPaths::AppLocalDataLocation).
    • Linux: ~/.local/share/photon/presets
    • macOS: ~/Library/Application Support/photon/presets
    • Windows: %LOCALAPPDATA%/photon/presets
  • Content: A vertical list of user-saved preset names.
  • Behavior:
    • Clicking a preset applies all contained adjustment parameters to the active image.
    • Applying a preset is a non-destructive action and adds a single step to the undo/redo stack.
    • Hovering over a preset name provides a "Delete" option.
    • A "Save Current" button opens a centered settings selection dialog before naming the preset.
    • The selection dialog uses hierarchical parent/child checkboxes (sections like Light/Presence/etc. with per-setting children).
    • Parent checkbox toggle behavior is cascading: checking parent checks all children; unchecking parent unchecks all children.
    • HSL adjustments are represented as a single Color Correction option (instead of per-channel H/S/L entries), and tone-curve channels are represented as a single Tone Curve option.
    • The selection dialog uses a wider plain section layout (no boxed cards) and flows sections top-to-bottom, then into the next column when the visible height is exceeded.
    • Dialog corners are rounded consistently (including top corners).
    • Only checked keys are serialized into the preset JSON; unchecked keys are omitted (never saved as defaults), so applying the preset does not change those fields.
    • The selection dialog is implemented as a reusable standalone component so the same UX can be reused for copy/paste settings workflows.

5. 🖱️ Contextual Actions & Shortcuts

Mouse Interactions:

  • Right-Click on Viewport / Filmstrip / Library thumbnails:

  • Context Menu (shared reusable component):

    • Copy settings (opens the reusable settings-selection dialog and stores only checked keys).
    • Paste settings (label becomes "Paste settings to N photos" when multiple are selected).
    • Rating actions (No rating, 1★..5★) applied to all selected photos.
    • Rating submenu exposes No rating + 1★..5★ actions.
    • Filter submenu exposes criteria cycle (= / > / ≥ / < / ≤) plus star threshold actions using the same rating-filter model as Library top bar.
    • Criteria cycling keeps the context-menu workflow active so users can iterate criteria quickly without reopening from scratch.
    • Rotate right / Rotate left / Flip horizontally / Flip vertically for selected photos.
    • Actions are context-safe: when used in Develop and the current image is selected, the viewport updates live and the remaining selected photos are updated via sidecar edits.
  • Multi-Select:

  • Shift+Click to select a range.

  • Ctrl+Click to toggle individual selection.

  • Paste Settings, rating, rotate, and flip actions apply to ALL selected images.

Keyboard Shortcuts:

  • Arrows: Navigate images.
  • 0-5: Set Star Rating.
  • P / U: Pick / Unpick (Flag).
  • X: Reject.
  • \ or B: Toggle Before/After comparison (show unprocessed original).
  • Ctrl+Z / Ctrl+Y: Undo/Redo edit steps.
  • Alt + Masking drag: Preview sharpening mask as grayscale overlay.

6. 👨‍💻 Reference Instruction

CRITICAL TASK FOR THE AGENT:

  1. Clone & Inspect: Clone the repository https://github.com/CyberTimon/RapidRAW.
  2. Analyze: strictly analyze its UI layout. Look at how they handle the histogram and slider density.
  3. Replicate & Optimize: We want that exact "Look & Feel"—the spacing, the font weights, the dark grey palette—but implemented using Qt Quick (QML) + C++ instead of Web technologies.
  4. Performance: Unlike Electron apps, our sliders must handle 60fps updates via the C++ RawEngine.

DaVinci Tonemapping

DEFINE_UI_PARAMS(max_input_nits, Max Input Nits, DCTLUI_SLIDER_FLOAT, 100.0, 48.0, 10000.0, 1.0)
DEFINE_UI_PARAMS(max_output_nits, Max Output Nits, DCTLUI_SLIDER_FLOAT, 100.0, 48.0, 10000.0, 1.0)
DEFINE_UI_PARAMS(adaptation, Adaptation, DCTLUI_SLIDER_FLOAT, 9.0, 9.0, 9.0, 1.0)
DEFINE_UI_PARAMS(user_b, User Input B, DCTLUI_SLIDER_FLOAT, 1.0, 0.0, 10.0, 0.0001)
DEFINE_UI_PARAMS(custom_b, Use Custom Adaptation, DCTLUI_CHECK_BOX, 0)
DEFINE_UI_PARAMS(invert, Invert, DCTLUI_CHECK_BOX, 0)
DEFINE_UI_PARAMS(clamp_toggle, Clamp, DCTLUI_CHECK_BOX, 1)

//clang-format on

__DEVICE__ float powf(float base, float exp) {
    return _copysignf(_powf(_fabs(base), exp), base);
}

__DEVICE__ float contrast(float x, float mid_gray, float gamma) {
    return mid_gray * powf(x / mid_gray, gamma);
}

// g(x) = a * (x / (x+b)) + c
__DEVICE__ float rolloff_function(float x, float a, float b, int invert) {
    if (invert) {
        return b * x / (a - x);
    } else {
        return a * (x / (x + b));
    }
}

__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B) {
    float input_white = max_input_nits / 100.0f;
    float output_white = max_output_nits / 100.0f;

    float3 out = make_float3(p_R, p_G, p_B);
    float b;
    if (custom_b) {
        b = user_b;
    } else {
        // Value of `b` when adaptation is 9.0:
        b = (input_white - (adaptation / 100.0f) * (input_white / output_white)) / ((input_white / output_white) - 1);
    }

    // Resolve evidently clamps the input to the input white point.
    if (clamp_toggle) {
        out.x = _fminf(out.x, input_white);
        out.y = _fminf(out.y, input_white);
        out.z = _fminf(out.z, input_white);
    }

    // Constraint 1: f(W_in) = W_out
    float a = output_white / (input_white / (input_white + b));
    if (input_white != output_white) {
        out.x = rolloff_function(out.x, a, b, invert);
        out.y = rolloff_function(out.y, a, b, invert);
        out.z = rolloff_function(out.z, a, b, invert);
    }

    // Resolve clamps to the output white point.
    if (clamp_toggle) {
        out.x = _clampf(out.x, 0.0f, output_white);
        out.y = _clampf(out.y, 0.0f, output_white);
        out.z = _clampf(out.z, 0.0f, output_white);
    }
    return out;
}