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);
}
Related
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;
}
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");
I can currently see the dialog window, the only thing I cannot see is the label where the picture is displayed and the rectangle drawn. Now I believe is because the widget is covering the label, but I cannot think on a way to walk around it. The application output says:
QRect(10,10 0x0), 0 0 0 final 0, QWidget::paintEngine: Should no longer be called, QPainter::begin: Paint device returned engine == 0, type: 1, QWidget::paintEngine: Should no longer be called, QPainter::begin: Paint device returned engine == 0, type: 1, QPainter::setPen: Painter not active, QPainter::drawRects: Painter not active, qDebug PAINTEVENT
my code is as follows:
This is my_qlabel.cpp
/Uses mouse events/
#include "my_qlabel.h"
#include <QMouseEvent>
#include <QPaintEvent>
my_qlabel::my_qlabel(QWidget *parent) :
QLabel (parent)
{
x = NULL;
y = NULL;
}//constructor
void my_qlabel::mouseMoveEvent (QMouseEvent *e)
{
this->x = e->x();
this->y = e->y();
emit mouse_pos();
}//mouseMoveEvent
void my_qlabel::mouseReleaseEvent (QMouseEvent *e)
{
this-> x = e->x ();
this-> y = e->y ();
emit mouse_release();
}//mouseReleaseEvent
void my_qlabel::mousePressEvent(QMouseEvent *)
{
emit mouse_pressed ();
}//mousePressEvent
void my_qlabel::paintEvent(QPaintEvent *pa)
{
this->parentWidget ();
this->lower ();
emit mouse_rectangle ();
pa->accept (); //accepts rect in pic
}//paintevent
void my_qlabel::leaveEvent(QEvent *)
{
emit mouse_left();
}//leaveEvent
below is my dialog.cpp:
/*Makes connections between my_qlabel and dialog.
*/
#include "dialog.h"
#include "ui_dialog.h"
#include "my_qlabel.h"
#include <QPainter>
#include <QEvent>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
InitialX = 0;
InitialY = 0;
Height = 0;
Width = 0;
ui->setupUi(this);
connect(ui-> lblMouse, SIGNAL(mouse_pos()), this, SLOT(mouse_current_pos()));
connect(ui-> lblMouse, SIGNAL(mouse_pressed()), this, SLOT(mouse_pressed()));
// connect(ui-> lblMouse, SIGNAL(mouse_pressed()), this, SLOT(paintEvent(QPaintEvent*)));
connect(ui-> lblMouse, SIGNAL(mouse_release()), this, SLOT(mouse_release()));
connect(ui-> lblMouse, SIGNAL(mouse_left()), this, SLOT(mouse_left()));
connect (ui->lblMouse,SIGNAL(mouse_rectangle()), this,SLOT(mouse_rectangle()));
//MUST: create function for clear rectangle
//connect(ui-> ClearSelection, SIGNAL(rectangle_clear()), this, SLOT(mouse_left()));
/*delete rectangle points and update paint*/
}
Dialog::~Dialog() { delete ui;} //deconstruct
/* Generate mouse position on real time in the label of X and Y*/
void Dialog::mouse_current_pos ()
{
ui->lblMouse_Current_Pos->setText(QString
(" X = %1 , Y = %2")
.arg(ui->lblMouse->x)/*%1*/
.arg(ui->lblMouse->y));/*%2*/
qDebug()<<"qDebug MOUSE_CURRENT_POS \n";
}//mouse_current_pos()
/* Uses mouse event to START rectangle paint event*/
void Dialog::mouse_pressed()
{
ui->lblMouse_Current_Event->setText(QString
("Mouse pressed at location %1 and %2!!!")
.arg(ui->lblMouse->x) //%1
.arg(ui->lblMouse->y)); //%2
/*Sets location of X and Y when is pressed*/
InitialX = ui->lblMouse->x;
InitialY = ui->lblMouse->y;
qDebug()<<"UPDATE OF MOUSE_PRESSED \n";
update();
} //mouse_pressed()
/*Uses release mouse event to END rectangle paint event*/
void Dialog::mouse_release()
{
ui->lblMouse_Current_Event->setText(QString
("Mouse released at location %1 and %2!!!")
.arg(ui->lblMouse->x) /*%1*/
.arg(ui->lblMouse->y));/*%2*/
/*Sets location of width and height when is released*/
Width= ui->lblMouse->x - InitialX;
Height= ui->lblMouse->y - InitialY;
qDebug()<<Width<<" final "<<Height;
qDebug()<<"qDebug MOUSE_RELEASE \n";
update();
}//mouse_release()
/*Mouse finds the cursor out of mouse area*/
void Dialog::mouse_left()
{
ui->lblMouse_Current_Event->setText("Mouse Left :( !!");
qDebug()<<"qDebug MOUSE_LEFT \n";
}//mouse_left()
void Dialog::mouse_rectangle()
/*PaintEvent paint rectangle*/
//!!!!!Runs good: Paints a rectangle and is adjusted!!!!
//void Dialog::paintEvent(QPaintEvent *pa)
{
// this->raise ();
QRect rectangle
(InitialX,InitialY, //Initial point of rectangle at press event
Width, Height); //Final point of rectangle at release event
rectangle.adjust (10,10,10,10);
qDebug()<<rectangle;
qDebug()<<InitialX<<InitialY<<Width<<" final "<<Height;
QPainter painter(this);
painter.begin (this);
painter.setPen(QPen(Qt::red, //Propierties of rectangle
2.0,
Qt::DotLine,
Qt::SquareCap,
Qt::MiterJoin));
painter.drawRect(rectangle);
qDebug()<<"qDebug PAINTEVENT \n";
}//paintEvent
Hopefully I have made my self clear, I want to draw on the qlabel, but I can't. If you can tell me how to fix the code in an explicit manner would be great.
The problem I see is, that you call a drawing function of a different class to draw in that different class by an emit in my_qlabels paintEvent.
If a widget receives the paintEvent only that widget can be drawn. An only in that class works QPainter p(this).
In your case, my_qlabel has the paintEvent but you "call" mouse_rectangle of the Dialog class to draw on the Dialog. But Dialog is not ready for painting. Hence the Paint device returned engine == 0 issue.
If your label shows a picture your label needs to do the drawing on itself to mark an area on the pic. and the label would be placed in the dialog. Within that paintEvent, at the beginning, you should call QLabel::paintEvent(event) to let the label do the picture painting. Your drawing will than be on top of that picture.
It is important that you draw in the paintEvent of the label on the label itself.
You may still use another function but you must pass along the pointer of my_qlabel and create the QPainter with that pointer (e.g. QPainter p(label) instead of QPainter p(this)).
So you could pass this on my_qlabel along with the emit, like emit mounse_rectangle(this) if you alter the signal and slot the accept QWidget * as argument. Though, I'd prefer calling any function directly from a paintEvent instead of using the signal and slot mechanism which is may introduce a considerable overhead in terms of performance and reduces code clarity.
I want to show a pixmap on Qt and make able to zoom, I'm using a QWidget inside a QScrollArea, but the ScrollBars of the area are not working, when I zoom on the image, the image gets bigger, but there is no scrollbars so i can move, let me show you some code :
Here is how I'm declaring the Widgets :
_scroll = new QScrollArea(_frame);
_image_widget = new QImageWidget(_scroll);
_scroll->setBackgroundRole(QPalette::Dark);
_scroll->setWidget(_image_widget);
_scroll->setWidgetResizable(true);
_frame is the area where i should show the hole thing, _image_widget is an object of QImageWidget which is inheriting from QWidget
if I dont use this : _scroll->setWidgetResizable(true); the image is just too small
And here is how I deal with the Zoom :
void QImageWidget::paintEvent(QPaintEvent *ev) {
QPainter p(this);
if(pixmap() != NULL){
int w = pixmap()->width();
int h = pixmap()->height();
QPixmap map = pixmap()->scaled(w*zoom,h*zoom,Qt::KeepAspectRatio);
p.drawPixmap(0, 0, map );
}
}
void QImageWidget::wheelEvent ( QWheelEvent * e )
{
int x = e->pos( ).x();
int y = e->pos( ).y();
if (e->delta() > 0)
zoom *= 2;
else
if(zoom > 1)
zoom /= 2;
}
SO the problem is as I said, the image keeps getting bigger when i'm zooming until it takes the hole area of the QScrollArea and then when I keep zooming, the zoom is working but there is no ScrollBars so i can see the other part of the Image.
Tell me if i'm not understandable !
thanks !
I found the answer to my question, all i had to do is add this resize(sizeHint()*zoom); before drawing the pixmap :
void QImageWidget::paintEvent(QPaintEvent *ev) {
QPainter p(this);
if(pixmap() != NULL){
int w = pixmap()->width();
int h = pixmap()->height();
QPixmap map = pixmap()->scaled(w*zoom,h*zoom,Qt::KeepAspectRatio);
resize(sizeHint()*zoom);
p.drawPixmap(0, 0, map );
}
}
the pixels were updated but not the size of the widget !
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...