Weird canvas behavior in QT/QML - qt

I have a strange issue in QML when trying to draw relatively simple graphics.
I'm using PyQt 5 with QT 5.11 and Ubuntu 18.04.
Since the graphics part I'm writing is mostly stationnary (changes about each 1 secs), I found out canvas would be a convenient way to draw graphics without using QPainter. But it's been noting but a nightmare with fonts.
For example, when drawing this simple QML component:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import QtGraphicalEffects 1.0
Rectangle {
id: cont_rect
Button {
text: "Repaint"
onClicked: {
cv.clearcv()
cv.requestPaint()
}
}
property string cvfont: "13px Ubuntu"
height: 80
width: 300
color: "#132931"
Canvas {
anchors.fill: parent
antialiasing: true
contextType: '2d'
id: cv
width: cont_rect.width
height: cont_rect.height
function clearcv() {
var ctx = cv.context
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = "black"
}
function drawStateLine() {
var ctx = (cv.context == null) ? getContext("2d", {alpha: false}) : cv.context
var x = width/2, y = height - 10
var txt_angle = -45 * 2*Math.PI / 360
ctx.save()
ctx.translate(x,y)
ctx.rotate(txt_angle)
ctx.fillStyle = "royalblue"
ctx.textAlign = "left"
ctx.font = cvfont
ctx.textBaseline = "middle"
ctx.globalAlpha = 1
ctx.fillText("Detected", 0, 0)
ctx.restore()
}
onPaint: {
drawStateLine();
}
}
}
I end up with extremely weird graphics behavior.
The FIRST time my component is rendered, I have good looking text:
Then, when I click my specially crafted button (for the sake of isolating the problem down to a simple component), I have:
Basically, whenever I am trying to repaint my damn canvas, first clearing it (I also tried ctx.reset(), ctx.fillRect with my background color, etc.), I end up with font rendering that is much less readable and that bothers me.
Is there anyone here who has an idea on how to avoid this?
I don't know if it's useful, but I use a 27in display with 1080p resolution.
Thanks!

Related

QML Canvas improperly rendered with QT_SCALE_FACTOR set

I have a Qt/QML program that runs on Ubuntu and on a Raspberry Pi 3B+. The program runs fine on both platforms however the Pi has a high DPI screen so I set a QT_SCALE_FACTOR to 1.3 on that device to make controls appear the right size. It works perfectly except for one thing: indicators on ComboBox and SpinBox controls are drawn the same size as if no QT_SCALE_FACTOR was set, and the rest of the rectangle they would fill if the QT_SCALE_FACTOR was applied is filled with random pixels. Setting a QT_SCALE_FACTOR on Ubuntu works without any issues.
I tried replacing the indicators with a custom one but I get the same result.
Leaving QT_SCALE_FACTOR unset produces correctly rendered indicators.
This is with Qt 5.13.2 and QtQuick.Controls 2.5 on Linux pi3 4.19.75-v7+ #1270 SMP Tue Sep 24 18:45:11 BST 2019 armv7l GNU/Linux
The pictures below illustrate the behavior with QT_SCALE_FACTOR=2:
This is the code for the custom indicator in the first picture:
ComboBox {
id: cb
...
width: 200
...
indicator: Canvas {
id: canvas
x: cb.width - width - cb.rightPadding
y: cb.topPadding + (cb.availableHeight - height) / 2
width: 12*2
height: 8*2
contextType: "2d"
Connections {
target: cb
function onPressedChanged() { canvas.requestPaint(); }
}
onPaint: {
var context = getContext("2d");
context.reset();
context.beginPath();
context.moveTo(0, 0);
context.lineTo(width, 0);
context.lineTo(width / 2, height);
context.closePath();
context.fillStyle = cb.pressed ? "#17a81a" : "#21be2b";
context.fill();
}
}

qml problem to resizeable scene3d in layout

In order to make a resizable 3d program i have created a scene3d in a layout and set the required properties. The scene resizing works properly while i change the height of windows but not in case of changing width.
I checked two examples of QTquick 3D and find out there's the same problem there.
To solve that i tried resizing windows by dynamic change scene scale in transform matrix4*4, and got this error:
after i ignore this, the program works correctly and resizes scene
AirplaneScene.qml:
Transform {
id: toyplaneTransform
matrix: {
var m = Qt.matrix4x4();
m.translate(Qt.vector3d(-30, -15, -30));
m.rotate(angle, Qt.vector3d(0, 1, 0));
m.rotate(rollAngle, Qt.vector3d(1, 0, 0));
m.rotate(pitchAngle, Qt.vector3d(0, 0,1));
m.scale(1.0 /scaleFactor );
return m;
}
main.qml:
Item{
id:sceneItem
Layout.preferredHeight: 700
Layout.preferredWidth: 1200
Layout.fillWidth: true
Layout.fillHeight: true
AirplaneScene{
id:airplane
scaleFactor: Math.round(mainLayout.width/sceneItem.width * 10) / 10
}
}
(when manually set the scalafactor this error not displaying)
I solved this problem by initialization scalefaktor with 3d scene to mainLayout ratio in AirplaneScene qml, and after loading page i changed scalefaktor dynamically
main.qml
property bool completeLoad: false
Component.onCompleted: {
completeLoad = true
}
onWidthChanged: {
if(completeLoad)
plane.scaleFactor= Math.round(mainLayout.width/sceneItem.width * 10) / 10
}
initialization scalefaktor in AirplaneScene.qml:
property real scaleFactor:1.3

QT-QML: How to create image (line) and work on it

I need to create and move an image on Qt but i cannot understand how to do that.
The image is just a line like the following:
The result that I'd like to obtain is to have a line which I can shift right or left (it should continue even outside the canvas for some cm) and color the line(or glowing it white with another background) when I call a function.
To do that im using a qml file in qt and I'm creating this line by using the Canvas object, but it is far away from the image showed before:
Canvas {
id: mycanvas
width: 1000; height: 600
contextType: "2d"
property var l_mez: 273
property var pp: 245
Path {
id: myPath
startX: 100; startY: 300
PathCurve { x: (100+(mycanvas.l_mez/2)); y: (300+mycanvas.pp/2) }
PathCurve { x: 100+(mycanvas.l_mez*3/2)-40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*3/2)+40; y: 300-mycanvas.pp/2+15 }
PathCurve { x: 100+(mycanvas.l_mez*2); y: 300}
PathCurve { x: 100+(mycanvas.l_mez*5/2); y: 300+mycanvas.pp/2 }
PathCurve { x: 100+(mycanvas.l_mez*7/2); y: 300-mycanvas.pp/2 }
}
onPaint: {
var ctx = getContext("2d");
ctx.strokeStyle = "grey";
ctx.path = myPath;
ctx.lineWidth =9
ctx.stroke();
}
The result is :
The question is, how can i properly draw the line on the first image?
I was thinking I could just create the image and import it but then I could not shift it and change the color of the line as I prefer.
Thanks for the help
PathCurve is really overspecifying the curve in your example which is where the waviness is coming from. I would instead use alternating PathLine and PathArc to achieve that look.
Note, you could do this with an image also if you were willing to give up on changing the line color. You can load your sample image above twice and place them next to each other so that they seamless connect. You would place them both inside of one Item and turn on clipping.
Then you translate them both left or right to give the sense of movement. When you get to the far end of the images in either direction you just jump the translation back by the image width and keep going.
It will look seamless and smooth to the user. Especially if you use an XAnimator as those run in a separate thread.

Problems with if statement in QML

I am trying to learn Qt and QML and I have hitten a wall.
I want to be able to determine the screen factor of the display and choose a background image accordingly, but I cannot.
This is my code:
import QtQuick 2.2
import QtQuick.Window 2.2
Image {
id: root
width: screenGeometry.width
height: screenGeometry.height
property real ratio: root.width / root.height
source: {
source = "../components/artwork/background.png"
if (ratio == 16.0 / 9.0) {
source = "../components/artwork/background_169.png"
}
else if (ratio == 16.0 / 10.0) {
source = "../components/artwork/background_1610.png"
}
else if (ratio == 4.0 / 3.0) {
source = "../components/artwork/background_43.png"
}
}
fillMode: Image.PreserveAspectFit
}
Independently from the screen size, it always choose the generic one.
From what I could see trying to debug the problem, it seems that the value of variable ratio is not understood within the if statement.
Searching for similar topics, I have found this one, but I can't still see the error.
Your help is much appreciated!

How to scroll QML ScrollView to center?

I have code like this:
ScrollView {
Image {
source: "..."
}
}
Image is higher than the ScrollView. How can I scroll the latter to the center of Image element?
Despite the appearence, ScrollView is tightly related to Flickable. Indeed, Flickable is used to control the visible area. Such an Item is available as the (readonly) property flickableItem. Flickable has the contentX and contentY properties to control the current visible area. These properties can be combined with the width and height of the ScrollView to position the visible area exactly at the center. Typically you have:
flickableItem.contentY = flickableItem.contentHeight / 2 - height / 2
flickableItem.contentX = flickableItem.contentWidth / 2 - width / 2
The difference is necessary since the first calls just moves the center to the top left point of the visible area (where contentX/contentY are located).
Here is a complete example with an Image as main child of the ScrollView.
Disclaimer: In the simple scenario proposed by the example, with a remotelly loaded image, sizes can still be unset when onCompleted is called, resulting in a centering code that doesn't work. By setting widths and heigths directly into code the problem is avoided. In a real scenario such detail should be unnecessary.
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
Window {
id: main
visible: true
width: 600; height: 350
ScrollView {
id: ss
width: 600
height: 350
Image {
id: name
width: 900
height: 600
source: "http://www.joomlaworks.net/images/demos/galleries/abstract/7.jpg"
}
Component.onCompleted: {
flickableItem.contentY = flickableItem.contentHeight / 2 - height / 2
flickableItem.contentX = flickableItem.contentWidth / 2 - width / 2
}
}
}
Here is a solution for QQC2. Tested on Qt 5.12.
The solution is simple, just store the scrollBar pointer and control it however you want.
ScrollView2.qml
import QtQuick 2.0
import QtQuick.Controls 2.4
ScrollView {
id: root
property ScrollBar hScrollBar: ScrollBar.horizontal
property ScrollBar vScrollBar: ScrollBar.vertical
/**
* #param type [Qt.Horizontal, Qt.Vertical]
* #param ratio 0.0 to 1.0
*/
function scrollTo(type, ratio) {
var scrollFunc = function (bar, ratio) {
bar.setPosition(ratio - bar.size/2)
}
switch(type) {
case Qt.Horizontal:
scrollFunc(root.hScrollBar, ratio)
break;
case Qt.Vertical:
scrollFunc(root.vScrollBar, ratio)
break;
}
}
}
main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 500
height: 500
ScrollView2 {
id: scroll
anchors.fill: parent
Text {
width: 1000
height: 1000
text: "ABC"
font.pixelSize: 800
Component.onCompleted: {
scroll.scrollTo(Qt.Horizontal, 0.5)
scroll.scrollTo(Qt.Vertical, 0.5)
}
}
}
}
Since Qt 5.14 (maybe already in 5.12) you can now simply do:
ScrollView {
Component.onCompleted {
// scroll to vertical center
ScrollBar.vertical.position = 0.5
}
// put your content item here:
Image {
// …
}
}

Resources