Smooth animation of second hand - animation

I am building a simple animated clock with QML and it works, but I want to have a "sweep second" in which the second hand rotates continuously and smoothly rather than abruptly transitioning from second to second. Without the Behavior line in the code below, I get a stepped-second but otherwise correct behavior. With the Behavior line, it works perfectly until the seconds go from 59 back to 0, and then it goes counter clockwise which is not all the behavior I want.
I read that using SmoothedAnimation would help, but when I tried substituting SmoothedAnimation for NumberAnimation it does a stepped-second until the 59 to 0 transition and then goes counter clockwise.
How do I alter this to have it only rotate clockwise but do so smoothly?
import QtQuick 2.0
Item {
width: 320; height: 320
Item {
property var time: new Date()
id: wallClock
Timer {
interval: 1000
running: true
repeat: true
onTriggered: wallClock.time = new Date()
}
}
Rectangle {
id: rect1
x: parent.width/2
y: parent.height/2 - 100
width: 10; height: 70
color: "red"
transform: Rotation {
origin.x: rect1.width/2
origin.y: rect1.height
angle: wallClock.time.getSeconds()*6
Behavior on angle { NumberAnimation { duration: 1000 } }
}
}
}

Rather than a NumberAnimation or SmoothedAnimation, I think a RotationAnimation is what you want. The advantage there is that it allows you to force the direction to remain clockwise.
transform: Rotation {
origin.x: rect1.width/2
origin.y: rect1.height
angle: wallClock.time.getSeconds()*6
Behavior on angle { RotationAnimation { duration: 1000; direction: RotationAnimation.Clockwise} }
}

Related

QML slow performance animating a lot of rectangles

I'm creating a "dotted paper" component for my QML app. Essentially this components consists of a dotted background that the user can zoom upon. To create this I used the following code:
DottedPaper.qml
import QtQuick 2.15
Rectangle{
id: page
property int spacing: 50
// this property changes the spacing between the dots
property real contentScale: 1.0
Repeater{
id: rowRepeater
model: 200
Repeater{
id: row
property int rowIndex: index
model: 200
Rectangle{
id: circle
color: "red"
width: 4; height: 4
x: index * spacing * page.contentScale + width/2;
y: rowIndex * spacing * page.contentScale+ height/2
radius: width/2
}
}
}
}
Then in my main file I have the following:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Flickable{
clip: true // doesn't improve anything
anchors.fill: parent
contentWidth: paper.width
contentHeight: paper.height
DottedPaper{
id: paper
width: 10000; height: 10000;
clip: true
}
}
// the slider changes the content scale of the dotted paper
Slider{
from: 1
to: 3
onValueChanged: paper.contentScale = value
}
}
The problem with this code is that it's slow. Upon checking the application does not render the content at 60fps, but I get around 45 - 50 fps on my desktop. I suspect the issues is that QML is reevaluating the contentScale property binding for each circle in the background even if the circle is not visible on the screen. What is the best way to prevent this?
It works a lot better if you change your Repeaters to be ListViews. That's because a ListView is smart enough to only create enough delegates as needed for the visible space. (At first I thought a GridView would work, but that doesn't give you enough control over rows/columns.)
Rectangle{
id: page
property int cellWidth: 50 * contentScale
property int dotWidth: 4
// this property changes the spacing between the dots
property real contentScale: 1.0
ListView {
id: grid
model: 200
anchors.fill: parent
anchors.leftMargin: page.dotWidth / 2
anchors.topMargin: page.dotWidth / 2
spacing: page.cellWidth - page.dotWidth
delegate: ListView {
model: 200
width: window.width
height: page.dotWidth
orientation: ListView.Horizontal
spacing: page.cellWidth - page.dotWidth
property int rowIndex: index
delegate: Rectangle{
id: circle
color: "red"
width: page.dotWidth; height: page.dotWidth
radius: width/2
}
}
}
}

Qt Quick: DropShadow is very slow when its radius is getting changed

I'll begin with my testcase. It's pretty basic.
It creates a rectangle rotating in 3D, and a DropShadow element for the rectangle.
It animates the radius property of the DropShadow.
It creates a 1x1px Canvas3D repainted constantly, so I can check how often it manages to get repainted with all the other stuff going on (Canvas3D has a built-in fps property). I use that for measuring the app's framerate.
When the value of radius is animated, the app is running at 10FPS. When this value is not animated, the app is running at 60FPS.
The point of the 3D rotation is to have some animation in the rectWrapper element, so the shadow has to get constantly recalculated, even if its radius is not changed. This proves that calculating the shadow is very fast, and what's slowing it down is just the constant changing of the radius.
My code:
main.cpp: (the only unusual thing in it is AA_UseOpenGLES, which is necessary because of my sub par OpenGL drivers)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_UseOpenGLES);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
import QtCanvas3D 1.1
Window {
visible: true
width: 640
height: 480
Text {
text: canvas3d.fps + " FPS"
font.pointSize: 18
}
// `rectWrapper` is needed as a layer of indirection around
// `rect` so DropShadow is aware of `rect`'s the rotation
Item {
id: rectWrapper
width: rect.width
// +100 here to accomodate the extra space needed due to
// 3D perspective projection of the 3D-rotated inner item
height: rect.height + 100
anchors.centerIn: parent
visible: false
Rectangle {
id: rect
color: "red"
width: 300
height: 100
anchors.centerIn: parent
property real yRot: 50
transform: Rotation {
origin.x: rect.width/2;
origin.y: rect.height/2;
axis { x: 0; y: 1; z: 0 }
angle: rect.yRot
}
SequentialAnimation on yRot {
loops: Animation.Infinite
NumberAnimation {
from: -180; to: 180; duration: 5000
}
}
}
}
DropShadow {
source: rectWrapper
anchors.fill: rectWrapper
samples: 16
radius: 16
SequentialAnimation on radius {
//running: false
loops: Animation.Infinite
NumberAnimation {
from: 2; to: 16; duration: 1000
}
}
}
Canvas3D {
id: canvas3d
width: 1; height: 1 // nonzero size so it can be redrawn
property var gl;
onInitializeGL: {
// should get and save context, otherwise FPS isn't measured for some reason
gl = canvas3d.getContext("canvas3d", {depth:true, antialias:true, alpha:true});
}
}
}
Uncomment the running: false to see the huge speedup.
My theory: The blur shader used by DropShadow may be getting recompiled on every change of radius. I've heard of that happening when you upload new values of bool uniforms in GLSL, but in this case it's not a bool, it's a real.
The question: Why does this slowdown occur? Can I work around it?

DropShadow with static source is faster when using "cached:true"

I'll begin with my testcase. It creates 21 unchanging shadowed blue rectangles. It also creates a 1x1px Canvas3D repainted constantly, so I can check how often it manages to get repainted with all the other stuff going on (Canvas3D has a built-in fps property). When cached: true is set on the DropShadow items, I get 60 FPS. When not, I get 30 FPS. But what I expect is to get the same FPS in both cases, since I don't expect the shadows' blur to ever get recalculated, considering that the source rects never get updated.
main.cpp: (trivial)
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtCanvas3D 1.1
Window {
visible: true
width: 800
height: 600
id: window
Column {
Text {
text: canvas3d.fps + " FPS"
font.pointSize: 18
}
Flow {
width: window.width
spacing: 10
Repeater {
model: 21
ShadowedItem {
}
}
}
Canvas3D {
id: canvas3d
width: 1; height: 1 // nonzero size so it can be redrawn
property var gl;
onInitializeGL: {
// should get and save context, otherwise FPS isn't measured for some reason
gl = canvas3d.getContext("canvas3d", {depth:true, antialias:true, alpha:true});
}
}
}
}
ShadowedItem.qml:
import QtQuick 2.0
import QtGraphicalEffects 1.0
Item {
width: 100
height: 100
Rectangle {
anchors.fill: parent
id: rect
visible: false
color: "blue"
}
DropShadow {
source: rect
anchors.fill: rect
cached: true // !
radius: 8
}
}
Any idea on the difference in performance?
I posted a follow-up question about this. In the comments to it, I learned that when 1 item in the scene (e.g. the Canvas3D in this case) needs to be redrawn, the entire scene gets redrawn. Which means that every time my Canvas3D is redrawn (which is constantly), all the shadows get redrawn. This, if cached is false, means that the blur gets recalculated, hence the slowdown.

QML/QtQuick2 Anchor Item to static location on a scaled image

What I am attempting to do is anchor an Item over a particular place over an Image. The Image needs to use Image.PreserveAspectFit for the fillMode. The problem I am having is that the height/width/paintedWidth/paintedHeight of the Image are for the entire image "canvas" (I'm not sure what it's actually called), not the drawn part of the image itself. So I can't anchor to the actual image.
See code below for an example I tried with anchors (red rectangle) and with a child rectangle and x,y coordinates (green rectangle).
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
Rectangle {
id: bottomRight
width: 40
height: 40
color: "green"
x: parent.width * 0.75
y: parent.height * 0.75
}
}
Rectangle {
id: topLeft
width: 40
height: 40
color: "red"
anchors.top: image.top
anchors.left: image.left
anchors.topMargin: image.height * 0.25
anchors.leftMargin: image.width * 0.25
}
}
When you change the size of the window the placement of the rectangles is not in the same place over the image. I'd post some screenshots but I don't have enough reputation yet!
I poked around through the widget tree using the debugger but I can't seem to find any properties that provide the information about the scaling that I could use to calculate placement of the rectangles.
Update
I used the following functions to calculate the margins since I will be using potentially lots of these overlays.
function horMargin(val)
{
return ((image.width - image.paintedWidth)/2 + image.paintedWidth * val)
}
function verMargin(val)
{
return ((image.height - image.paintedHeight)/2 + image.paintedHeight * val)
}
Did you try to use paintedWidth and paintedHeight? :)
This will give you a rectangle that fills the painted image:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
height: 400
width: 400
Image {
id: image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: "image.jpg"
}
Rectangle {
x: (image.width - image.paintedWidth) / 2
y: (image.height - image.paintedHeight) / 2
width: image.paintedWidth
height: image.paintedHeight
color: "transparent"
border.color: "darkorange"
border.width: 2
}
}
From there, you can work out how to apply the margins that you need.

qml image size negative value

I want to make a QML animation with QML Image element height property.
But negative value for Image height property does not work in Timer.
Can you tell me what is wrong with this animation?
import QtQuick 1.1
Rectangle {
width: 300
height: 300
property int mySize: 10
Image {
x: 150; y: 150
width: 20
height: -mySize // animation does not work in Timer, Why?
//height: mySize // animation works in Timer
source: "Bar.jpg"
}
Timer {
interval: 200
running: true
repeat: true
onTriggered: mySize +=5
}
}
First, (to answer your question), you can't use negative sizes. Use scaling instead, which supports negative values.
Scale {
yScale: ... // <-- change this value; 1 = original size
Image {
x: 150; y: 150
width: 20; height: 10 // <-- use constant size
source: "Bar.jpg"
}
}
Second, you clearly should read about animations in QML. In QML, you don't need timers to implement animations. The idea is that you only tell the start and end value of the property to be animated, and activate this animation. You can also configure the speed / duration of the animation and even the easing curve (to slow the animation down at the end and fancy stuff like bouncing...). One example could be:
import QtQuick 1.1
Rectangle {
width: 300; height:300
Scale {
Image {
x: 150; y: 150
width: 20; height: 10
source: "Bar.jpg"
}
NumberAnimation on yScale {
from: 1
to: -1
running: ... // <-- Animation is running while expression is true
}
}
}
Or, if you don't want to use an expression with property binding on Animation.running, you can also use the methods Animation.start(), stop(), etc. (Set an id on the animation to make it addressable from JavaScript.)
But understanding and working with property bindings in expressions is a major part of QML, so you should try to express what you want using expressions, not using method calls, events, conditions, whatever. That's the way QML is and why it is beautiful. ;)

Resources