From 20c83e14d79ea2f2617d5dc398bd3be037a9d99e Mon Sep 17 00:00:00 2001 From: AlanRockefeller Date: Tue, 28 Apr 2026 13:37:12 -0700 Subject: [PATCH 1/5] Modernize image editor controls --- faststack/qml/ImageEditorDialog.qml | 267 +++++++++++++++------------- 1 file changed, 140 insertions(+), 127 deletions(-) diff --git a/faststack/qml/ImageEditorDialog.qml b/faststack/qml/ImageEditorDialog.qml index 005f617..5f16295 100644 --- a/faststack/qml/ImageEditorDialog.qml +++ b/faststack/qml/ImageEditorDialog.qml @@ -18,6 +18,8 @@ Window { property int updatePulse: 0 property color backgroundColor: "#1e1e1e" // Default dark background property color textColor: "white" // Default text color + property bool sourceExpanded: true + readonly property int secondaryButtonHeight: 30 // Modern Color Palette readonly property color accentColor: "#6366f1" // Modern Indigo @@ -105,13 +107,28 @@ Window { // Component for Section Header Component { id: sectionHeader - Label { - font.bold: true - font.pixelSize: 15 - font.letterSpacing: 1.0 - color: imageEditorDialog.accentColorHover + ColumnLayout { + property alias text: sectionHeaderText.text + + Layout.fillWidth: true Layout.topMargin: 5 Layout.bottomMargin: 10 + spacing: 6 + + Text { + id: sectionHeaderText + Layout.fillWidth: true + font.pixelSize: 10 + font.weight: Font.DemiBold + font.letterSpacing: 1.2 + color: "#9a9795" + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2e2e2e" + } } } @@ -137,7 +154,7 @@ Window { Loader { sourceComponent: sectionHeader Layout.topMargin: 0 // Remove top margin for the very first item - onLoaded: item.text = "☀ Light" + onLoaded: item.text = "LIGHT" } ListModel { id: lightModel @@ -156,7 +173,7 @@ Window { // --- Detail Group --- Loader { sourceComponent: sectionHeader - onLoaded: item.text = "🔍 Detail" + onLoaded: item.text = "DETAIL" } ListModel { id: detailModel @@ -254,41 +271,103 @@ Window { spacing: 15 // --- Source Group --- - Loader { - sourceComponent: sectionHeader - Layout.topMargin: 0 // Remove top margin for the very first item - onLoaded: item.text = "📸 Source" - visible: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.hasRaw : false - } - Button { - text: (imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.isRawActive) ? "RAW Loaded" : "Load RAW" + ColumnLayout { Layout.fillWidth: true + Layout.topMargin: 0 // Remove top margin for the very first item visible: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.hasRaw : false - enabled: imageEditorDialog.uiStateRef ? !imageEditorDialog.uiStateRef.isRawActive : false - onClicked: { - if (imageEditorDialog.uiStateRef) imageEditorDialog.uiStateRef.enableRawEditing() - imageEditorDialog.updatePulse++ + spacing: 6 + + RowLayout { + Layout.fillWidth: true + spacing: 6 + + Text { + text: imageEditorDialog.sourceExpanded ? "▾" : "▸" + font.pixelSize: 10 + color: "#9a9795" + Layout.alignment: Qt.AlignVCenter + } + + Text { + text: "SOURCE" + font.pixelSize: 10 + font.weight: Font.DemiBold + font.letterSpacing: 1.2 + color: "#9a9795" + Layout.alignment: Qt.AlignVCenter + } + + Rectangle { + Layout.alignment: Qt.AlignVCenter + implicitWidth: sourceBadgeText.implicitWidth + 10 + implicitHeight: sourceBadgeText.implicitHeight + 4 + color: "#2a2010" + border.color: "#3a3010" + border.width: 1 + radius: 3 + + Text { + id: sourceBadgeText + anchors.centerIn: parent + text: "experimental" + font.pixelSize: 9 + font.family: "IBM Plex Mono" + color: "#7a6a3a" + } + } + + Item { Layout.fillWidth: true } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2e2e2e" + } + + TapHandler { + acceptedButtons: Qt.LeftButton + onTapped: imageEditorDialog.sourceExpanded = !imageEditorDialog.sourceExpanded } } - Label { - text: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.saveBehaviorMessage : "" + + ColumnLayout { Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 11 - color: imageEditorDialog.textColor - opacity: 0.7 - font.italic: true + visible: (imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.hasRaw : false) && imageEditorDialog.sourceExpanded + opacity: 0.65 + spacing: 8 + + Button { + text: (imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.isRawActive) ? "RAW Loaded" : "Load RAW" + Layout.fillWidth: true + Layout.preferredHeight: imageEditorDialog.secondaryButtonHeight + font.pixelSize: 12 + enabled: imageEditorDialog.uiStateRef ? !imageEditorDialog.uiStateRef.isRawActive : false + onClicked: { + if (imageEditorDialog.uiStateRef) imageEditorDialog.uiStateRef.enableRawEditing() + imageEditorDialog.updatePulse++ + } + } + Label { + text: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.saveBehaviorMessage : "" + Layout.fillWidth: true + wrapMode: Text.WordWrap + font.pixelSize: 11 + color: imageEditorDialog.textColor + opacity: 0.7 + font.italic: true + } } Loader { sourceComponent: sectionSeparator - visible: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.hasRaw : false + visible: (imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.hasRaw : false) && imageEditorDialog.sourceExpanded } // --- Color Group --- Loader { sourceComponent: sectionHeader Layout.topMargin: (imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.hasRaw) ? 5 : 0 // Adjust logic if needed - onLoaded: item.text = "🎨 Color" + onLoaded: item.text = "COLOR" } ListModel { id: colorModel @@ -306,6 +385,7 @@ Window { id: autoWbButton text: "Auto WB" Layout.fillWidth: true + Layout.preferredHeight: imageEditorDialog.secondaryButtonHeight font.pixelSize: 12 onClicked: { if (imageEditorDialog.controllerRef) imageEditorDialog.controllerRef.auto_white_balance() @@ -316,6 +396,7 @@ Window { id: autoLevelsButton text: "Auto Levels" Layout.fillWidth: true + Layout.preferredHeight: imageEditorDialog.secondaryButtonHeight font.pixelSize: 12 onClicked: { if (imageEditorDialog.controllerRef) imageEditorDialog.controllerRef.auto_levels() @@ -329,7 +410,7 @@ Window { // --- Effects Group --- Loader { sourceComponent: sectionHeader - onLoaded: item.text = "✨ Effects" + onLoaded: item.text = "EFFECTS" } ListModel { id: effectsModel @@ -364,7 +445,7 @@ Window { // --- Transform Group --- Loader { sourceComponent: sectionHeader - onLoaded: item.text = "🔄 Transform" + onLoaded: item.text = "TRANSFORM" } RowLayout { Layout.fillWidth: true @@ -400,15 +481,15 @@ Window { text: "Reset" flat: true Layout.preferredWidth: 80 - Material.foreground: imageEditorDialog.textColor + Material.foreground: "#6b6764" onClicked: { if (imageEditorDialog.controllerRef) imageEditorDialog.controllerRef.reset_edit_parameters() imageEditorDialog.updatePulse++ } background: Rectangle { - color: resetButton.down ? "#20ffffff" : "transparent" + color: "transparent" radius: 4 - border.color: resetButton.hovered ? "#40ffffff" : "transparent" + border.color: "transparent" } } @@ -431,9 +512,10 @@ Window { verticalAlignment: Text.AlignVCenter } background: Rectangle { - color: closeEditorButton.down ? "#40ffffff" : "#20ffffff" + color: closeEditorButton.down ? "#20ffffff" : "transparent" radius: 4 - border.color: closeEditorButton.hovered ? "#60ffffff" : "transparent" + border.color: closeEditorButton.hovered ? "#60ffffff" : imageEditorDialog.controlBorder + border.width: 1 } } @@ -608,8 +690,8 @@ Window { Rectangle { anchors.fill: parent radius: 3 - color: imageEditorDialog.controlBg - border.color: imageEditorDialog.controlBorder + color: "#2e2e2e" + border.color: "#383838" border.width: 1 } @@ -639,9 +721,9 @@ Window { width: 12 height: 12 radius: 6 - color: slider.pressed ? imageEditorDialog.accentColor : "white" - border.color: slider.pressed ? "white" : imageEditorDialog.accentColor - border.width: 2 + color: "#e8e6e3" + border.color: slider.pressed ? imageEditorDialog.accentColor : "#5a5755" + border.width: 1 // Glow/Scale effect on hover scale: hoverHandler.hovered || slider.pressed ? 1.3 : 1.0 @@ -654,96 +736,27 @@ Window { } } - // Refined SpinBox - SpinBox { - id: valueInput - from: sliderRow.minVal - to: sliderRow.maxVal - stepSize: 1 - editable: true - Layout.preferredWidth: 80 - Layout.alignment: Qt.AlignVCenter - - value: sliderRow.isReversed ? -slider.value : slider.value - - onValueModified: { - var val = value - var sendValue = sliderRow.isReversed ? -val : val - if (imageEditorDialog.controllerRef) imageEditorDialog.controllerRef.set_edit_parameter(sliderRow.key, sendValue / sliderRow.maxVal) - imageEditorDialog.updatePulse++ - } - - contentItem: TextInput { - z: 2 - text: valueInput.displayText - font.pixelSize: 12 - font.family: valueInput.font.family - color: imageEditorDialog.textColor - selectionColor: imageEditorDialog.accentColor - selectedTextColor: "#ffffff" - horizontalAlignment: Qt.AlignHCenter - verticalAlignment: Qt.AlignVCenter - readOnly: !valueInput.editable - validator: valueInput.validator - inputMethodHints: Qt.ImhFormattedNumbersOnly - - // Highlight on focus - onActiveFocusChanged: { - if(activeFocus) valueInputBackground.border.color = imageEditorDialog.accentColor - else valueInputBackground.border.color = imageEditorDialog.controlBorder - } - } - - up.indicator: Item { - x: valueInput.mirrored ? 0 : parent.width - width - height: parent.height - width: 16 // Smaller button - - Rectangle { - anchors.centerIn: parent - width: 16; height: 16 - radius: 2 - color: valueInput.up.pressed ? imageEditorDialog.accentColor : (valueInput.up.hovered ? Qt.lighter(imageEditorDialog.controlBg, 1.5) : "transparent") - - Text { - text: "+" - font.pixelSize: 12 - anchors.centerIn: parent - color: valueInput.up.pressed ? "white" : imageEditorDialog.textColor - } - } - } + Text { + id: valueReadout + property int displayValue: Math.round(sliderRow.isReversed ? -slider.value : slider.value) - down.indicator: Item { - x: valueInput.mirrored ? parent.width - width : 0 - height: parent.height - width: 16 // Smaller button - - Rectangle { - anchors.centerIn: parent - width: 16; height: 16 - radius: 2 - color: valueInput.down.pressed ? imageEditorDialog.accentColor : (valueInput.down.hovered ? Qt.lighter(imageEditorDialog.controlBg, 1.5) : "transparent") - - Text { - text: "-" - font.pixelSize: 12 - anchors.centerIn: parent - color: valueInput.down.pressed ? "white" : imageEditorDialog.textColor - } + text: displayValue === 0 ? "0" : (displayValue > 0 ? "+" + displayValue : "−" + Math.abs(displayValue)) + Layout.preferredWidth: 40 + Layout.alignment: Qt.AlignVCenter + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + font.family: "IBM Plex Mono" + font.pixelSize: 11 + color: displayValue === 0 ? "#6b6764" : "#e8e6e3" + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (!slider.isResetting) + slider.triggerReset() } } - - background: Rectangle { - id: valueInputBackground - implicitWidth: 80 - color: "transparent" - border.color: imageEditorDialog.controlBorder - border.width: 1 - radius: 4 - - Behavior on border.color { ColorAnimation { duration: 150 } } - } } } } From 4e5094de744f06538e4a6be64533bba6d88a0ccf Mon Sep 17 00:00:00 2001 From: AlanRockefeller Date: Tue, 28 Apr 2026 14:47:22 -0700 Subject: [PATCH 2/5] Add overlaid histogram --- faststack/qml/HistogramWindow.qml | 156 +++++++++++++++++++++------- faststack/qml/ImageEditorDialog.qml | 143 ++++++++++++++++--------- faststack/qml/OverlaidHistogram.qml | 142 +++++++++++++++++++++++++ 3 files changed, 354 insertions(+), 87 deletions(-) create mode 100644 faststack/qml/OverlaidHistogram.qml diff --git a/faststack/qml/HistogramWindow.qml b/faststack/qml/HistogramWindow.qml index 2a7d6e3..612de67 100644 --- a/faststack/qml/HistogramWindow.qml +++ b/faststack/qml/HistogramWindow.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Window import QtQuick.Layouts 1.15 +import QtCore Window { id: histogramWindow @@ -11,6 +12,12 @@ Window { minimumHeight: 50 property var uiStateRef: typeof uiState !== "undefined" ? uiState : null property var controllerRef: typeof controller !== "undefined" ? controller : null + Settings { + id: histSettings + category: "histogram" + property bool overlaidMode: true + } + visible: histogramWindow.uiStateRef ? histogramWindow.uiStateRef.isHistogramVisible : false FocusScope { @@ -54,54 +61,123 @@ Window { color: windowBackgroundColor - RowLayout { + ColumnLayout { anchors.fill: parent anchors.margins: histogramWindow.width > 200 ? 15 : 2 - spacing: histogramWindow.width > 200 ? 15 : 2 + spacing: histogramWindow.width > 200 ? 8 : 2 - SingleChannelHistogram { - Layout.fillWidth: true - Layout.fillHeight: true - - channelName: "Red" - channelColor: "#e15050" - gridLineColor: histogramWindow.gridLineColor - dangerColor: histogramWindow.dangerColor - textColor: histogramWindow.primaryTextColor - - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_preclip"] || 0) : 0 - } - - SingleChannelHistogram { + // Histogram toggle button + RowLayout { Layout.fillWidth: true - Layout.fillHeight: true - - channelName: "Green" - channelColor: "#50e150" - gridLineColor: histogramWindow.gridLineColor - dangerColor: histogramWindow.dangerColor - textColor: histogramWindow.primaryTextColor - - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_preclip"] || 0) : 0 + Item { Layout.fillWidth: true } + Row { + spacing: 0 + Rectangle { + width: 70; height: 20 + radius: 3 + color: histSettings.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + Text { + anchors.centerIn: parent + text: "Overlaid" + font.pixelSize: 10 + color: histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" + } + MouseArea { + anchors.fill: parent + onClicked: { + histSettings.overlaidMode = true + } + } + } + Rectangle { + width: 70; height: 20 + radius: 3 + color: !histSettings.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + Text { + anchors.centerIn: parent + text: "Channels" + font.pixelSize: 10 + color: !histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" + } + MouseArea { + anchors.fill: parent + onClicked: { + histSettings.overlaidMode = false + } + } + } + } } - SingleChannelHistogram { + // Histogram display (overlaid or 3-channel) + Item { Layout.fillWidth: true Layout.fillHeight: true - - channelName: "Blue" - channelColor: "#5050e1" - gridLineColor: histogramWindow.gridLineColor - dangerColor: histogramWindow.dangerColor - textColor: histogramWindow.primaryTextColor - - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_preclip"] || 0) : 0 + + OverlaidHistogram { + anchors.fill: parent + visible: histSettings.overlaidMode + rData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r"] || []) : [] + gData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g"] || []) : [] + bData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b"] || []) : [] + rClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 + gClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 + bClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 + gridLineColor: histogramWindow.gridLineColor + } + + RowLayout { + anchors.fill: parent + visible: !histSettings.overlaidMode + spacing: histogramWindow.width > 200 ? 15 : 2 + + SingleChannelHistogram { + Layout.fillWidth: true + Layout.fillHeight: true + + channelName: "Red" + channelColor: "#e15050" + gridLineColor: histogramWindow.gridLineColor + dangerColor: histogramWindow.dangerColor + textColor: histogramWindow.primaryTextColor + + histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r"] || []) : [] + clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 + preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_preclip"] || 0) : 0 + } + + SingleChannelHistogram { + Layout.fillWidth: true + Layout.fillHeight: true + + channelName: "Green" + channelColor: "#50e150" + gridLineColor: histogramWindow.gridLineColor + dangerColor: histogramWindow.dangerColor + textColor: histogramWindow.primaryTextColor + + histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g"] || []) : [] + clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 + preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_preclip"] || 0) : 0 + } + + SingleChannelHistogram { + Layout.fillWidth: true + Layout.fillHeight: true + + channelName: "Blue" + channelColor: "#5050e1" + gridLineColor: histogramWindow.gridLineColor + dangerColor: histogramWindow.dangerColor + textColor: histogramWindow.primaryTextColor + + histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b"] || []) : [] + clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 + preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_preclip"] || 0) : 0 + } + } } } } diff --git a/faststack/qml/ImageEditorDialog.qml b/faststack/qml/ImageEditorDialog.qml index 5f16295..5ef1db7 100644 --- a/faststack/qml/ImageEditorDialog.qml +++ b/faststack/qml/ImageEditorDialog.qml @@ -5,6 +5,7 @@ import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.15 import QtQuick.Layouts 1.15 import QtQuick.Window 2.15 +import QtCore Window { id: imageEditorDialog @@ -15,6 +16,12 @@ Window { title: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.editorFilename ? "Image Editor - " + imageEditorDialog.uiStateRef.editorFilename + " (" + imageEditorDialog.uiStateRef.editorBitDepth + "-bit)" : "Image Editor" visible: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.isEditorOpen : false flags: Qt.Window | Qt.WindowTitleHint | Qt.WindowCloseButtonHint + Settings { + id: histSettings + category: "histogram" + property bool overlaidMode: true + } + property int updatePulse: 0 property color backgroundColor: "#1e1e1e" // Default dark background property color textColor: "white" // Default text color @@ -183,59 +190,101 @@ Window { } Repeater { model: detailModel; delegate: editSlider } - // --- Histogram Group --- + // Histogram toggle button RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 140 - Layout.topMargin: 5 - spacing: 5 - - SingleChannelHistogram { - Layout.fillWidth: true - Layout.fillHeight: true - - channelName: "Red" - channelColor: "#e15050" - gridLineColor: imageEditorDialog.controlBorder - dangerColor: "#40ff0000" - textColor: imageEditorDialog.textColor - minimal: false - - histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r"] || []) : [] - clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_clip"] || 0) : 0 - preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_preclip"] || 0) : 0 + Layout.topMargin: 8 + Item { Layout.fillWidth: true } + Row { + spacing: 0 + Rectangle { + width: 70; height: 20 + radius: 3 + color: histSettings.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + Text { + anchors.centerIn: parent + text: "Overlaid" + font.pixelSize: 10 + color: histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" + } + MouseArea { + anchors.fill: parent + onClicked: { + histSettings.overlaidMode = true + } + } + } + Rectangle { + width: 70; height: 20 + radius: 3 + color: !histSettings.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + Text { + anchors.centerIn: parent + text: "Channels" + font.pixelSize: 10 + color: !histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" + } + MouseArea { + anchors.fill: parent + onClicked: { + histSettings.overlaidMode = false + } + } + } } - - SingleChannelHistogram { - Layout.fillWidth: true - Layout.fillHeight: true - - channelName: "Green" - channelColor: "#50e150" + } + + // Histogram display (overlaid or 3-channel) + Item { + Layout.fillWidth: true + Layout.preferredHeight: 140 + + OverlaidHistogram { + anchors.fill: parent + visible: histSettings.overlaidMode + rData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r"] || []) : [] + gData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g"] || []) : [] + bData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b"] || []) : [] + rClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_clip"] || 0) : 0 + gClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_clip"] || 0) : 0 + bClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_clip"] || 0) : 0 gridLineColor: imageEditorDialog.controlBorder - dangerColor: "#40ff0000" - textColor: imageEditorDialog.textColor - minimal: false - - histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g"] || []) : [] - clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_clip"] || 0) : 0 - preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_preclip"] || 0) : 0 } - SingleChannelHistogram { - Layout.fillWidth: true - Layout.fillHeight: true - - channelName: "Blue" - channelColor: "#5050e1" - gridLineColor: imageEditorDialog.controlBorder - dangerColor: "#40ff0000" - textColor: imageEditorDialog.textColor - minimal: false - - histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b"] || []) : [] - clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_clip"] || 0) : 0 - preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_preclip"] || 0) : 0 + RowLayout { + anchors.fill: parent + visible: !histSettings.overlaidMode + spacing: 5 + + SingleChannelHistogram { + Layout.fillWidth: true; Layout.fillHeight: true + channelName: "Red"; channelColor: "#e15050" + gridLineColor: imageEditorDialog.controlBorder + dangerColor: "#40ff0000"; textColor: imageEditorDialog.textColor; minimal: false + histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r"] || []) : [] + clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_clip"] || 0) : 0 + preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_preclip"] || 0) : 0 + } + SingleChannelHistogram { + Layout.fillWidth: true; Layout.fillHeight: true + channelName: "Green"; channelColor: "#50e150" + gridLineColor: imageEditorDialog.controlBorder + dangerColor: "#40ff0000"; textColor: imageEditorDialog.textColor; minimal: false + histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g"] || []) : [] + clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_clip"] || 0) : 0 + preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_preclip"] || 0) : 0 + } + SingleChannelHistogram { + Layout.fillWidth: true; Layout.fillHeight: true + channelName: "Blue"; channelColor: "#5050e1" + gridLineColor: imageEditorDialog.controlBorder + dangerColor: "#40ff0000"; textColor: imageEditorDialog.textColor; minimal: false + histogramData: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b"] || []) : [] + clipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_clip"] || 0) : 0 + preClipCount: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_preclip"] || 0) : 0 + } } } diff --git a/faststack/qml/OverlaidHistogram.qml b/faststack/qml/OverlaidHistogram.qml new file mode 100644 index 0000000..4eef765 --- /dev/null +++ b/faststack/qml/OverlaidHistogram.qml @@ -0,0 +1,142 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +Item { + id: root + + property var rData: [] + property var gData: [] + property var bData: [] + property int rClip: 0 + property int gClip: 0 + property int bClip: 0 + property int rPreClip: 0 + property int gPreClip: 0 + property int bPreClip: 0 + property color gridLineColor: "#2e2e2e" + + onRDataChanged: canvas.requestPaint() + onGDataChanged: canvas.requestPaint() + onBDataChanged: canvas.requestPaint() + onGridLineColorChanged: canvas.requestPaint() + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Canvas { + id: canvas + + Layout.fillWidth: true + Layout.fillHeight: true + + onAvailableChanged: { if (available) requestPaint() } + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, width, height) + + // Background + ctx.fillStyle = "#0a0a0a" + ctx.fillRect(0, 0, width, height) + + // Grid lines (4 horizontal divisions) + ctx.strokeStyle = root.gridLineColor + ctx.lineWidth = 1 + for (var i = 1; i < 4; i++) { + ctx.beginPath() + ctx.moveTo(0, i * height / 4) + ctx.lineTo(width, i * height / 4) + ctx.stroke() + } + + // Danger zone (rightmost ~2%) + ctx.fillStyle = "rgba(255, 0, 0, 0.12)" + var dz = (250 / 255) * width + ctx.fillRect(dz, 0, width - dz, height) + + // Draw channels: B first (back), G, R (front) + var channels = [ + { data: root.bData, fill: "rgba(60, 80, 220, 0.40)", stroke: "rgba(80, 100, 230, 0.85)" }, + { data: root.gData, fill: "rgba(60, 180, 60, 0.38)", stroke: "rgba(80, 200, 80, 0.80)" }, + { data: root.rData, fill: "rgba(200, 60, 60, 0.50)", stroke: "rgba(220, 80, 80, 0.90)" }, + ] + + for (var ci = 0; ci < channels.length; ci++) { + var ch = channels[ci] + var data = ch.data + if (!data || data.length === 0) continue + + var len = data.length + var maxVal = 0 + for (var j = 0; j < len; j++) maxVal = Math.max(maxVal, data[j]) + if (maxVal === 0) continue + + ctx.beginPath() + ctx.moveTo(0, height) + + if (width >= len) { + for (var k = 0; k < len; k++) { + var x = len > 1 ? (k / (len - 1)) * width : width / 2 + ctx.lineTo(x, height - (data[k] / maxVal) * height) + } + } else { + // Max-pool downsampling (same as SingleChannelHistogram) + for (var px = 0; px < width; px++) { + var binStart = Math.floor((px / width) * len) + var binEnd = Math.ceil(((px + 1) / width) * len) + binStart = Math.max(0, Math.min(len - 1, binStart)) + binEnd = Math.max(binStart + 1, Math.min(len, binEnd)) + var localMax = 0 + for (var b = binStart; b < binEnd; b++) { + if (b < len) localMax = Math.max(localMax, data[b]) + } + ctx.lineTo(px, height - (localMax / maxVal) * height) + } + } + + ctx.lineTo(width, height) + ctx.closePath() + ctx.fillStyle = ch.fill + ctx.fill() + ctx.strokeStyle = ch.stroke + ctx.lineWidth = 1.2 + ctx.stroke() + } + } + } + + // Stat bar + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 20 + color: "#1a1a1a" + + Row { + anchors.centerIn: parent + spacing: 16 + + Text { + text: "R:" + root.rClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: root.rClip > 0 ? "#ff6060" : "#804040" + } + Text { + text: "G:" + root.gClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: root.gClip > 0 ? "#60ff60" : "#407040" + } + Text { + text: "B:" + root.bClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: root.bClip > 0 ? "#8080ff" : "#404080" + } + } + } + } +} From 3569e1c36b2bf3d2e2d128b0938fb475d4dc733b Mon Sep 17 00:00:00 2001 From: AlanRockefeller Date: Tue, 28 Apr 2026 15:45:32 -0700 Subject: [PATCH 3/5] Fix minor bugs --- faststack/qml/HistogramModeToggle.qml | 53 +++++++++++++++++++++ faststack/qml/HistogramWindow.qml | 48 +++---------------- faststack/qml/ImageEditorDialog.qml | 68 +++++++-------------------- faststack/qml/OverlaidHistogram.qml | 66 +++++++++++++++++++------- pyproject.toml | 2 +- 5 files changed, 126 insertions(+), 111 deletions(-) create mode 100644 faststack/qml/HistogramModeToggle.qml diff --git a/faststack/qml/HistogramModeToggle.qml b/faststack/qml/HistogramModeToggle.qml new file mode 100644 index 0000000..49228e5 --- /dev/null +++ b/faststack/qml/HistogramModeToggle.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Layouts 1.15 + +RowLayout { + id: root + + property bool overlaidMode: true + signal modeRequested(bool overlaid) + + Item { Layout.fillWidth: true } + + Row { + spacing: 0 + + Rectangle { + width: 70; height: 20 + radius: 3 + color: root.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + + Text { + anchors.centerIn: parent + text: "Overlaid" + font.pixelSize: 10 + color: root.overlaidMode ? "#e8e6e3" : "#6b6764" + } + + MouseArea { + anchors.fill: parent + onClicked: root.modeRequested(true) + } + } + + Rectangle { + width: 70; height: 20 + radius: 3 + color: !root.overlaidMode ? "#2c2c2c" : "transparent" + border.color: "#3a3a3a"; border.width: 1 + + Text { + anchors.centerIn: parent + text: "Channels" + font.pixelSize: 10 + color: !root.overlaidMode ? "#e8e6e3" : "#6b6764" + } + + MouseArea { + anchors.fill: parent + onClicked: root.modeRequested(false) + } + } + } +} diff --git a/faststack/qml/HistogramWindow.qml b/faststack/qml/HistogramWindow.qml index 612de67..4b74734 100644 --- a/faststack/qml/HistogramWindow.qml +++ b/faststack/qml/HistogramWindow.qml @@ -66,49 +66,10 @@ Window { anchors.margins: histogramWindow.width > 200 ? 15 : 2 spacing: histogramWindow.width > 200 ? 8 : 2 - // Histogram toggle button - RowLayout { + HistogramModeToggle { Layout.fillWidth: true - Item { Layout.fillWidth: true } - Row { - spacing: 0 - Rectangle { - width: 70; height: 20 - radius: 3 - color: histSettings.overlaidMode ? "#2c2c2c" : "transparent" - border.color: "#3a3a3a"; border.width: 1 - Text { - anchors.centerIn: parent - text: "Overlaid" - font.pixelSize: 10 - color: histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" - } - MouseArea { - anchors.fill: parent - onClicked: { - histSettings.overlaidMode = true - } - } - } - Rectangle { - width: 70; height: 20 - radius: 3 - color: !histSettings.overlaidMode ? "#2c2c2c" : "transparent" - border.color: "#3a3a3a"; border.width: 1 - Text { - anchors.centerIn: parent - text: "Channels" - font.pixelSize: 10 - color: !histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" - } - MouseArea { - anchors.fill: parent - onClicked: { - histSettings.overlaidMode = false - } - } - } - } + overlaidMode: histSettings.overlaidMode + onModeRequested: (overlaid) => histSettings.overlaidMode = overlaid } // Histogram display (overlaid or 3-channel) @@ -125,6 +86,9 @@ Window { rClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 gClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 bClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 + rPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_preclip"] || 0) : 0 + gPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_preclip"] || 0) : 0 + bPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_preclip"] || 0) : 0 gridLineColor: histogramWindow.gridLineColor } diff --git a/faststack/qml/ImageEditorDialog.qml b/faststack/qml/ImageEditorDialog.qml index 5ef1db7..e783034 100644 --- a/faststack/qml/ImageEditorDialog.qml +++ b/faststack/qml/ImageEditorDialog.qml @@ -190,50 +190,11 @@ Window { } Repeater { model: detailModel; delegate: editSlider } - // Histogram toggle button - RowLayout { + HistogramModeToggle { Layout.fillWidth: true Layout.topMargin: 8 - Item { Layout.fillWidth: true } - Row { - spacing: 0 - Rectangle { - width: 70; height: 20 - radius: 3 - color: histSettings.overlaidMode ? "#2c2c2c" : "transparent" - border.color: "#3a3a3a"; border.width: 1 - Text { - anchors.centerIn: parent - text: "Overlaid" - font.pixelSize: 10 - color: histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" - } - MouseArea { - anchors.fill: parent - onClicked: { - histSettings.overlaidMode = true - } - } - } - Rectangle { - width: 70; height: 20 - radius: 3 - color: !histSettings.overlaidMode ? "#2c2c2c" : "transparent" - border.color: "#3a3a3a"; border.width: 1 - Text { - anchors.centerIn: parent - text: "Channels" - font.pixelSize: 10 - color: !histSettings.overlaidMode ? "#e8e6e3" : "#6b6764" - } - MouseArea { - anchors.fill: parent - onClicked: { - histSettings.overlaidMode = false - } - } - } - } + overlaidMode: histSettings.overlaidMode + onModeRequested: (overlaid) => histSettings.overlaidMode = overlaid } // Histogram display (overlaid or 3-channel) @@ -250,6 +211,9 @@ Window { rClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_clip"] || 0) : 0 gClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_clip"] || 0) : 0 bClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_clip"] || 0) : 0 + rPreClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["r_preclip"] || 0) : 0 + gPreClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["g_preclip"] || 0) : 0 + bPreClip: imageEditorDialog.uiStateRef && imageEditorDialog.uiStateRef.histogramData ? (imageEditorDialog.uiStateRef.histogramData["b_preclip"] || 0) : 0 gridLineColor: imageEditorDialog.controlBorder } @@ -397,15 +361,17 @@ Window { imageEditorDialog.updatePulse++ } } - Label { - text: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.saveBehaviorMessage : "" - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 11 - color: imageEditorDialog.textColor - opacity: 0.7 - font.italic: true - } + } + + Label { + text: imageEditorDialog.uiStateRef ? imageEditorDialog.uiStateRef.saveBehaviorMessage : "" + visible: text.length > 0 + Layout.fillWidth: true + wrapMode: Text.WordWrap + font.pixelSize: 11 + color: imageEditorDialog.textColor + opacity: 0.7 + font.italic: true } Loader { sourceComponent: sectionSeparator diff --git a/faststack/qml/OverlaidHistogram.qml b/faststack/qml/OverlaidHistogram.qml index 4eef765..09a5ef1 100644 --- a/faststack/qml/OverlaidHistogram.qml +++ b/faststack/qml/OverlaidHistogram.qml @@ -116,25 +116,57 @@ Item { Row { anchors.centerIn: parent - spacing: 16 - - Text { - text: "R:" + root.rClip - font.pixelSize: 10 - font.family: "IBM Plex Mono" - color: root.rClip > 0 ? "#ff6060" : "#804040" + spacing: 14 + + Row { + spacing: 4 + Text { + text: "R P:" + root.rPreClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: "#804040" + } + Text { + text: "C:" + root.rClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + font.bold: root.rClip > 0 + color: root.rClip > 0 ? "#ff6060" : "#804040" + } } - Text { - text: "G:" + root.gClip - font.pixelSize: 10 - font.family: "IBM Plex Mono" - color: root.gClip > 0 ? "#60ff60" : "#407040" + + Row { + spacing: 4 + Text { + text: "G P:" + root.gPreClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: "#407040" + } + Text { + text: "C:" + root.gClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + font.bold: root.gClip > 0 + color: root.gClip > 0 ? "#60ff60" : "#407040" + } } - Text { - text: "B:" + root.bClip - font.pixelSize: 10 - font.family: "IBM Plex Mono" - color: root.bClip > 0 ? "#8080ff" : "#404080" + + Row { + spacing: 4 + Text { + text: "B P:" + root.bPreClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + color: "#404080" + } + Text { + text: "C:" + root.bClip + font.pixelSize: 10 + font.family: "IBM Plex Mono" + font.bold: root.bClip > 0 + color: root.bClip > 0 ? "#8080ff" : "#404080" + } } } } diff --git a/pyproject.toml b/pyproject.toml index cb86782..f156b2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ classifiers = [ "Operating System :: POSIX :: Linux", ] dependencies = [ - "PySide6>=6.0,<7.0", + "PySide6>=6.5,<7.0", "PyTurboJPEG>=1.8,<2.0", "numpy>=2.0,<3.0", "cachetools>=5.0,<6.0", From 538ec5c1bbf1dd3df85a2d047108915dd210ee03 Mon Sep 17 00:00:00 2001 From: AlanRockefeller Date: Wed, 29 Apr 2026 00:16:38 -0700 Subject: [PATCH 4/5] spell out clip and pre-clip --- faststack/qml/HistogramWindow.qml | 46 +++++++++++-------- faststack/qml/OverlaidHistogram.qml | 58 +++++++++++++++--------- faststack/qml/SingleChannelHistogram.qml | 11 +++-- 3 files changed, 70 insertions(+), 45 deletions(-) diff --git a/faststack/qml/HistogramWindow.qml b/faststack/qml/HistogramWindow.qml index 4b74734..17a0a6e 100644 --- a/faststack/qml/HistogramWindow.qml +++ b/faststack/qml/HistogramWindow.qml @@ -58,6 +58,16 @@ Window { property color primaryTextColor: "#222222" property color gridLineColor: "#dcdcdc" property color dangerColor: Qt.rgba(1, 0, 0, 0.25) + property var histogramDataRef: histogramWindow.uiStateRef ? histogramWindow.uiStateRef.histogramData : null + property var rHistogramData: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["r"] || []) : [] + property var gHistogramData: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["g"] || []) : [] + property var bHistogramData: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["b"] || []) : [] + property int rClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["r_clip"] || 0) : 0 + property int gClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["g_clip"] || 0) : 0 + property int bClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["b_clip"] || 0) : 0 + property int rPreClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["r_preclip"] || 0) : 0 + property int gPreClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["g_preclip"] || 0) : 0 + property int bPreClipCount: histogramWindow.histogramDataRef ? (histogramWindow.histogramDataRef["b_preclip"] || 0) : 0 color: windowBackgroundColor @@ -80,15 +90,15 @@ Window { OverlaidHistogram { anchors.fill: parent visible: histSettings.overlaidMode - rData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r"] || []) : [] - gData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g"] || []) : [] - bData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b"] || []) : [] - rClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 - gClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 - bClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 - rPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_preclip"] || 0) : 0 - gPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_preclip"] || 0) : 0 - bPreClip: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_preclip"] || 0) : 0 + rData: histogramWindow.rHistogramData + gData: histogramWindow.gHistogramData + bData: histogramWindow.bHistogramData + rClip: histogramWindow.rClipCount + gClip: histogramWindow.gClipCount + bClip: histogramWindow.bClipCount + rPreClip: histogramWindow.rPreClipCount + gPreClip: histogramWindow.gPreClipCount + bPreClip: histogramWindow.bPreClipCount gridLineColor: histogramWindow.gridLineColor } @@ -107,9 +117,9 @@ Window { dangerColor: histogramWindow.dangerColor textColor: histogramWindow.primaryTextColor - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["r_preclip"] || 0) : 0 + histogramData: histogramWindow.rHistogramData + clipCount: histogramWindow.rClipCount + preClipCount: histogramWindow.rPreClipCount } SingleChannelHistogram { @@ -122,9 +132,9 @@ Window { dangerColor: histogramWindow.dangerColor textColor: histogramWindow.primaryTextColor - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["g_preclip"] || 0) : 0 + histogramData: histogramWindow.gHistogramData + clipCount: histogramWindow.gClipCount + preClipCount: histogramWindow.gPreClipCount } SingleChannelHistogram { @@ -137,9 +147,9 @@ Window { dangerColor: histogramWindow.dangerColor textColor: histogramWindow.primaryTextColor - histogramData: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b"] || []) : [] - clipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_clip"] || 0) : 0 - preClipCount: histogramWindow.uiStateRef && histogramWindow.uiStateRef.histogramData ? (histogramWindow.uiStateRef.histogramData["b_preclip"] || 0) : 0 + histogramData: histogramWindow.bHistogramData + clipCount: histogramWindow.bClipCount + preClipCount: histogramWindow.bPreClipCount } } } diff --git a/faststack/qml/OverlaidHistogram.qml b/faststack/qml/OverlaidHistogram.qml index 09a5ef1..34baf8f 100644 --- a/faststack/qml/OverlaidHistogram.qml +++ b/faststack/qml/OverlaidHistogram.qml @@ -111,58 +111,72 @@ Item { // Stat bar Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 20 + Layout.preferredHeight: 38 color: "#1a1a1a" - Row { - anchors.centerIn: parent - spacing: 14 + RowLayout { + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 8 + spacing: 10 - Row { - spacing: 4 + Column { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 1 Text { - text: "R P:" + root.rPreClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "R Preclip: " + root.rPreClip + font.pixelSize: 9 font.family: "IBM Plex Mono" color: "#804040" } Text { - text: "C:" + root.rClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "Clipped: " + root.rClip + font.pixelSize: 9 font.family: "IBM Plex Mono" font.bold: root.rClip > 0 color: root.rClip > 0 ? "#ff6060" : "#804040" } } - Row { - spacing: 4 + Column { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 1 Text { - text: "G P:" + root.gPreClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "G Preclip: " + root.gPreClip + font.pixelSize: 9 font.family: "IBM Plex Mono" color: "#407040" } Text { - text: "C:" + root.gClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "Clipped: " + root.gClip + font.pixelSize: 9 font.family: "IBM Plex Mono" font.bold: root.gClip > 0 color: root.gClip > 0 ? "#60ff60" : "#407040" } } - Row { - spacing: 4 + Column { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 1 Text { - text: "B P:" + root.bPreClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "B Preclip: " + root.bPreClip + font.pixelSize: 9 font.family: "IBM Plex Mono" color: "#404080" } Text { - text: "C:" + root.bClip - font.pixelSize: 10 + anchors.horizontalCenter: parent.horizontalCenter + text: "Clipped: " + root.bClip + font.pixelSize: 9 font.family: "IBM Plex Mono" font.bold: root.bClip > 0 color: root.bClip > 0 ? "#8080ff" : "#404080" diff --git a/faststack/qml/SingleChannelHistogram.qml b/faststack/qml/SingleChannelHistogram.qml index ccf3deb..ba0cb03 100644 --- a/faststack/qml/SingleChannelHistogram.qml +++ b/faststack/qml/SingleChannelHistogram.qml @@ -140,22 +140,23 @@ Item { } } - RowLayout { + ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: 5 + spacing: 0 visible: !root.minimal && root.height > 80 Text { - text: "P:" + root.preClipCount + text: "Preclip: " + root.preClipCount color: root.textColor font.pixelSize: Math.max(8, Math.min(11, root.height / 15)) - visible: root.width > 120 + Layout.alignment: Qt.AlignHCenter } Text { - text: (root.width > 120 ? "Clipped: " : "C:") + root.clipCount + text: "Clipped: " + root.clipCount color: root.clipCount > 0 ? "red" : root.textColor font.bold: root.clipCount > 0 font.pixelSize: Math.max(8, Math.min(11, root.height / 15)) + Layout.alignment: Qt.AlignHCenter } } } From b453a94b6bfa0daf569de649d32aaff0c2aa6484 Mon Sep 17 00:00:00 2001 From: AlanRockefeller Date: Wed, 29 Apr 2026 00:34:20 -0700 Subject: [PATCH 5/5] Abbreviate clip/pre-clip when the window gets narrow --- faststack/qml/SingleChannelHistogram.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/faststack/qml/SingleChannelHistogram.qml b/faststack/qml/SingleChannelHistogram.qml index ba0cb03..f3924f9 100644 --- a/faststack/qml/SingleChannelHistogram.qml +++ b/faststack/qml/SingleChannelHistogram.qml @@ -11,6 +11,7 @@ Item { property color gridLineColor: "#50ffffff" // Default semi-transparent white property color dangerColor: Qt.rgba(1, 0, 0, 0.25) property color textColor: "white" + readonly property bool compactStats: root.width < 50 // Allow minimal mode (hide text) property bool minimal: false @@ -146,13 +147,13 @@ Item { visible: !root.minimal && root.height > 80 Text { - text: "Preclip: " + root.preClipCount + text: (root.compactStats ? "P: " : "Pre-clip: ") + root.preClipCount color: root.textColor font.pixelSize: Math.max(8, Math.min(11, root.height / 15)) Layout.alignment: Qt.AlignHCenter } Text { - text: "Clipped: " + root.clipCount + text: (root.compactStats ? "C: " : "Clipped: ") + root.clipCount color: root.clipCount > 0 ? "red" : root.textColor font.bold: root.clipCount > 0 font.pixelSize: Math.max(8, Math.min(11, root.height / 15))