EffectComposer: Fix effect preview zoom

Zoom must scale the component showing the preview, not the source
image. This way the effect stays consistent regardless of the zoom
level.

Fixes: QDS-11899
Change-Id: I550eb9ff693c24a853f5c25d9d79fa146448663f
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2024-02-09 16:31:30 +02:00
parent c90970afed
commit 93993c322e
2 changed files with 129 additions and 110 deletions

View File

@@ -22,11 +22,13 @@ Column {
readonly property int previewMargin: 5 readonly property int previewMargin: 5
property real previewScale: 1
// Create a dummy parent to host the effect qml object // Create a dummy parent to host the effect qml object
function createNewComponent() { function createNewComponent() {
// If we have a working effect, do not show preview image as it shows through // If we have a working effect, do not show preview image as it shows through
// transparent parts of the final image // transparent parts of the final image
source.visible = false; placeHolder.visible = false;
var oldComponent = componentParent.children[0]; var oldComponent = componentParent.children[0];
if (oldComponent) if (oldComponent)
@@ -48,7 +50,7 @@ Column {
errorLine = e.lineNumber; errorLine = e.lineNumber;
} }
effectComposerModel.setEffectError(errorString, 0, errorLine); effectComposerModel.setEffectError(errorString, 0, errorLine);
source.visible = true; placeHolder.visible = true;
} }
} }
@@ -84,42 +86,42 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale < 3 enabled: root.previewScale < 3
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.zoomIn_medium buttonIcon: StudioTheme.Constants.zoomIn_medium
tooltip: qsTr("Zoom In") tooltip: qsTr("Zoom In")
onClicked: { onClicked: {
sourceImage.enableAnim(true) imageScaler.enableAnim(true)
sourceImage.scale += .2 root.previewScale += .2
sourceImage.enableAnim(false) imageScaler.enableAnim(false)
zoomIndicator.show() zoomIndicator.show()
} }
} }
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale > .4 enabled: root.previewScale > .4
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.zoomOut_medium buttonIcon: StudioTheme.Constants.zoomOut_medium
tooltip: qsTr("Zoom out") tooltip: qsTr("Zoom out")
onClicked: { onClicked: {
sourceImage.enableAnim(true) imageScaler.enableAnim(true)
sourceImage.scale -= .2 root.previewScale -= .2
sourceImage.enableAnim(false) imageScaler.enableAnim(false)
zoomIndicator.show() zoomIndicator.show()
} }
} }
HelperWidgets.AbstractButton { HelperWidgets.AbstractButton {
enabled: sourceImage.scale !== 1 || sourceImage.x !== root.previewMargin enabled: root.previewScale !== 1 || imageScaler.x !== root.previewMargin
|| sourceImage.y !== root.previewMargin || imageScaler.y !== root.previewMargin
style: StudioTheme.Values.viewBarButtonStyle style: StudioTheme.Values.viewBarButtonStyle
buttonIcon: StudioTheme.Constants.fitAll_medium buttonIcon: StudioTheme.Constants.fitAll_medium
tooltip: qsTr("Reset View") tooltip: qsTr("Reset View")
onClicked: { onClicked: {
sourceImage.resetTransforms() imageScaler.resetTransforms()
} }
} }
} }
@@ -187,8 +189,8 @@ Column {
property bool panning: false property bool panning: false
onPressed: { onPressed: {
pressX = mouseX - sourceImage.x pressX = mouseX - imageScaler.x
pressY = mouseY - sourceImage.y pressY = mouseY - imageScaler.y
panning = true panning = true
} }
@@ -197,22 +199,21 @@ Column {
} }
onWheel: (wheel) => { onWheel: (wheel) => {
let prevScale = sourceImage.scale let oldPoint = imageScaler.mapFromItem(mouseArea, Qt.point(wheel.x, wheel.y))
if (wheel.angleDelta.y > 0) { if (wheel.angleDelta.y > 0) {
if (sourceImage.scale < 3) if (root.previewScale < 3)
sourceImage.scale += .2 root.previewScale += .2
} else { } else {
if (sourceImage.scale > .4) if (root.previewScale > .4)
sourceImage.scale -= .2 root.previewScale -= .2
} }
let dScale = sourceImage.scale - prevScale let newPoint = imageScaler.mapFromItem(mouseArea, Qt.point(wheel.x, wheel.y))
imageScaler.x -= (oldPoint.x - newPoint.x) * imageScaler.scale
imageScaler.y -= (oldPoint.y - newPoint.y) * imageScaler.scale
sourceImage.x += (sourceImage.x + sourceImage.width * .5 - wheel.x) * dScale; imageScaler.checkBounds()
sourceImage.y += (sourceImage.y + sourceImage.height * .5 - wheel.y) * dScale;
sourceImage.checkBounds()
zoomIndicator.show() zoomIndicator.show()
} }
@@ -222,98 +223,40 @@ Column {
repeat: true repeat: true
onTriggered: { onTriggered: {
sourceImage.x = mouseArea.mouseX - mouseArea.pressX imageScaler.x = mouseArea.mouseX - mouseArea.pressX
sourceImage.y = mouseArea.mouseY - mouseArea.pressY imageScaler.y = mouseArea.mouseY - mouseArea.pressY
sourceImage.checkBounds() imageScaler.checkBounds()
} }
} }
} }
Image {
id: placeHolder
anchors.fill: parent
anchors.margins: root.previewMargin
fillMode: Image.PreserveAspectFit
source: imagesComboBox.selectedImage
smooth: true
}
Item { // Source item as a canvas (render target) for effect Item { // Source item as a canvas (render target) for effect
id: source id: source
anchors.fill: parent width: sourceImage.sourceSize.width
height: sourceImage.sourceSize.height
layer.enabled: true layer.enabled: true
layer.mipmap: true layer.mipmap: true
layer.smooth: true layer.smooth: true
visible: false
Image { Image {
id: sourceImage id: sourceImage
function checkBounds() { onSourceChanged: imageScaler.resetTransforms()
let edgeMargin = 10
// correction factor to account for an observation that edgeMargin decreases
// with increased zoom
let corrFactor = 10 * sourceImage.scale
let imgW2 = sourceImage.paintedWidth * sourceImage.scale * .5
let imgH2 = sourceImage.paintedHeight * sourceImage.scale * .5
let srcW2 = source.width * .5
let srcH2 = source.height * .5
if (sourceImage.x < -srcW2 - imgW2 + edgeMargin + corrFactor) fillMode: Image.Pad
sourceImage.x = -srcW2 - imgW2 + edgeMargin + corrFactor
else if (x > srcW2 + imgW2 - edgeMargin - corrFactor)
sourceImage.x = srcW2 + imgW2 - edgeMargin - corrFactor
if (sourceImage.y < -srcH2 - imgH2 + edgeMargin + corrFactor)
sourceImage.y = -srcH2 - imgH2 + edgeMargin + corrFactor
else if (y > srcH2 + imgH2 - edgeMargin - corrFactor)
sourceImage.y = srcH2 + imgH2 - edgeMargin - corrFactor
}
function resetTransforms() {
sourceImage.enableAnim(true)
sourceImage.scale = 1
sourceImage.x = root.previewMargin
sourceImage.y = root.previewMargin
sourceImage.enableAnim(false)
}
function enableAnim(flag) {
xBehavior.enabled = flag
yBehavior.enabled = flag
scaleBehavior.enabled = flag
}
onSourceChanged: sourceImage.resetTransforms()
fillMode: Image.PreserveAspectFit
x: root.previewMargin
y: root.previewMargin
width: parent.width - root.previewMargin * 2
height: parent.height - root.previewMargin * 2
source: imagesComboBox.selectedImage source: imagesComboBox.selectedImage
smooth: true smooth: true
Behavior on x {
id: xBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
Behavior on y {
id: yBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
Behavior on scale {
id: scaleBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
} }
} }
@@ -325,14 +268,90 @@ Column {
} }
Item { Item {
id: componentParent id: imageScaler
width: source.width x: root.previewMargin
height: source.height y: root.previewMargin
anchors.centerIn: parent width: parent.width - root.previewMargin * 2
// Cache the layer. This way heavy shaders rendering doesn't height: parent.height - root.previewMargin * 2
// slow down code editing & rest of the UI.
layer.enabled: true scale: root.previewScale * (width > height ? height / sourceImage.sourceSize.height
layer.smooth: true : width / sourceImage.sourceSize.width)
Behavior on x {
id: xBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
Behavior on y {
id: yBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
Behavior on scale {
id: scaleBehavior
enabled: false
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
function checkBounds() {
let edgeMargin = 10
// correction factor to account for an observation that edgeMargin decreases
// with increased zoom
let corrFactor = 10 * imageScaler.scale
let imgW2 = sourceImage.paintedWidth * imageScaler.scale * .5
let imgH2 = sourceImage.paintedHeight * imageScaler.scale * .5
let srcW2 = width * .5
let srcH2 = height * .5
if (imageScaler.x < -srcW2 - imgW2 + edgeMargin + corrFactor)
imageScaler.x = -srcW2 - imgW2 + edgeMargin + corrFactor
else if (x > srcW2 + imgW2 - edgeMargin - corrFactor)
imageScaler.x = srcW2 + imgW2 - edgeMargin - corrFactor
if (imageScaler.y < -srcH2 - imgH2 + edgeMargin + corrFactor)
imageScaler.y = -srcH2 - imgH2 + edgeMargin + corrFactor
else if (y > srcH2 + imgH2 - edgeMargin - corrFactor)
imageScaler.y = srcH2 + imgH2 - edgeMargin - corrFactor
}
function resetTransforms() {
imageScaler.enableAnim(true)
root.previewScale = 1
imageScaler.x = root.previewMargin
imageScaler.y = root.previewMargin
imageScaler.enableAnim(false)
}
function enableAnim(flag) {
xBehavior.enabled = flag
yBehavior.enabled = flag
scaleBehavior.enabled = flag
}
Item {
id: componentParent
width: source.width
height: source.height
anchors.centerIn: parent
// Cache the layer. This way heavy shaders rendering doesn't
// slow down code editing & rest of the UI.
layer.enabled: true
layer.smooth: true
}
} }
Rectangle { Rectangle {
@@ -349,7 +368,7 @@ Column {
} }
Text { Text {
text: Math.round(sourceImage.scale * 100) + "%" text: Math.round(root.previewScale * 100) + "%"
color: StudioTheme.Values.themeTextColor color: StudioTheme.Values.themeTextColor
anchors.centerIn: parent anchors.centerIn: parent
} }

View File

@@ -178,7 +178,7 @@ private:
QList<CompositionNode *> m_nodes; QList<CompositionNode *> m_nodes;
int m_selectedIndex = -1; int m_selectedIndex = -1;
bool m_isEmpty = true; bool m_isEmpty = false; // Init to false to force initial bake after setup
bool m_hasUnsavedChanges = false; bool m_hasUnsavedChanges = false;
// True when shaders haven't changed since last baking // True when shaders haven't changed since last baking
bool m_shadersUpToDate = true; bool m_shadersUpToDate = true;