Qt: QGraphicsView content not erased before drawForeground() is called - c++

I'm facing an annoying issue using Qt QGraphicsView framework.
I write a basic image viewer to display large B&W tiff images.
Images are displayed using a QGraphicsPixmapItem and added to the scene.
I also need to draw a colored rectangle around the viewport.
void ImageView::drawForeground( QPainter * painter, const QRectF & rect )
{
if( m_image ) {
qDebug() << "drawForeground(), rect = " << rect;
QBrush br(m_image->isValidated() ? Qt::green : Qt::red);
qreal w = 40. / m_scaleFactor;
QPen pen(br, w);
painter->save();
painter->setOpacity(0.5);
painter->setPen(pen);
painter->drawRect(rect);
painter->restore();
}
}
It works fine, at first glance.
But when I scroll the viewport content, things are getting ugly.
drawForeground() method is effectively called but it seems the content of the viewport is not erased before. So the drawing becomes horrible on screen.
Is there a better way to achieve it?
EDIT
As leems mentioned it, Qt internals don't alow me to achieve it with drawForeground().
I found a workaround by using a QGraphicsRectItem which gets resized in the viewportEvent().
Loos like:
bool ImageView::viewportEvent(QEvent *ev)
{
QRect rect = viewport()->rect();
if( m_image ) {
QPolygonF r = mapToScene(rect);
QBrush br2(m_image->isValidated() ? Qt::green : Qt::red);
qreal w2 = 40. / m_scaleFactor;
QPen pen2(br2, w2);
m_rect->setPen(pen2);
m_rect->setOpacity(0.5);
m_rect->setRect(r.boundingRect());
}
return QGraphicsView::viewportEvent(ev);
}
The code is not finalized but will basically looks like that.
It works fine, though it blinks a little bit when scrolling too fast...

Related

How to draw on zoomed image in Qt

I'm working on a project where I have to do an image editor similar to Paint. I would like to open up an image, zoom in/zoom out if necessary, and then draw on it.
For that, I used Image Viewer and Scribble examples, the only thing different that I added was a QLabel subclass that is supposed to draw lines and other forms with the mouse press/mouse release events. The problem is with the overriden paintEvent.
void imLabel::paintEvent(QPaintEvent *event){
if(tipo != ""){ //draw mode
QPainter painter(this);
QRect dirtyRect = event->rect();
painter.drawImage(dirtyRect,image,dirtyRect);
}
else QLabel::paintEvent(event);
}
I am able to zoom in normally in the beginning, but when I start drawing the image comes back to its original size, while the rest of the label that is not occupied by the image is completely white, because it kept the zoomed in size.
Is there a way that I can keep the image zoomed in while I draw, just like in Paint?
UPDATE
Here's the code of the draw function, maybe it'll help
void imLabel::draw(const QPoint &endPoint){
QPainter painter(&image);
painter.setPen(QPen(myPenColor, myPenWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
if(tipo == "maoLivre" || tipo == "reta") //draw line or free hand
painter.drawLine(startPoint, endPoint);
else{
int x = startPoint.x(), y = startPoint.y(),
largura = endPoint.x()- startPoint.x(),
altura = endPoint.y() - startPoint.y();
if(tipo == "retangulo") //draw rectangle
painter.fillRect(x,y,largura,altura,Qt::green);
else if(tipo == "circulo"){ //draw circle
painter.setBrush(Qt::green);
painter.drawEllipse(startPoint,altura,altura);
}
}
modified = true;
update();
startPoint = endPoint;
}

Custom Widget in QScrollArea Badly Redrawing Only on Scroll

I'm trying to get a custom scrolling widget in QT, and I'm getting redraw errors on scroll. Alt-tab or other redrawing events redraw correctly.
I'm basing it on the example at http://doc.qt.io/qt-5/qtwidgets-widgets-charactermap-example.html
repeatingwidget.cpp (excerpt):
QSize RepeatingWidget::sizeHint() const {
return QSize(500, itemHeight * displayItems.size() + 1);
}
void RepeatingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QRect itemRect = event->rect();
int top = itemRect.top();
QFontMetrics fontMetrics(*displayFont);
for (auto item : displayItems) {
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect.left(), top, itemRect.right(), itemHeight);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + top + fontMetrics.ascent(), item.name);
top += itemHeight;
}
}
mainwindow.cpp (excerpt):
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
QMenu *filemenu = menuBar()->addMenu(tr("File"));
filemenu->addAction(tr("Quit"), this, &QWidget::close);
auto *centralWidget = new QWidget;
scrollArea = new QScrollArea;
repeatingArea = new RepeatingWidget();
scrollArea->setWidget(repeatingArea);
auto *centralLayout = new QVBoxLayout;
centralLayout->addWidget(scrollArea, 1);
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
setWindowTitle(tr("Widget Test"));
}
This seems to match the example, but I'm getting redraw errors that don't happen in charmap.
I've tried setGeometry, setWidgetResizable, and different size policies, but I'm still getting these redraw errors.
After scrolling:
I don't know what I'm doing wrong because it's largely identical in important ways to the example code from the charmap.
This is the full code: https://gist.github.com/jonasbuckner/2acc1a960e457946ce4756199de3fb57
QPaintEvent is a method that allows you to make an intelligent painting, that is, to paint where necessary, thus saving resources, for example it gives us the information of the rectangle that must be painted through event->rect(), with this we can calculate the items that have to be painted since others will be hidden and therefore it is not necessary to paint them:
void RepeatingWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.fillRect(event->rect(), QBrush(Qt::white));
painter.setFont(displayFont);
QFontMetrics fontMetrics(displayFont);
int i = std::max(event->rect().top()/itemHeight, 0);
int j = std::min(event->rect().bottom()/itemHeight+1, displayItems.size());
QRect itemRect(0, i*itemHeight, width(), itemHeight);
for(; i < j; i++){
painter.setPen(QPen(Qt::gray));
painter.drawRect(itemRect);
painter.setPen(QPen(Qt::black));
painter.drawText(8, 4 + itemRect.top() + fontMetrics.ascent(), displayItems[i].name);
itemRect.translate(0, itemHeight);
}
}
Your original code didn't work because you were drawing all of the items, but using the event->rect, which may only be part of the RepeatingWidget.
Sometimes it is not easy to calculate which items are in the event->rect as #eyllanesc shows. In these cases, just use clientRect instead - Qt will clip the drawing for you.

Drawing Text In a QGraphicsWidget::paint function

I'm trying to draw text inside a qgraphicswidget. The scale of the scene is -180 to 180 in the horizontal and -90 to +90 in the vertical (it's a world map).
When i zoom in to individual items on the map, i want some text to show up. My code for the paint function of one particular item looks like this:
void AirportGraphicsWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
QPen pen;
pen.setStyle(Qt::PenStyle::NoPen);
painter->setBrush(Qt::lightGray);
painter->setPen(pen);
if (m_curr_lod <= LevelOfDetail::MEDIUM) {
painter->setBrush(QColor(206, 211, 219));
painter->drawEllipse(m_airport_significance_rect);
} else if(m_curr_lod == LevelOfDetail::HIGH) {
painter->setBrush(QColor(56, 55, 52, 150));
painter->drawEllipse(m_airport_boundary);
DrawRunways(painter, option, widget);
} else {
painter->setBrush(QColor(56, 55, 52));
painter->drawEllipse(m_airport_boundary);
pen.setStyle(Qt::PenStyle::SolidLine);
pen.setColor(Qt::black);
painter->setPen(pen);
DrawRunways(painter, option, widget);
DrawILS(painter, option, widget);
DrawCOM(painter, option, widget);
QPen pen;
pen.setStyle(Qt::PenStyle::SolidLine);
pen.setColor(Qt::white);
pen.setWidth(0);
QFont font("Arial");
font.setPixelSize(15);
painter->setFont(font);
painter->setPen(pen);
painter->drawText(m_airport_boundary, "TEST");
}
}
The drawText call does not seem to be working at all. My scale at this zoom level is very small. The m_airport_boundary QRectF variable has the following values:
{ x = -0.010286252057250001, y = -0.010286252057250001, width = 0.020572504114500002, height = 0.020572504114500002 }
the drawing of the m_airport_boundary rect is visible so I know im trying to draw in the correct location. Can anyone tell me what I'm doing wrong?
Screenshot of what is drawing... The dark circle is the m_airport_boundary ellipse. Green things are a result of DrawILS and the blue circle is DrawCOM
The current QTransform scale is affecting the font size.
I suggest to calculate the text position in screen space, reset the transform and then call drawText().
Here is a snippet (suppose you want to draw at the center):
QPointF pos = m_airport_boundary.center();
QTransform t = painter->transform();
painter->resetTransform();
pos = t.map(pos);
painter->drawText(pos, "TEST");

QT 5.7 QPainter line aligment

I am working with QT 5.7 and C++.
At the moment I try to get used to draw my own widgets with the QPainter class.
But I noticed a problem I couldn't solve.
I try to draw a border line extactly at the widget border but if I do so:
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter;
painter.begin(this);
painter.setBrush(Qt::cyan);
QBrush brush(Qt::black);
QPen pen(brush, 2);
painter.setPen(pen);
painter.drawRect(0, 0, size().width() - 1, size().height() - 1);
painter.end();
}
The Line is at the bottom and right site bigger than the others:
And before someone is telling me I have to remove the two -1 expressions,
you should know if I do this and also set the pen width to 1 there is no line anymore at the bottom and right side.
I think this artifact is caused by the "line aligment".
QT tries to tint the the pixels near the logical lines defined by the rectangle but actually because finally all have to be in pixels it has to decide.
If I am right, why there is no method to set the line aligment of the pen like in GDI+?
And how I can solve this?
Everything depends on whether you want the entire pen's width to be visible or not. By drawing the rectangle starting at 0,0, you're only showing half of the pen's width, and that makes things unnecessarily complicated - never mind that the line appears too thin. In Qt, the non-cosmetic pen is always drawn aligned to the middle of the line. Qt doesn't let you change it: you can change the drawn geometry instead.
To get it right for odd line sizes, you must give rectangle's coordinates as floating point values, and they must be fall in the middle of the line. So, e.g. if the pen is 3.0 units wide, the rectangle's geometry will be (1.5, 1.5, width()-3.0, width()-3.0).
Here's a complete example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/widget-pen-wide-38019846
#include <QtWidgets>
class Widget : public QWidget {
Q_OBJECT
Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth)
qreal m_penWidth = 1.0;
protected:
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.setPen({Qt::black, m_penWidth, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin});
p.setBrush(Qt::cyan);
qreal d = m_penWidth/2.0;
p.drawRect(QRectF{d, d, width()-m_penWidth, height()-m_penWidth});
}
public:
explicit Widget(QWidget * parent = 0) : QWidget{parent} { }
qreal penWidth() const { return m_penWidth; }
void setPenWidth(qreal width) {
if (width == m_penWidth) return;
m_penWidth = width;
update();
}
QSize sizeHint() const override { return {100, 100}; }
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget top;
QVBoxLayout layout{&top};
Widget widget;
QSlider slider{Qt::Horizontal};
layout.addWidget(&widget);
layout.addWidget(&slider);
slider.setMinimum(100);
slider.setMaximum(1000);
QObject::connect(&slider, &QSlider::valueChanged, [&](int val){
widget.setPenWidth(val/100.0);
});
top.show();
return app.exec();
}
#include "main.moc"

Qt drawPixmap isn't drawing what I expect

I'm trying to make a Paint application in C++ with Qt. Everytime I click or click & drag the mouse, the program will draw something on a pixmap. After that, it updates the window calling paintEvent(), which will draw the pixmap onto the window.
void QPaintArea::mousePressEvent(QMouseEvent *event){
startpoint = event->pos();
drawPoint(startpoint);
is_pressed = true;
}
void QPaintArea::mouseReleaseEvent(QMouseEvent *event){
is_pressed = false;
}
void QPaintArea::mouseMoveEvent(QMouseEvent *event){
if(is_pressed == true){
endpoint = event->pos();
drawLine(startpoint, endpoint);
startpoint = endpoint;
}
else{
return;
}
}
void QPaintArea::paintEvent(QPaintEvent *event){
QDesktopWidget *desktop = QApplication::desktop();
int x = (desktop->width() - 800) / 2;
int y = (desktop->height() - 600) / 2;
QPainter painter(this);
QRect target(QPoint(x, y - 35), QSize(800, 600));
QRect dirtyrect(QPoint(0,0), QSize(800, 600));
painter.drawPixmap(target, *pixmap, dirtyrect);
}
The problem is that, the program is not printing the pixmap onto the window as expected. For example, I press the mouse at x: 17, y: 82 trying to draw something. The program will print what I drew but at an offset location, like x + 20, y.
Maybe I don't fully understand how QRect or drawPixmap works, but the pixmap is 800x600. "dirtyrect" is supposed to save the entire pixmap (starting a x: 0, y: 0, and the size 800x600).
drawPixmap(target, pixmap, source) paints on target rect of painter area (QPaintArea in this case) source part of pixmap. So you paint whole pixmap (0,0,800,600) at some (x,y-35,800,600) rect of QPaintArea. If you want to paint whole pixmap on whole QPaintArea just use drawPixmap(QPoint(0,0), *pixmap).
// EDIT
But if you expected, that pixmap will be painted with some offset from QPaintArea top left corner, then your calculations are wrong, and if you wont explain what did you want to achieve we won't be able to help you. Explain us your calculations of x,y (and magic -35 for y), and maybe we will be able to figure something out
// EDIT
You don't have to use window offsets like -35 if you're painting on widget. 0,0 of the widget is not top left corner of window frame, but of widget contents. How do you expect it to behave on other platforms?
If you want to paint it in the middle of your window simply use:
void QPaintArea::paintEvent(QPaintEvent *event){
QPoint middle = geometry.center();
int x = middle.x() - 800/2; // probably best would be pixmap->width()/2
int y = middle.y() - 600/2; // probably best would be pixmap->height()/2
QPainter painter(this);
painter.drawPixmap(QPoint(x,y), *pixmap);
}