Apply gradient to drawn figure - qt

I have this MWE code here which is drawing a triangle with a set color:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
}
}
I am trying to figure out how to apply gradient to this code, so having 2 colors (for example yellow and red) the triangle would gradient from the first to the other like this (edited in paint):
I tried using Gradient object from the documentation here: https://doc.qt.io/qt-5/qml-qtquick-gradient.html but with no success.

createLinearGradient method is used to draw gradient on canvas.
Just create a gradient using this method, add colors of gradient and finally assign it to fillStyle.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
// create a gradient
var gradient = ctx.createLinearGradient(100,0,100,200)
gradient.addColorStop(0, "yellow")
gradient.addColorStop(1, "red")
ctx.fillStyle = gradient // assign gradient
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
}
}
Here is the output
Also check simple example on gradient from QML book.

Related

QML: Draw geofence area on QML Map

Any ideas how to draw custom geofence area in QML (Qt Location module) similar to this:
Something similar to MapCircle or MapPolygon but filled outside of the region? And have custom filling pattern?
You probably can play with one of MapItems, for example MapQuickItem allows you put any QML item inside, for example I use Canvas for that:
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75)
zoomLevel: 14
MapQuickItem {
id: marker
anchorPoint.x: image.width / 2
anchorPoint.y: image.height / 2
coordinate: map.center
sourceItem: Canvas {
id: image
width: map.width
height: map.height
onPaint: {
var ctx = getContext("2d");
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(image.width, 0);
ctx.lineTo(image.width, image.height);
ctx.lineTo(0, image.height);
ctx.lineTo(0, 0);
ctx.moveTo(image.width/2 + 50, image.height/2);
ctx.arc(image.width/2, image.height/2, 50, 0, 2 * Math.PI,0, true);
ctx.closePath();
ctx.fillStyle = Qt.rgba(1.0, 0, 0, 0.5);
ctx.strokeStyle = "red"
ctx.lineWidth = 10;
ctx.fill();
ctx.stroke();
}
}
}
}
Pay attention, the arc's counterclockwise is true to make a hole.
In the same way you can add images etc. But in a real project I would use some custom QQuickItem based item, just for better performance.

QML Circular Gauge

I'm currently creating a virtual dashboard and i would like to get type of progress bar behind needle as in this link: https://forum.qt.io/topic/89307/qml-circular-gauge-styling-needle-trailing-colour-glow .
So far, I've only done needle progress bar using Canvas. I dont understand how to use conicalGradient with opacity mask to achieve
the effect that I need.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
ApplicationWindow
{
id:root
visible: true
width: 1024
height: 600
property int external_width: 534
property int external_height: 533
property bool external_reverse: false
property int external_angle: 0
property int externalstart_angle: 138 //138
property int external_angle_limit: 360//264
property int external_radius: 235
property int external_lineWidth: 60
Canvas {
id: external_progress_bar
width: root.external_width
height: root.external_height
x: (root.width - width)/2
y: (root.height - height)/2
property real angle: 260
property real nextAngle: (Math.PI/180)*angle
property color col: "red"
onPaint: {
var ctx = getContext("2d");
ctx.reset();
ctx.beginPath();
ctx.arc(width/2, height/2, root.external_radius, (Math.PI/180) * root.externalstart_angle,(Math.PI/180) * root.externalstart_angle + nextAngle, root.center_reverse);
ctx.lineWidth = root.external_lineWidth
ctx.strokeStyle = col
ctx.stroke()
}
}
}
I would be very grateful for the sample code with explanations.
What you can do is using a ConicalGradient and a OpacityMask.
Create the same canvas with a different color. Then, a ConicalGradient from transparent to white and a mask to reduce the painted area to your canvas:
Canvas {
id: external_progress_bar
...
visible: false // Not visible (it will be painted by the mask)
}
ConicalGradient {
id: progress
anchors.fill: external_progress_bar
angle: 45.0 // Change this angle to move the gradient effect
gradient: Gradient {
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 0.1; color: "white" } // Just a part of the canvas
}
visible: false // Not visible (it will be painted by the mask)
}
OpacityMask {
anchors.fill: progress
source: external_progress_bar
maskSource: progress
invert: true
}
You will get :
For more explanation on OpacityMask, see the Qt documentation

Proper shadow drop on rotation

I am trying to create a rotatable triangle that will always cast a shadow on its base. So far I was able to create the code for making and rotating the triangle but the shadow part is problematic. Here is my minimal code example:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
DropShadow
{
anchors.fill: parent
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: parent
}
}
}
}
I am facing the following problems:
The parser gives me some errors about ShaderEffectSource, that recursive property has to be set, but I dont know whos property is that.
Starting M:\bitbucket\qtworkspace\build-mwe-Desktop_Qt_5_11_0_MinGW_32bit-Debug\debug\mwe.exe...
QML debugging is enabled. Only use this in a safe environment.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
QObject::disconnect: No such signal QObject::screenChanged(QScreen*) in items\qquickscreen.cpp:476
M:/bitbucket/qtworkspace/build-mwe-Desktop_Qt_5_11_0_MinGW_32bit-Debug/debug/mwe.exe exited with code 0
Somewhere in the web I have found a bug report that DropShadow property source cannot accept parent but since Canvas cannot be assigned with id, I am not sure how to do it. Other than this, the shadow renders correctly for rotAngle at 0 degrees:
It doesn't rotate correctly after adding some angle, for example 45 degrees:
It seems to me that the shadow is not being rotated together with the polygon. Can this be adjusted? The last thing is: How to hide shadow behind the polygon? At the moment its interfering with it.
I am not sure either the parser error is connected or not, that's why I have added it together here.
since Canvas cannot be assigned with id
Why Canvas can't be assigned with id?
The following code won't have that warning.
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real rotAngle: 0
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.rotate((Math.PI / 180) * rotAngle);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}
}
About the warning:
I believe that should not consider as a BUG. DropShadow is an Item, so if you nest with its parent, it will do like render a shadow on the shadow.
Why didn't the shadow rotated?
Because you are rotating the contents of Canvas.
Even rotate the polygon, these properties won't change. Always drop the shadow vertically.
horizontalOffset: 0
verticalOffset: 3
Rotate both:
Item {
anchors.fill: parent
rotation: 30
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
var trheight = Math.sqrt(Math.pow(trbase, 2) - Math.pow(trbase / 2, 2));
trheight = trheight * hFactor;
var hfBase = trbase * hFactor;
ctx.lineTo(hfBase / -2, trheight); // left arm
ctx.lineTo(hfBase / 2, trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}

Snap text to a point

In my test application I am drawing triangles. In the down left corner of this triangle (lest call it nr 2) I need to add text that is just under the line. If the triangle rotates, the text should rotate as well. Heres an "explanation" image:
And here is the code:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
Window
{
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle
{
id: rectMain
anchors.centerIn: parent
width: parent.width
height: parent.height
color: "white"
Item
{
anchors.fill: parent
rotation: 0
property real trheight: 0;
property real hfBase: 0;
Canvas
{
id: canvas
anchors.fill: parent
// set properties with default values
property real hFactor: 1 // height factor
property real trbase: 200
property color strokeColor: "black"
property color fillColor: "yellow"
property int lineWidth: 1
property real alpha: 1
property real parentWidth: parent.width; // try
property real parentHeight: parent.height;
onStrokeColorChanged: requestPaint();
onFillColorChanged: requestPaint();
onLineWidthChanged: requestPaint();
onPaint:
{
hFactor = Math.abs(hFactor)
var ctx = getContext("2d") // get context to draw with
ctx.clearRect(0, 0, width, height); // remove what is painted so far
ctx.lineWidth = lineWidth
ctx.strokeStyle = strokeColor
ctx.fillStyle = fillColor
ctx.globalAlpha = alpha
ctx.save();
ctx.beginPath();
ctx.translate(parentWidth / 2, parentHeight / 2);
ctx.moveTo(0, 0);
// drawing part, first calculate height using Pythagoras equation
parent.trheight = (Math.sqrt(Math.pow(trbase, 2) -
Math.pow(trbase / 2, 2))) * hFactor;
parent.hfBase = trbase * hFactor;
ctx.lineTo(parent.hfBase / -2, parent.trheight); // left arm
ctx.lineTo(parent.hfBase / 2, parent.trheight); // right arm
ctx.closePath(); // base drawn automatically
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
DropShadow
{
anchors.fill: canvas
horizontalOffset: 0
verticalOffset: 3
radius: 3
samples: 7
color: "#80000000"
source: canvas
}
}
Text
{
//x: ?
//y: ?
font.family: "RobotoCondensed-Regular";
font.pointSize: 12;
text: qsTr("TEST");
opacity: 0.6;
}
}
}
So ideally the end effect would look like this (edited in paint):
Then on 30 degree rotation like this:
The easiest way would be to have the Text as a child item of the item that is rotated. Then you could just position it at the bottom of it and it would rotate with its parent.
However, your canvas covers the whole window, so your only option (given the current code) is to draw the text when you draw the shape. You can draw text with text(), fillText() or strokeText().
I would suggest rewriting your code to use the first approach I mentioned. Give each shape its own canvas, and make the text a child of the Canvas item (or its parent if you don't want the drop shadow effect to be applied to the text). Rotating an item is much cheaper than rotating by repainting a Canvas.

Drawing segments on QML canvas

Consider the code below, which allows you to insert successive points represented as black circles onto the canvas. The aim of the code, is to
join each successive point by a red line segment.
However, the code gives me a canvas which looks like this
Only the first and second points have been joined by a red segment. None of the other points have been joined by segments. How do I fix that?
Here is the QML code
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 500
height: 500
function clear() {
var ctx = getContext("2d");
ctx.reset();
mycanvas.requestPaint();
}
Path {
id: myPath
startX: 0; startY: 100
PathLine { x: 400; y: 500 }
}
property var arrpoints : []
onPaint: {
var context = getContext("2d");
// Render all the points as small black-circles
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
for(var i=0; i < arrpoints.length; i++){
var point= arrpoints[i]
context.ellipse(point["x"], point["y"], 10, 10)
}
context.fill()
context.stroke()
// Join successive points with red segments
for (var j=1 ; j < arrpoints.length ; j++){
var start = arrpoints[j-1]
var end = arrpoints[j]
context.beginPath();
context.lineWidth = 2;
context.moveTo(start["x"], start["y"]);
context.strokeStyle = "red"
context.lineTo(end["x"], end["y"]);
}
context.stroke();
}
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
console.log( mycanvas.arrpoints )
}
}
}
Button {
text: "Clear Points"
anchors.top : mycanvas.bottom
onClicked: {
mycanvas.arrpoints.length = 0
mycanvas.clear()
console.log( mycanvas.arrpoints )
}
}
}//Window
According to the documentation:
object ellipse(real x, real y, real w, real h)
Creates an ellipse within the bounding rectangle defined by its
top-left corner at (x, y), width w and height h, and adds it to the
path as a closed subpath.
The ellipse is composed of a clockwise curve, starting and finishing
at zero degrees (the 3 o'clock position).
That is, an ellipse is drawn inscribed in a rectangle determined by the position (x, y) and the size w, h, so we must obtain the topLeft point using the mouse point and the radius.
For lines it is not necessary to make a path for each line, you just have to use a path, and then create the line and move to a next point as shown below:
import QtQuick 2.7
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window{
id: root
width: 640
height: 480
visible: true
Canvas {
id: mycanvas
width: 500
height: 500
function clear() {
var ctx = getContext("2d");
ctx.reset();
mycanvas.requestPaint();
}
Path {
id: myPath
startX: 0; startY: 100
PathLine { x: 400; y: 500 }
}
property real radius: 10
property var arrpoints : []
onPaint: {
var context = getContext("2d");
context.save()
if(arrpoints.length > 0){
for(var i=0; i < arrpoints.length; i++){
var point= arrpoints[i]
context.ellipse(point["x"]-radius, point["y"]-radius, 2*radius, 2*radius)
}
context.strokeStyle = Qt.rgba(0, 1, 1, 0)
context.fill()
context.stroke()
context.beginPath()
var start = arrpoints[0]
context.moveTo(start["x"], start["y"])
for(var j=1; j < arrpoints.length; j++){
var end= arrpoints[j]
context.lineTo(end["x"], end["y"])
context.moveTo(end["x"], end["y"])
}
context.closePath()
context.strokeStyle = "red"
context.lineWidth = 2;
context.stroke()
}
context.restore()
}
MouseArea {
id: mymouse
anchors.fill: parent
onClicked: {
mycanvas.arrpoints.push({"x": mouseX, "y": mouseY})
mycanvas.requestPaint()
}
}
}
Button {
text: "Clear Points"
anchors.top : mycanvas.bottom
onClicked: {
mycanvas.clear()
console.log( mycanvas.arrpoints )
}
}
}//Window

Resources