Issue with drawing an Qml Item with raw OpenGL calls - qt

I want to draw a single item in QtQuick scene using raw OpenGL calls. I have decided to take approach suggested in this question.
I have created a Qt Quick item deriving from QQuickFramebufferObject and exposed it to QML as Renderer: (code is based on Qt example: Scene Graph - Rendering FBOs)
class FboInSGRenderer : public QQuickFramebufferObject {
Q_OBJECT
public:
Renderer *createRenderer() const;
};
source file:
class LogoInFboRenderer : public QQuickFramebufferObject::Renderer {
public:
LogoInFboRenderer() { }
void render() {
int width = 1, height = 1;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0, 1.0, 0.0, 0.8);
glBegin(GL_QUADS);
glVertex2f(0, 0);
glVertex2f(width, 0);
glVertex2f(width, height);
glVertex2f(0, height);
glEnd();
glLineWidth(2.5);
glColor4f(0.0, 0.0, 0.0, 1.0);
glBegin(GL_LINES);
glVertex2f(0, 0);
glVertex2f(width, height);
glVertex2f(width, 0);
glVertex2f(0, height);
glEnd();
update();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
format.setSamples(4);
return new QOpenGLFramebufferObject(size, format);
}
};
QQuickFramebufferObject::Renderer *FboInSGRenderer::createRenderer() const {
return new LogoInFboRenderer();
}
In Qml I use it as follows:
import QtQuick 2.4
import SceneGraphRendering 1.0
Rectangle {
width: 400
height: 400
color: "purple"
Renderer {
id: renderer
anchors.fill: parent
}
}
I was expecting to see that rendered "X" will fill entire scene, but instead I get the result presented below:
Other experiments seem to confirm that drew shape has always it's size (width/height) divided by 2.
I also checked that size parameter in createFramebufferObject has correct value.
Looking into docs led me to property textureFollowsItemSize in QQuickFramebufferObject class but it is by default set to true.
Am I doing something wrong or should I consider this behavior as Qt bug?

The drawn rectangle is half the sizes you expect because the default coordinate range is [-1, 1], not [0, 1] as your code assumes. If you want to use [0, 1] scale, then you should appropriately set the projection matrix:
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);

As Qt documentation says: "Warning: It is crucial that OpenGL operations and interaction with the scene graph happens exclusively on the rendering thread, primarily during the updatePaintNode() call. The best rule of thumb is to only use classes with the "QSG" prefix inside the QQuickItem::updatePaintNode() function." I do it in this way:
*.h
class MyQuickItem : public QQuickItem
{
Q_OBJECT
public:
MyQuickItem();
~MyQuickItem();
protected:
QSGNode *updatePaintNode(QSGNode * oldNode, UpdatePaintNodeData * updatePaintNodeData);
QSGNode *addNode(QSGGeometry *geometry, const QColor &color);
};
*.cpp
MyQuickItem::MyQuickItem()
{
setFlag(QQuickItem::ItemHasContents,true);
}
MyQuickItem::~MyQuickItem()
{
}
QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData)
QSGTransformNode *root = static_cast<QSGTransformNode *>(oldNode);
if(!root) root = new QSGTransformNode;
QSGNode *node;
QSGGeometry *geometry;
QSGSimpleRectNode *rect = new QSGSimpleRectNode();
rect->setColor(Qt::green);
rect->setRect(boundingRect());
root->appendChildNode(rect);
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
geometry->setDrawingMode(GL_LINES);
geometry->setLineWidth(5.0);
geometry->vertexDataAsPoint2D()[0].set(x(), y());
geometry->vertexDataAsPoint2D()[1].set(width(), height());
node = addNode(geometry,Qt::blue);
root->appendChildNode(node);
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2);
geometry->setDrawingMode(GL_LINES);
geometry->setLineWidth(5.0);
geometry->vertexDataAsPoint2D()[0].set(width(), y());
geometry->vertexDataAsPoint2D()[1].set(x(), height());
node = addNode(geometry,Qt::blue);
root->appendChildNode(node);
return root;
}
QSGNode *MyQuickItem::addNode(QSGGeometry *geometry, const QColor &color)
{
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(color);
QSGGeometryNode *node = new QSGGeometryNode;
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
return node;
}
In main.cpp
qmlRegisterType<MyQuickItem>("MyObjects", 1, 0, "MyObject");
And usage:
import QtQuick 2.3
import QtQuick.Window 2.2
import MyObjects 1.0
Window {
visible: true
width: 360
height: 360
MyObject {
anchors.fill: parent
}
}

Related

drawing with Canvas in realtime (Qt/QML)

I hope i will be as clear as possible for my problem.
I'm currently devlopping an Application using a QML file as GUI.
The goal is simple : i become a large amount of data via QTcpSocket (raw data from a linear camera) and I would like to display the image. It's important to know that each time I receive a frame, it's a 1*2048 array of colors. I'd like to display it as fast as possible after receiving a data.
I've tried to set up a property string containing the color and 2 others properties containing the position (x;y) and then :
Canvas {
id: camera
objectName: "cameradrawer"
x: 1270
y: 30
width: 650
height: 500
renderStrategy: Canvas.Threaded
property string iColor
property int imageX
property int imageY
property var line
property var ctx
onPaint: {
ctx = getContext('2d');
ctx.clearRect(0, 0, width, height);
// draw here
ctx.fillStyle = iColor;
ctx.fillRect(imageY,imageX, 1, 1);
}
}
When i change the properties and then use ìnvokeMethod("requestPaint"), the application keeps crashing.
Am i doing something wrong or is the QML not used for high speed processes?
Thank you in advance for your help!
PS : i can post more code (C++ part) if necessary.
I had a bit of free time so I researched the issue as I found it quite interesting. So the faster way to implement that I guess is creating an image from the data and so create a texture from it that can be used in a QQuickItem-based item.
The custom item:
CustomItem.h
class CustomItem : public QQuickItem
{
Q_OBJECT
QML_ELEMENT
public:
CustomItem(QQuickItem *parent = nullptr);
~CustomItem();
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
void componentComplete() override;
protected:
void createImage();
void timerHandler();
private:
uint32_t m_buffer[BUFFER_SIZE] = {};
QSGTexture *m_texture = nullptr;
QSGTexture *m_newTexture = nullptr;
QTimer m_timer;
};
CustomItem.cpp
CustomItem::CustomItem(QQuickItem *parent):
QQuickItem(parent)
{
setFlag( QQuickItem::ItemHasContents, true);
QObject::connect(&m_timer, &QTimer::timeout, this, &CustomItem::timerHandler);
}
CustomItem::~CustomItem()
{
if(m_texture != nullptr)
{
delete m_texture;
m_texture = nullptr;
}
if(m_newTexture != nullptr)
{
delete m_newTexture;
m_newTexture = nullptr;
}
}
QSGNode *CustomItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node);
if (n == nullptr)
{
if(m_newTexture != nullptr)
{
n = new QSGSimpleTextureNode();
n->setRect(boundingRect());
}
}
if(n != nullptr)
{
if(m_newTexture != nullptr)
{
if(m_texture != nullptr)
{
delete m_texture;
m_texture = nullptr;
}
m_texture = m_newTexture;
m_newTexture = nullptr;
n->setTexture(m_texture);
}
}
return n;
}
void CustomItem::componentComplete()
{
createImage();
m_timer.start(1000);
QQuickItem::componentComplete();
}
void CustomItem::createImage()
{
if(m_newTexture == nullptr)
{
QRandomGenerator::global()->fillRange(m_buffer, BUFFER_SIZE);
QImage img(reinterpret_cast<uchar *>(m_buffer), IMAGE_WIDTH, IMAGE_HEIGHT, QImage::Format_ARGB32);
auto wnd = window();
if(wnd != nullptr)
{
m_newTexture = wnd->createTextureFromImage(img);
}
}
}
void CustomItem::timerHandler()
{
createImage();
update();
}
Some short clarification:
the class contains a big array of integers. I simulate the periodical data updating with timer so the array is updated once per second with random data.
as soon as data changed I create a texture. I use 2 pointers to avoid deleting the old texture while rendering and so I delete the old one inside updatePaintNode when it's safety. I use QSGSimpleTextureNode since that allows use textures but I guess you can use any other classes you want. The texture has alpha channel, you can use Format_RGB32 instead to avoid that. If the item bigger the the texture it will be scaled, you can play with that too.
The main.qml for testing can be like this:
import QtQuick
import QtQuick.Window
import CustomItems 1.0
Window {
id: window
visible: true
height: 400
width: 400
Rectangle {
width: 300
height: 300
color: "orange"
anchors.centerIn: parent
CustomItem {
width: 200
height: 200
anchors.centerIn: parent
}
}
}
To register the custom item in case of CMake the following lines should be added into CMakeFiles.txt:
set(CMAKE_AUTOMOC ON)
qt_add_qml_module(qml_test
URI CustomItems
VERSION 1.0
QML_FILES main.qml
SOURCES CustomItem.h CustomItem.cpp
The sources could be found here

Poor rendering quality in custom QQuickPaintedItem

I have a created a small drawing application in QML, I created a small subclass of QQuickPaintedItem. Then in QML, I used a MouseArea to feed the input to my class. From there I simply store the mouse positions in a vector and then paint the points as I receive onto a QImage using QPainter (I used a simple algorithm to draw a quadratic bezier curve using the last three points in my vector). Then I call QQuickPainted::update() and in my implementation of QQuickPaintedItem::paint() I draw the image. Now the program works ok, but the problem is that the rendering of the painting is quite poor (I am already using QPainter::AntiAliasing). Below there is a picture. As you can see the curves are not very sharp and I can see the "pixels" on oblique lines (when I try the same thing with OneNote everything is smooth and nice).
Here is the a full example from my github repository if you want to test it out (the code is below as well). Is there something I can do about this?
.
#ifndef DRAWINGCANVAS_H
#define DRAWINGCANVAS_H
#include <QObject>
#include <QQuickPaintedItem>
#include <QPixmap>
#include <QPainter>
struct Outline{
QPolygonF points;
void addPoint(QPointF p){
points.append(p);
}
void clear(){
points.clear();
}
};
// a custom QQuickPainted used as a canvas in QML
class DrawingCanvas : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool drawing READ drawing WRITE setDrawing NOTIFY drawingChanged)
Q_PROPERTY(int penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
Q_PROPERTY(QString penColor READ penColor WRITE setPenColor NOTIFY penColorChanged)
public:
explicit DrawingCanvas(QQuickItem *parent = nullptr);
bool drawing() const;
Q_INVOKABLE void initiateBuffer();
Q_INVOKABLE void penPressed(QPointF pos);
Q_INVOKABLE void penMoved(QPointF pos);
Q_INVOKABLE void penReleased();
int penWidth() const;
void paint(QPainter *painter) override;
QString penColor() const;
public slots:
void setDrawing(bool drawing);
void setPenWidth(int penWidth);
void setPenColor(QString penColor);
signals:
void drawingChanged(bool drawing);
void penWidthChanged(int penWidth);
void penColorChanged(QString penColor);
private:
void drawOnBuffer(QPointF pos);
bool m_drawing;
QPixmap m_buffer;
int m_penWidth;
QString m_penColor;
QPointF m_lastPoint;
Outline m_currentOutline;
QRect m_updateRect;
QVector<Outline> m_outlines;
bool m_outlineEraser;
};
#endif // DRAWINGCANVAS_H
#include "drawingcanvas.h"
#include <QPainter>
DrawingCanvas::DrawingCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
m_penWidth = 4;
}
bool DrawingCanvas::drawing() const
{
return m_drawing;
}
void DrawingCanvas::penPressed(QPointF pos)
{
setDrawing(true);
m_currentOutline.addPoint(pos);
m_lastPoint = pos;
}
void DrawingCanvas::penMoved(QPointF pos)
{
if(drawing()){
m_currentOutline.addPoint(pos);
// draw the points on the buffer
drawOnBuffer(pos);
}
m_lastPoint = pos;
}
void DrawingCanvas::penReleased()
{
setDrawing(false);
m_outlines.append(m_currentOutline);
m_currentOutline.clear();
m_lastPoint = QPointF();
}
// draws the actual item
void DrawingCanvas::paint(QPainter *painter)
{
painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
QPen pen;
pen.setWidth(penWidth());
pen.setColor(penColor());
painter->setPen(pen);
painter->drawPixmap(m_updateRect, m_buffer, m_updateRect);
m_updateRect = QRect();
}
// draws on the image
void DrawingCanvas::drawOnBuffer(QPointF pos)
{
QPainter bufferPainter;
if(bufferPainter.begin(&m_buffer)){
QPen pen;
pen.setWidth(penWidth());
pen.setColor(penColor());
bufferPainter.setPen(pen);
bufferPainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
int pointsLength = m_currentOutline.points.length();
QPainterPath path;
// this will help smoothing the curves
if(pointsLength > 2){
auto previousPoint = m_currentOutline.points.at(pointsLength - 3);
auto mid1 = (m_lastPoint + previousPoint)/2;
auto mid2 = (pos + m_lastPoint)/2;
path.moveTo(mid1);
path.quadTo(m_lastPoint, mid2);
bufferPainter.drawPath(path);
}
// update the canvas
int rad = (penWidth() / 2) + 2;
auto dirtyRect = path.boundingRect().toRect().normalized()
.adjusted(-rad, -rad, +rad, +rad);
// change the canvas dirty region
if(m_updateRect.isNull()){
m_updateRect = dirtyRect;
}
else{
m_updateRect = m_updateRect.united(dirtyRect);
}
update(dirtyRect);
m_lastPoint = pos;
}
}
QString DrawingCanvas::penColor() const
{
return m_penColor;
}
int DrawingCanvas::penWidth() const
{
return m_penWidth;
}
void DrawingCanvas::setDrawing(bool drawing)
{
if (m_drawing == drawing)
return;
m_drawing = drawing;
emit drawingChanged(m_drawing);
}
void DrawingCanvas::setPenWidth(int penWidth)
{
if (m_penWidth == penWidth)
return;
m_penWidth = penWidth;
emit penWidthChanged(m_penWidth);
}
void DrawingCanvas::setPenColor(QString penColor)
{
if (m_penColor == penColor)
return;
m_penColor = penColor;
emit penColorChanged(m_penColor);
}
// initiates the QImage buffer
void DrawingCanvas::initiateBuffer()
{
qDebug() << this << "Initiating buffer" << width() << height();
m_buffer = QPixmap(width(), height());
}
In QML:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import QtQuick.Dialogs 1.3
import Drawing 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable {
id: scrollView
anchors.fill: parent
contentHeight: drawingCanvas.height
DrawingCanvas {
id: drawingCanvas
width: parent.width
height: 2000
penColor: "red"
onWidthChanged: drawingCanvas.initiateBuffer()
}
}
MouseArea {
anchors.fill: parent
anchors.rightMargin: 20
onPressed: drawingCanvas.penPressed(
Qt.point(mouseX, mouseY + scrollView.contentY))
onPositionChanged: drawingCanvas.penMoved(
Qt.point(mouseX, mouseY + scrollView.contentY))
onReleased: drawingCanvas.penReleased()
}
}
Your rendering issue doesn't seem to be due to the antialising qt option but more to the smoothing of your strokes. I recommend you to modify your custom bezier smoothing techniques or to use a dedicated lib for that [0] .
Secondly, you should create a dedicated QPen in your draw methods and "play" with the QPen and QBrush options [1] if you want the "OneNote drawing feeling". The major difference I saw between the two screenshots was the brush scale dynamics (at the beginning and the end of the strokes).
0: For example, https://github.com/oysteinmyrmo/bezier
1: https://doc.qt.io/qt-5/qpen.html

Mouse event handling in rotated QQuickItem

I have a simple QQuickPaintedItem which draws a circular sector at a given position, with a given sector size and azimuth. Here is the header:
#pragma once
#include <QtQuick/QQuickPaintedItem>
#include <QColor>
#include <QPainter>
class MarkerItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(int azimuth READ azimuth WRITE setAzimuth)
Q_PROPERTY(int sectorSize READ sectorSize WRITE setSectorSize)
Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorChanged)
Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
public:
MarkerItem(QQuickItem *parent = nullptr);
QColor color() const;
void setColor(const QColor &color);
int azimuth() const;
void setAzimuth(int angle);
int sectorSize() const;
void setSectorSize(int span);
QPointF anchorPoint() const;
void setAnchorPoint(const QPointF &value);
bool selected();
void setSelected(const bool bVal);
public slots:
void mousePressEvent(QMouseEvent *event) override;
protected:
void paint(QPainter *painter) override;
private:
QColor mcColor;
int miAzimuth;
int miSectorSize;
QPointF mcAnchorPoint;
bool mbSelected;
const int miSectorRadius = 40;
signals:
void anchorChanged(QPointF);
void selectedChanged(bool);
};
and the implementation:
#include "markeritem.h"
#include <QSGGeometryNode>
#include <QtMath>
MarkerItem::MarkerItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
setAcceptedMouseButtons(Qt::LeftButton);
qDebug() << "MarkerItem";
setWidth(qTan(qDegreesToRadians(30.0f))*40);
setHeight(40);
mbSelected = false;
}
void MarkerItem::paint(QPainter *painter)
{
qDebug() << " > MarkerItem paint, azimuth " << miAzimuth;
QPen pen;
QBrush brush(mcColor);
if (mbSelected)
{
pen.setColor(Qt::magenta);
pen.setWidth(2);
brush.setColor(mcColor.lighter(150));
}
else
{
pen.setColor(Qt::black);
pen.setWidth(1);
}
painter->setPen(pen);
painter->setBrush(brush);
painter->setRenderHints(QPainter::Antialiasing, true);
const QRectF rect = boundingRect();
setAnchorPoint(QPointF(rect.left()+rect.width()/2.0,rect.bottom()));
// we define a helper rectangle what we use to draw a pie,
// as the drawPie() method expect a rect, and the pie will start from the
// center of that rect
QRectF cPieRect(anchorPoint().x()-rect.height(), anchorPoint().y()-rect.height(),
rect.height()*2,rect.height()*2);
painter->drawPie(cPieRect,(90-miSectorSize/2)*16, miSectorSize * 16);
// drawing the bounding rectangle in red for visual debugging
QPen pen2(Qt::red,1);
painter->setPen(pen2);
painter->setBrush(QBrush(Qt::transparent));
painter->drawRect(rect);
setTransformOriginPoint(anchorPoint());
setRotation(miAzimuth);
}
// setters/getters
QPointF MarkerItem::anchorPoint() const
{
return mcAnchorPoint;
}
void MarkerItem::setAnchorPoint(const QPointF &value)
{
mcAnchorPoint = value;
emit anchorChanged(value);
}
void
MarkerItem::mousePressEvent(QMouseEvent *event)
{
qDebug() << "MarkerItem MousePressEvent" << event;
setSelected(!mbSelected);
}
bool
MarkerItem::selected()
{
return mbSelected;
}
void
MarkerItem::setSelected(const bool bVal)
{
if (bVal == mbSelected)
{
return;
}
mbSelected = bVal;
emit selectedChanged(mbSelected);
update();
}
QColor MarkerItem::color() const
{
return mcColor;
}
void MarkerItem::setColor(const QColor &acColor)
{
mcColor = acColor;
}
int MarkerItem::azimuth() const
{
return miAzimuth;
}
void MarkerItem::setAzimuth(int angle)
{
miAzimuth = angle;
}
int MarkerItem::sectorSize() const
{
return miSectorSize;
}
void MarkerItem::setSectorSize(int angle)
{
miSectorSize = angle;
}
And using it like:
import Marker 1.0 // this is the Markeritem class
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Item
{
id: root
visible: true
width: 1800
height: 900
MarkerItem
{
id: m0
x: 900
y:450
color: "green"
azimuth: 0
sectorSize: 30
}
MarkerItem
{
id: m1
x: 900
y:450
color: "green"
azimuth: 120
sectorSize: 30
}
}
The QQuickPaintedItem defines a bounding rectangle which fits to the circular section to draw, and using QPainter's drawPie() method to draw the needed shape, after which by calling setRotate(), it rotates the item into the requested azimuth. I also need to handle the mouse click event to select/unselect an item. What i experience is that when the azimuth of an item is 0 (so it's not rotated), the mouse events of the bounding rectangle are received by my handler, but when the item is rotated, my item doesn't receive the mouse event from the area of the drawn item, but receives some of the mouse events outside from the bounding rectangle, from an area which seems to be at the point reflection of the original area, where the center is the anchorpoint (the tip if the circular sector, which is also the center of the rotation), but it's hard to define the area from which the events are delivered to my handler.
My assumption would be that with the "setRotation()" call, also the mouse area is rotating. Is there something I've overlooked? Is there any method to handle mouse events from a rotated item?
The item is supposed to use in a Map item via a MapQuickItem, but the issue is also reproducible without a Map item, so the map related parts are mostly omitted here.
setRotation() rotates the item so it should not be in paint since that method is called every time the item is painted, for example before the mouse event, in conclusion setRotation() should only be called in setAzimuth()
void MarkerItem::paint(QPainter *painter)
{
qDebug() << " > MarkerItem paint, azimuth " << miAzimuth;
QPen pen;
QBrush brush(mcColor);
if (mbSelected)
{
pen.setColor(Qt::magenta);
pen.setWidth(2);
brush.setColor(mcColor.lighter(150));
}
else
{
pen.setColor(Qt::black);
pen.setWidth(1);
}
painter->setPen(pen);
painter->setBrush(brush);
painter->setRenderHints(QPainter::Antialiasing, true);
const QRectF rect = boundingRect();
setAnchorPoint(QPointF(rect.center().x(),rect.bottom()));
// we define a helper rectangle what we use to draw a pie,
// as the drawPie() method expect a rect, and the pie will start from the
// center of that rect
QRectF cPieRect(anchorPoint().x()-rect.height(), anchorPoint().y()-rect.height(),
rect.height()*2,rect.height()*2);
painter->drawPie(cPieRect,(90-miSectorSize/2)*16, miSectorSize * 16);
// drawing the bounding rectangle in red for visual debugging
QPen pen2(Qt::red,1);
painter->setPen(pen2);
painter->setBrush(QBrush(Qt::transparent));
painter->drawRect(rect);
}
...
void MarkerItem::setAzimuth(int angle)
{
miAzimuth = angle;
setTransformOriginPoint(anchorPoint());
setRotation(miAzimuth);
}
OK, so turned out that the problem was with incorrectly setting the transform origin. I set the anchor point to the bottom center of the bounding rect as:
setTransformOriginPoint(anchorPoint());
where the anchor point was set earlier to
setAnchorPoint(QPointF(rect.center().x(),rect.bottom()));
If i set the transform origin explicitly to the bottom center of the item as:
setTransformOrigin(QQuickItem::Bottom);
It works as it should.

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

How to integrate a custom GraphicsItem into a QML scene?

Assume you have created the following custom QGraphicsRectItem in C++:
class MyCustomItem : public QGraphicsRectItem
{
public:
MyCustomItem(MyCustomItem* a_Parent = 0);
virtual ~MyCustomItem();
// specific methods
private:
// specific data
};
Assume also that you have defined in a QML script an ApplicationWindow:
// main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.0
ApplicationWindow {
id: myWindow
title: qsTr("My Window")
width: 640
height: 480
visible: true
}
The simple task I would like to do is to display an instance of MyCustomItem in that ApplicationWindow. I wanted to do the following:
// part of main.cpp
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
MyCustomItem* myItem;
engine.rootContext()->setContextProperty("MyCustomItem", myItem);
return app.exec();
}
But of course this doesn't work, because MyCustomItem is neither a QObject nor a QVariant. I don't want my item to be anything else than a QGraphicsRectItem. Isn't that possible to display that graphics item? That should be simple as hell, shouldn't it? Is there a way with QDeclarativeItem or something? I can't find how to solve this problem, that's very frustrating. Would I implement my application with "normal" Qt, the problem would already be solved, because in this case you have a scene, and the scene has a member method addItem() and I don't need to do complicated stuff to add my custom graphics item to my scene. Do I have to wrap this item in a QDeclarativeItem or a QObject in order to get the thing done? That would be so awful, in my opinion. Aren't there better options?
EDIT
Can that be that QGraphicsRectItem is not the right class to inherit from and that something like QQuickPaintedItem (as suggested in the comments) would be more appropriate?
I can't speak for Qt 4, but in Qt 5, you have several options for custom drawing:
QQuickPaintedItem
A QPainter-based QQuickItem. This sounds the closest to what you want. A snippet from the documentation of one of the examples:
void TextBalloon::paint(QPainter *painter)
{
QBrush brush(QColor("#007430"));
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setRenderHint(QPainter::Antialiasing);
painter->drawRoundedRect(0, 0, boundingRect().width(), boundingRect().height() - 10, 10, 10);
if (rightAligned)
{
const QPointF points[3] = {
QPointF(boundingRect().width() - 10.0, boundingRect().height() - 10.0),
QPointF(boundingRect().width() - 20.0, boundingRect().height()),
QPointF(boundingRect().width() - 30.0, boundingRect().height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
else
{
const QPointF points[3] = {
QPointF(10.0, boundingRect().height() - 10.0),
QPointF(20.0, boundingRect().height()),
QPointF(30.0, boundingRect().height() - 10.0),
};
painter->drawConvexPolygon(points, 3);
}
}
Canvas
JavaScript-based drawing QML type with an HTML5-like API. A snippet from one of the examples:
Canvas {
id: canvas
width: 320
height: 250
antialiasing: true
property color strokeStyle: Qt.darker(fillStyle, 1.2)
property color fillStyle: "#6400aa"
property int lineWidth: 2
property int nSize: nCtrl.value
property real radius: rCtrl.value
property bool fill: true
property bool stroke: false
property real px: width/2
property real py: height/2 + 10
property real alpha: 1.0
onRadiusChanged: requestPaint();
onLineWidthChanged: requestPaint();
onNSizeChanged: requestPaint();
onFillChanged: requestPaint();
onStrokeChanged: requestPaint();
onPaint: squcirle();
function squcirle() {
var ctx = canvas.getContext("2d");
var N = canvas.nSize;
var R = canvas.radius;
N=Math.abs(N);
var M=N;
if (N>100) M=100;
if (N<0.00000000001) M=0.00000000001;
ctx.save();
ctx.globalAlpha =canvas.alpha;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = canvas.strokeStyle;
ctx.fillStyle = canvas.fillStyle;
ctx.lineWidth = canvas.lineWidth;
ctx.beginPath();
var i = 0, x, y;
for (i=0; i<(2*R+1); i++){
x = Math.round(i-R) + canvas.px;
y = Math.round(Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(i-R),M)),1/M)) + canvas.py;
if (i == 0)
ctx.moveTo(x, y);
else
ctx.lineTo(x, y);
}
for (i=(2*R); i<(4*R+1); i++){
x =Math.round(3*R-i)+canvas.px;
y = Math.round(-Math.pow(Math.abs(Math.pow(R,M)-Math.pow(Math.abs(3*R-i),M)),1/M)) + canvas.py;
ctx.lineTo(x, y);
}
ctx.closePath();
if (canvas.stroke) {
ctx.stroke();
}
if (canvas.fill) {
ctx.fill();
}
ctx.restore();
}
}
QSGGeometryNode
As mentioned in this answer, you could take advantage of the Qt Quick Scene Graph. A snippet from one of the examples:
QSGNode *BezierCurve::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
QSGGeometryNode *node = 0;
QSGGeometry *geometry = 0;
if (!oldNode) {
node = new QSGGeometryNode;
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), m_segmentCount);
geometry->setLineWidth(2);
geometry->setDrawingMode(GL_LINE_STRIP);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry);
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(QColor(255, 0, 0));
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial);
} else {
node = static_cast<QSGGeometryNode *>(oldNode);
geometry = node->geometry();
geometry->allocate(m_segmentCount);
}
QRectF bounds = boundingRect();
QSGGeometry::Point2D *vertices = geometry->vertexDataAsPoint2D();
for (int i = 0; i < m_segmentCount; ++i) {
qreal t = i / qreal(m_segmentCount - 1);
qreal invt = 1 - t;
QPointF pos = invt * invt * invt * m_p1
+ 3 * invt * invt * t * m_p2
+ 3 * invt * t * t * m_p3
+ t * t * t * m_p4;
float x = bounds.x() + pos.x() * bounds.width();
float y = bounds.y() + pos.y() * bounds.height();
vertices[i].set(x, y);
}
node->markDirty(QSGNode::DirtyGeometry);
return node;
}
QQuickWidget
If you really want to use QGraphicsItem subclasses, you could go the opposite direction, and have a widget-based app that contains certain "Qt Quick Widgets", though this is not optimal (see Qt Weekly #16: QQuickWidget for more information).

Resources