fix(StatusImageCrop): fix artefacts due to offscreen caching

Fix by disabling layering, leftover from the previous approach of not
using Canvas.

Also investigate TODO regarding artefacts workaround and implement
a proper approach

Fixes: #6640
This commit is contained in:
Stefan 2022-08-05 13:31:49 +02:00 committed by Michał Cieślak
parent fc7c0a07a5
commit 0a6c1bfcac
3 changed files with 209 additions and 32 deletions

View File

@ -1,11 +1,20 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import QtQuick.Dialogs 1.3
import QtQml 2.14
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import QtGraphicalEffects 1.14
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
Item {
id: root
@ -15,6 +24,12 @@ Item {
property var testControls: [ctrl1, ctrl2, ctrl3]
property bool globalStylePreferRound: true
property var testImageList: ["qrc:/demoapp/data/logo-test-image.png",
"qrc:/demoapp/data/profile-image-2.jpeg",
"qrc:/demoapp/data/profile-image-1.jpeg"]
property int testImageIndex: 0
property int testSpacing: 0
property int testFrameMargins: 10
ColumnLayout {
id: mainLayout
@ -22,7 +37,12 @@ Item {
anchors.fill: parent
RowLayout {
spacing: root.testSpacing
ColumnLayout {
spacing: root.testSpacing
Layout.margins: root.testSpacing
Text {
text: `AR: ${ctrl1.aspectRatio.toFixed(2)}`
}
@ -32,8 +52,9 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
source: root.testImageList[root.testImageIndex]
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
margins: root.testFrameMargins
}
Text {
text: `AR: ${ctrl2.aspectRatio.toFixed(2)}`
@ -43,13 +64,15 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
source: root.testImageList[root.testImageIndex]
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rectangular : StatusImageCrop.WindowStyle.Rounded
aspectRatio: 16/9
enableCheckers: true
margins: root.testFrameMargins + 2
}
}
ColumnLayout {
Layout.margins: root.testSpacing
Text {
text: `AR: ${ctrl3.aspectRatio.toFixed(2)}`
}
@ -59,36 +82,178 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
source: "qrc:/demoapp/data/logo-test-image.png"
source: root.testImageList[root.testImageIndex]
windowStyle: globalStylePreferRound ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
aspectRatio: 0.8
margins: root.testFrameMargins + 5.3
}
}
}
Shortcut {
sequence: StandardKey.ZoomIn
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, -0.05))
RowLayout {
StatusButton {
text: qsTr("Cycle image")
onClicked: {
let newIndex = root.testImageIndex
newIndex++
if(newIndex >= root.testImageList.length)
newIndex = 0
root.testImageIndex = newIndex
}
}
}
Shortcut {
sequence: StandardKey.ZoomOut
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, 0.05))
StatusButton {
text: qsTr("Cycle spacing")
onClicked: {
root.testSpacing += (root.testSpacing/2) + 1
if(root.testSpacing > 35)
root.testSpacing = 0
}
}
}
Shortcut {
sequences: ["Ctrl+W"]
onActivated: globalStylePreferRound = !globalStylePreferRound
StatusButton {
text: qsTr("Cycle frame margins")
onClicked: {
root.testFrameMargins += (root.testFrameMargins/2) + 1
if(root.testFrameMargins > 50)
root.testFrameMargins = 0
}
}
StatusButton {
text: qsTr("Load external image")
onClicked: workflowLoader.active = true
}
}
}
Loader {
id: workflowLoader
sourceComponent: workflowComponent
active: false
onStatusChanged: {
if(status === Loader.Ready) {
item.imageFileDialogTitle = qsTr("Test Title")
item.title = "Test popup"
item.acceptButtonText = "Load Custom Image"
item.chooseImageToCrop()
}
}
Connections {
target: workflowLoader.item
function onImageCropped(image, cropRect) {
ctrl1.source = image
ctrl2.source = image
ctrl3.source = image
}
function onCanceled() {
workflowLoader.active = false
}
}
}
Component {
id: workflowComponent
Item {
id: workflowItem
property alias aspectRatio: imageCropper.aspectRatio
property alias windowStyle: imageCropper.windowStyle
/*required*/ property string imageFileDialogTitle: ""
/*required*/ property string title: ""
/*required*/ property string acceptButtonText: ""
property bool roundedImage: true
signal imageCropped(var image, var cropRect)
signal canceled()
function chooseImageToCrop() {
fileDialog.open()
}
FileDialog {
id: fileDialog
title: workflowItem.imageFileDialogTitle
folder: workflowItem.userSelectedImage ? imageCropper.source.substr(0, imageCropper.source.lastIndexOf("/")) : shortcuts.pictures
nameFilters: [qsTr("Supported image formats (%1)").arg("*.jpg *.jpeg *.jfif *.webp *.png *.heif")]
onAccepted: {
if (fileDialog.fileUrls.length > 0) {
imageCropper.source = fileDialog.fileUrls[0]
imageCropperModal.open()
}
}
onRejected: workflowItem.canceled()
} // FileDialog
StatusModal {
id: imageCropperModal
header.title: workflowItem.title
anchors.centerIn: Overlay.overlay
width: workflowItem.roundedImage ? 480 : 580
onClosed: workflowItem.canceled()
StatusImageCropPanel {
id: imageCropper
implicitHeight: workflowItem.roundedImage ? 350 : 370
anchors {
fill: parent
leftMargin: 10 * 2
rightMargin: 10 * 2
topMargin: 15
bottomMargin: 15
}
margins: workflowItem.roundedImage ? 10 : 20
windowStyle: workflowItem.roundedImage ? StatusImageCrop.WindowStyle.Rounded : StatusImageCrop.WindowStyle.Rectangular
enableCheckers: true
}
rightButtons: [
StatusButton {
text: workflowItem.acceptButtonText
enabled: imageCropper.sourceSize.width > 0 && imageCropper.sourceSize.height > 0
onClicked: {
workflowItem.imageCropped(imageCropper.source, imageCropper.cropRect)
imageCropperModal.close()
}
}
]
} // StatusModal
} // Item
}
Shortcut {
sequence: StandardKey.ZoomIn
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, -0.05))
}
}
}
Shortcut {
sequence: StandardKey.ZoomOut
onActivated: {
for(let i in testControls) {
const c = testControls[i]
c.setCropRect(ctrl1.inflateRectBy(c.cropRect, 0.05))
}
}
}
Shortcut {
sequences: ["Ctrl+W"]
onActivated: globalStylePreferRound = !globalStylePreferRound
}
}

View File

@ -142,6 +142,7 @@ Item {
anchors.top: parent.top
anchors.right: cropEditor.left
anchors.bottom: parent.bottom
anchors.bottomMargin: -1
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
@ -160,8 +161,9 @@ Item {
StatusImageCrop {
id: cropEditor
anchors.centerIn: parent
width: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height * aspectRatio : cropSpaceItem.width - root.margins * 2
height: aspectRatio < cropSpaceItem.width/cropSpaceItem.height ? cropSpaceItem.height - root.margins * 2 : cropSpaceItem.width / aspectRatio
readonly property bool cropARSmall: aspectRatio < cropSpaceItem.width/cropSpaceItem.height
width: cropARSmall ? cropSpaceItem.height * aspectRatio : cropSpaceItem.width - root.margins * 2
height: cropARSmall ? cropSpaceItem.height - root.margins * 2 : cropSpaceItem.width / aspectRatio
}
Rectangle {
@ -170,6 +172,7 @@ Item {
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: -1
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
@ -181,6 +184,7 @@ Item {
anchors.top: cropEditor.bottom
anchors.right: rightOverlay.left
anchors.bottom: parent.bottom
anchors.bottomMargin: -1
color: wallColor
opacity: wallTransparency
z: cropEditor.z + 1
@ -190,6 +194,7 @@ Item {
Canvas {
visible: root.enableCheckers
anchors.fill: parent
anchors.bottomMargin: -1
onPaint: {
var ctx = getContext("2d")
for(let xI = 0; xI < Math.ceil(width/10); xI++) {

View File

@ -303,8 +303,6 @@ Item {
anchors.fill: parent
layer.enabled: true
property bool widthFit: (root.width / root.aspectRatio) <= root.height
// Window width
property real wW: widthFit ? root.width : root.height * root.aspectRatio
@ -312,22 +310,31 @@ Item {
property real wH: widthFit ? root.width / root.aspectRatio : root.height
onPaint: {
const wSize = Qt.size(wW, wH)
const contentSize = canvasSize
const clearSize = Qt.size(Math.ceil(canvasSize.width), Math.ceil(canvasSize.height))
const r = root.radius
var ctx = getContext("2d")
ctx.save()
// TODO: observed drawing artefacts at the edge. Draw one pixel more for now until root cause is found
ctx.clearRect(0, 0, width + 1, height + 1)
ctx.clearRect(0, 0, clearSize.width, clearSize.height)
// Fill all with wallColor in order to clip the window from it
ctx.fillStyle = Qt.rgba(root.wallColor.r, root.wallColor.g, root.wallColor.b, root.wallTransparency)
ctx.fillRect(0, 0, width + 1, height + 1)
ctx.fillRect(0, 0, clearSize.width, clearSize.height)
// Cut opaque new pixels from background
ctx.globalCompositeOperation = "source-out"
// Draw the window
ctx.beginPath()
const cW = Qt.rect((width - wW)/2, (height - wH)/2, wW, wH)
const cW = Qt.rect((contentSize.width - wSize.width)/2, (contentSize.height - wSize.height)/2, wSize.width, wSize.height)
if(root.windowStyle === StatusImageCrop.WindowStyle.Rounded)
ctx.ellipse(cW.x, cW.y, cW.width, cW.height)
else if(root.windowStyle === StatusImageCrop.WindowStyle.Rectangular)
ctx.roundedRect(cW.x, cW.y, cW.width, cW.height, root.radius, root.radius)
ctx.roundedRect(cW.x, cW.y, cW.width, cW.height, r, r)
ctx.fill()
ctx.restore()
}