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:
parent
fc7c0a07a5
commit
0a6c1bfcac
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue