Search code examples
c++imageqtuser-interfaceqtgui

How to Draw a point in a QPixmap Image?


I have a project where i want to draw a point into a image inside a QPixmap. The point would be draw with the mouse click on the QLabel. I created a eventFilter() which corresponds to mouse click. When I click with the mouse, these eventFilter is called and draw a point in the image, but my code doesn't works. I tried many others options like subclassing the QLabel, but didn't work either.

And sometimes my compiler shows these error messages:

QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::setPen: Painter not active
QPainter::drawPoints: Painter not active
QPainter::end: Painter not active, aborted

but I don't understand, because the Qt documentation says that is allowed use the QPainter outside of paintEvent just using with QPixmap.

Below is my code with the method that starts the QPainter.

bool mainwindow::eventFilter(QObject* watched, QEvent* event) {
if ( watched != ui->labelScreen )
    return false;
if ( event->type() != QEvent::MouseButtonPress )
    return false;
const QMouseEvent* const me = static_cast<const QMouseEvent*>( event );
//might want to check the buttons here
const QPoint p = me->pos(); //...or ->globalPos();

ui->label_Xget->setNum(this->xReal);
ui->label_Yget->setNum(this->yReal);

///////////////////////////////////

QPixmap pix;
pix.fromImage(QImage::fromData("C:/Users/Syn/Pictures/imagem137.jpg"));

QPainter *painter = new QPainter(&pix);

painter->setPen(Qt::red);
painter->drawPoint(p.x(), p.y());
ui->labelScreen->setPixmap(pix);
painter->end();

///////////////////////////////////


return false;
}

Someone would can help me solve this problem? Thanks.


Solution

  • The error messages are not from your compiler, they happen at runtime, and you can't be sure whether the code you quote above is their cause.

    There are several problem:

    1. QPixmap::fromImage is a static method that returns a pixmap. You're ignoring its return value. The correct way to use it would be:

      // C++11
      auto pix = QPixmap::fromImage(QImage{"filename"});
      // C++98
      QPixmap pix(QPixmap::fromImage(QImage("filename")));
      
    2. You can pass the filename directly to QPixmap constructor:

      // C++11
      QPixmap pix{"filename"};
      // C++98
      QPixmap pix("filename");
      
    3. You're leaking the painter instance. You should avoid any dynamic memory allocation unless you truly need it. Also, store ui by value - it's cheaper that way.

    4. It is a very bad idea to do any blocking I/O, such as reading a file, in event handlers. Preload and store the pixmap as a member of the MainWindow class.

    Thus (in C++11):

    Interface

    template <class F>
    class MainWindow : public QMainWindow {
      QPixmap m_clickPixmap;
      Ui::MainWindow ui;
      bool eventFilter(QObject*, QEvent*) override;
    public:
      MainWindow(QWidget * parent = nullptr);
    };
    

    Implementation

    void loadImage(const QString & fileName, QObject * context, F && functor) {
      QtConcurrent::run([=]{
        QImage img{fileName};
        if (img.isNull()) return;
        QTimer::singleShot(0, context, functor, img);
      });
    }
    
    MainWindow::MainWindow(QWidget * parent) :
      QMainWindow{parent}
    {
      loadImage("C:/Users/Syn/Pictures/imagem137.jpg", this, [this](const QImage & img){
        m_clickPixmap = QPixmap::fromImage(img);
      });
      ui.setupUi(this);
    }
    
    bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
      if ( watched != ui.labelScreen )
        return false;
      if ( event->type() != QEvent::MouseButtonPress )
        return false;
      auto const me = static_cast<const QMouseEvent*>(event);
      auto const p = me->pos(); //...or ->globalPos();       
      ui.label_Xget->setNum(this->xReal);
      ui.label_Yget->setNum(this->yReal);
    
      auto pix{m_clickPixmap};
      QPainter painter(&pix);
      painter.setPen(Qt::red);
      painter.drawPoint(p.x(), p.y());
      painter.end(); // probably not needed
      ui.labelScreen->setPixmap(pix);
      return false;
    }