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
Related
I'm trying to create a very simple rich text editor component in QML. The requirements are that the user must be able to quickly format individual words inside a textbox separately. So for example, the user could press "Ctrl+B" in the middle of typing and from that point onward the text becomes bold, when the user press "Ctrl+B" again the subsequently typed text has a normal font weight:
So looking around I found that there is an official example provided by the Qt company to do this. So I extrapolated this to a very basic app:
// DocumentHandler.cpp
#pragma once
#include <QObject>
#include <QQuickTextDocument>
#include <QTextCharFormat>
class DocumentHandler : public QObject {
Q_OBJECT
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged)
Q_PROPERTY(bool modified READ modified WRITE setModified NOTIFY modifiedChanged)
Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged)
public:
QQuickTextDocument *document() const;
void setDocument(QQuickTextDocument *newDocument);
int cursorPosition() const;
void setCursorPosition(int newCursorPosition);
int selectionStart() const;
void setSelectionStart(int newSelectionStart);
int selectionEnd() const;
void setSelectionEnd(int newSelectionEnd);
bool modified() const;
void setModified(bool newModified);
bool bold() const;
void setBold(bool newBold);
signals:
void documentChanged();
void cursorPositionChanged();
void selectionStartChanged();
void selectionEndChanged();
void modifiedChanged();
void boldChanged();
private:
void mergeFormatOnWordOrSelection(const QTextCharFormat & format);
void reset();
QTextCursor textCursor() const;
QTextDocument* textDocument() const;
QQuickTextDocument * m_document = nullptr;
int m_cursorPosition = -1;
int m_selectionStart = 0;
int m_selectionEnd = 0;
};
// DocumentHandler.cpp
#include "documenthandler.h"
#include <QTextCursor>
QQuickTextDocument *DocumentHandler::document() const
{
return m_document;
}
void DocumentHandler::setDocument(QQuickTextDocument *newDocument)
{
if (m_document == newDocument)
return;
if(m_document)
m_document->textDocument()->disconnect(this);
m_document = newDocument;
if(m_document){
QObject::connect(m_document->textDocument(), &QTextDocument::modificationChanged,
this, &DocumentHandler::modifiedChanged);
}
emit documentChanged();
}
int DocumentHandler::cursorPosition() const
{
return m_cursorPosition;
}
void DocumentHandler::setCursorPosition(int newCursorPosition)
{
if (m_cursorPosition == newCursorPosition)
return;
m_cursorPosition = newCursorPosition;
reset();
emit cursorPositionChanged();
}
int DocumentHandler::selectionStart() const
{
return m_selectionStart;
}
void DocumentHandler::setSelectionStart(int newSelectionStart)
{
if (m_selectionStart == newSelectionStart)
return;
m_selectionStart = newSelectionStart;
emit selectionStartChanged();
}
int DocumentHandler::selectionEnd() const
{
return m_selectionEnd;
}
void DocumentHandler::setSelectionEnd(int newSelectionEnd)
{
if (m_selectionEnd == newSelectionEnd)
return;
m_selectionEnd = newSelectionEnd;
emit selectionEndChanged();
}
bool DocumentHandler::modified() const
{
return m_document && m_document->textDocument()->isModified();
}
void DocumentHandler::setModified(bool newModified)
{
if (m_document)
m_document->textDocument()->setModified(newModified);
}
void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = textCursor();
if (!cursor.hasSelection())
cursor.select(QTextCursor::WordUnderCursor);
cursor.mergeCharFormat(format);
}
void DocumentHandler::reset()
{
emit boldChanged();
}
QTextCursor DocumentHandler::textCursor() const
{
QTextDocument* doc = textDocument();
if(!doc)
return QTextCursor();
QTextCursor cursor = QTextCursor(doc);
if(m_selectionStart != m_selectionEnd){
cursor.setPosition(m_selectionStart);
cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor);
}
else{
cursor.setPosition(m_cursorPosition);
}
return cursor;
}
QTextDocument *DocumentHandler::textDocument() const
{
return m_document ? m_document->textDocument() : nullptr;
}
bool DocumentHandler::bold() const
{
auto cursor = textCursor();
if(cursor.isNull())
return false;
return cursor.charFormat().fontWeight() == QFont::Bold;
}
void DocumentHandler::setBold(bool newBold)
{
QTextCharFormat format;
format.setFontWeight(newBold ? QFont::Bold : QFont::Normal);
mergeFormatOnWordOrSelection(format);
emit boldChanged();
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import DocumentHandler 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
color: "lightpink"
header: ToolBar {
id: appToolbar
ToolButton {
text: "Bold"
checkable: true
checked: documentHandler.bold
onClicked: {
documentHandler.bold = !documentHandler.bold
}
}
}
DocumentHandler {
id: documentHandler
document: textInput.textDocument
cursorPosition: textInput.cursorPosition
selectionStart: textInput.selectionStart
selectionEnd: textInput.selectionEnd
}
TextArea {
id: textInput
anchors.fill: parent
font.pixelSize: 30
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
focus: true
selectByMouse: true
persistentSelection: true
textFormat: Qt.RichText
}
}
If you try to run this app, type something like "hello" and the press the "Bold" button the app correctly makes the "hello" bold. However, there is a problem I'm struggling to get past:
if you click on the button when the textarea is empty, then typing doesn't give you bold text. In fact, it seems that it is not possible to change the font weight of the textarea prior to having some text in it. Also, if you type a word, then press space and then click the button: nothing happens.
Anyone got any clue on what's the root cause of this behaviour?
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
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.
I want to draw straight lines on qml , but every time I draw a line, the previous disappears, I wonder if it has any thing to do with the update method, any ways to solve this problem.
main.qml
MouseArea{
id:fidpoint
anchors.fill: parent
onPressed: {
switch(addstate.currentText){
case 'Track':
map.setTstart(mouseX,mouseY);
draw_line.setColor("black");
draw_line.setStart(Qt.point(mouseX,mouseY));
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
onReleased: {
switch(addstate.currentText){
case 'Track':
map.addTrack(mouseX,mouseY);
draw_line.setColor("black");
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
onPositionChanged: {
switch(addstate.currentText){
case 'Track':
draw_line.setColor("black");
draw_line.setEnd(Qt.point(mouseX,mouseY));
draw_line.update();
break;
}
}
}
draw_line is the Object's id that I registered to qml from main.cpp
paint.cpp
void Paint::paint(QPainter *painter)
{
QPen pen(m_color, 2);
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing,true);
painter->drawLine(startNode,endNode);
}
paint is the class that inherits from QQuickPaintedItem
A possible option would be to create an image and save the changes there, but this would solve your problem in the short term. A better option is that draw_line draw only one line, then add it as a child of the item you want the canvas to be.
lineitem.h
#ifndef LINEITEM_H
#define LINEITEM_H
#include <QQuickPaintedItem>
class LineItem: public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QPoint startPos READ startPos WRITE setStartPos NOTIFY startPosChanged)
Q_PROPERTY(QPoint endPos READ endPos WRITE setEndPos NOTIFY endPosChanged)
Q_PROPERTY(QColor lineColor READ lineColor WRITE setLineColor NOTIFY lineColorChanged)
public:
using QQuickPaintedItem::QQuickPaintedItem;
void paint(QPainter *painter);
QPoint startPos() const;
void setStartPos(const QPoint &startPos);
QPoint endPos() const;
void setEndPos(const QPoint &endPos);
QColor lineColor() const;
void setLineColor(const QColor &lineColor);
signals:
void startPosChanged();
void endPosChanged();
void lineColorChanged();
private:
QPoint m_startPos;
QPoint m_endPos;
QColor m_lineColor;
};
#endif // LINEITEM_H
lineitem.cpp
#include "lineitem.h"
#include <QPainter>
void LineItem::paint(QPainter *painter)
{
painter->setRenderHint(QPainter::Antialiasing, true);
QPen pen(m_lineColor, 2);
painter->setPen(pen);
painter->drawLine(m_startPos, m_endPos);
}
QPoint LineItem::startPos() const
{
return m_startPos;
}
void LineItem::setStartPos(const QPoint &startPos)
{
if(m_startPos == startPos)
return;
m_startPos = startPos;
emit startPosChanged();
update();
}
QPoint LineItem::endPos() const
{
return m_endPos;
}
void LineItem::setEndPos(const QPoint &endPos)
{
if(m_endPos == endPos)
return;
m_endPos = endPos;
emit endPosChanged();
update();
}
QColor LineItem::lineColor() const
{
return m_lineColor;
}
void LineItem::setLineColor(const QColor &lineColor)
{
if(m_lineColor == lineColor)
return;
m_lineColor = lineColor;
emit lineColorChanged();
update();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import com.eyllanesc.org 1.0
Window {
id: win
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property LineItem currentItem: null
Rectangle{
id: canvas
anchors.fill: parent
MouseArea{
anchors.fill: parent
onPressed: {
currentItem = create_lineitem(canvas)
currentItem.lineColor = "green"
currentItem.anchors.fill = canvas
currentItem.startPos = Qt.point(mouseX,mouseY)
currentItem.endPos = Qt.point(mouseX,mouseY)
}
onReleased: currentItem.endPos = Qt.point(mouseX,mouseY)
onPositionChanged: currentItem.endPos = Qt.point(mouseX,mouseY)
}
}
function create_lineitem(parentItem, color) {
return Qt.createQmlObject('import com.eyllanesc.org 1.0; LineItem{}',
parentItem);
}
}
main.cpp
#include "lineitem.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
qmlRegisterType<LineItem>("com.eyllanesc.org", 1, 0, "LineItem");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
The complete example can be found in the following link.
What I'm doing, in short:
Deriving a MyItem class from QQuickFramebufferObject
In MyItem I have a QQuickItem* sourceItem property from which I fetch a texture and draw it in a triangle
From QML I supply an Image called sourceItem to MyItem. This Image has cache: false
When the Image finishes loading, I wait one frame and then call update on MyItem
I have a 6x6 grid of MyItem instances
The problem is that some of the instances of MyItem draw nothing:
Any ideas?
My code:
main.cpp:
#include <QQmlApplicationEngine>
#include <QGuiApplication>
#include <QQuickFramebufferObject>
#include <QOpenGLFramebufferObject>
#include <QSGTextureProvider>
#include <QSGTexture>
#include <QQuickWindow>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include "propertyhelper.h" // this file is from http://syncor.blogspot.bg/2014/11/qt-auto-property.html
class MyItem : public QQuickFramebufferObject {
Q_OBJECT
AUTO_PROPERTY(QQuickItem*, sourceItem)
public:
Renderer *createRenderer() const;
};
class MyItemRenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions {
public:
MyItemRenderer() {
initializeOpenGLFunctions();
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,
"in highp vec2 aPos;\
out highp vec2 vTexCoord;\
\
void main() {\
gl_Position = vec4(aPos, 0.0, 1.0);\
vTexCoord = aPos * .5 + .5;\
}"
);
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,
"in highp vec2 vTexCoord;\
out vec4 outputColor;\
uniform sampler2D uTex;\
\
void main() {\
outputColor = texture(uTex, vTexCoord);\
}"
);
m_program.link();
m_program.setUniformValue("uTex", 0);
createGeometry();
}
void synchronize(QQuickFramebufferObject* qqfbo){
auto item = (MyItem*)qqfbo;
m_window = item->window();
m_tex = item->sourceItem()->textureProvider()->texture();
}
QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) {
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
return new QOpenGLFramebufferObject(size, format);
}
void paintGeometry() {
m_program.enableAttributeArray("aPos");
m_program.setAttributeArray("aPos", m_vertices.constData());
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
m_program.disableAttributeArray("aPos");
}
void createGeometry() {
m_vertices << QVector2D(-1, -1);
m_vertices << QVector2D(1, -1);
m_vertices << QVector2D(-1, 1);
}
void render() {
glDisable(GL_DEPTH_TEST);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
m_program.bind();
glActiveTexture(GL_TEXTURE0);
m_tex->bind();
paintGeometry();
m_window->resetOpenGLState();
}
private:
QQuickWindow* m_window;
QVector<QVector2D> m_vertices;
QSGTexture* m_tex;
QOpenGLShaderProgram m_program;
};
QQuickFramebufferObject::Renderer *MyItem::createRenderer() const {
return new MyItemRenderer();
}
int main(int argc, char** argv) {
qmlRegisterType<MyItem>("MyItem", 1, 0, "MyItem");
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
id: window
width: 600
height: 600
Flow {
anchors.fill: parent
Repeater {
model: 36
delegate: MyItemWrapper {
width: 100
height: 100
}
}
}
}
MyItemWrapper.qml:
import QtQuick 2.5
import MyItem 1.0
Item {
Image {
x: -100000 // hide the sourceItem
id: sourceItem
layer.enabled: true
source: "https://images-na.ssl-images-amazon.com/images/M/MV5BMTg2MTMyMzU0M15BMl5BanBnXkFtZTgwOTU3ODk4NTE#._V1_SX300.jpg"
cache: false
function updateCppItemOnce() {
window.afterRendering.disconnect(updateCppItemOnce);
cppItem.update();
}
onStatusChanged: {
if (status == Image.Ready) {
window.afterRendering.connect(updateCppItemOnce);
}
}
}
MyItem {
sourceItem: sourceItem
anchors.fill: parent
id: cppItem
}
}
Notes:
I tried enabling OpenGL logging but got no messages
I've tried the testcase on 3 other PCs, all of them much weaker/slower than this one. The bug doesn't occur on them. So I think the bug happens only on fast PCs - it may be a timing problem.
I now realized that instead of the clunky way I was calling update(), I could just connect to the item->sourceItem()->textureProvider()->textureChanged() signal. Did that and now it works like a charm.
I'd still like to know why my original way fails, though :)
Gunnar Sletta provided the solution as a comment in my bugreport.
I suspect the problem is your use of the 'afterRendering' signal. When running on a threaded render loop this signal will fire when rendering is done and the slot will be called almost right away on the main thread. Depending on where the threaded render loop is when the image completes loading that means you'll have the slot invoked potentially right away. When the scene graph reaches the sync phase, the order in which nodes are processed is arbitrary, so some nodes may be processed before the image and some after. Those processed before the image gets a texture gets null and those processed after gets a valid texture.
It would be better to connect to afterSynchronization signal as this would actually fire first after the synchronization of the Image was guaranteed to have completed.
Indeed, using afterSynchronizing fixed my problem.