Making zooms on an QWidget while inside a QScrollArea - c++

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 !

Related

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.

QT Clear painter canvas Widget drawn using Overlay Widget

I have referenced this code to draw points on my widget.
OverLay::OverLay(std::vector<int> &points, QWidget *parent) :
QWidget(parent),
m_points(points)
{
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
void OverLay::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(QPen(Qt::red,2));
//painter.eraseRect( this->rect() );
int y_pos = height()/2;
for(int i=0; i<m_points.size();i++){
int x_pos = m_points[i];
QPointF p1 = QPointF(x_pos,y_pos);
painter.drawPoint(p1);
}
}
Here am creating new Overlay Widget and drawing
void Dialog::draw_points(std::vector<int> points)
{
OverLay *m_overlay = new OverLay(points,ui->view->parentWidget());
m_overlay->setGeometry(ui->view->geometry());
m_overlay->show();
}
So, everytime, Whenever I call draw_points functions, old drawing will be there. I want to clear the canvas everytime before drawing again.
painter.eraseRect( this->rect() ); will clear points. But, it will also clear underlying view.
Well, I always do it always specific:
painter.setBrush(Qt::NoBrush);
painter.setPen(Qt::NoPen);
painter.drawRect(rect());
I did not know it was so simple ..
Simply deleting instance of m_overlay worked.
if(m_overlay!=NULL){
delete m_overlay;
}

save the current items on qwidget as image

I'm trying draw some rhombuses with random colors in a QWidget. And i want to save the current QWidget as image. I use such code to do this:
QPixmap pixmap(this->size());
this->render(&pixmap);
pixmap.save("test.png");
The problem is that the render() seems to call paintEvent again, and the paintEvent will draw the rhombuses with new random colors, so that i always get a different Image saved compared to the image displayed. Can someone tell me how to save the current QWidget? Thanks in advance.
Code for drawing rhombuses:
void Dialog::paintEvent(QPaintEvent *e) {
QPainter painter(this);
QRect background(0,0,this->geometry().width(),this->geometry().height());
painter.setBrush( QBrush( Qt::white ) );
painter.setPen( Qt::NoPen );
//QBrush bbrush(Qt::black,Qt::SolidPattern);
painter.drawRect(background);
int width = this->geometry().width();
int height = this->geometry().height();
//draw rectangles
int rec_size=64;
int rows=0;
int cols=0;
rows=floor((double)height/(double)rec_size);
cols=floor((double)width/(double)rec_size);
QPointF points[4]; // QRect rec(0,0,rec_size,rec_size);
for (int i=0;i<floor(rows);i++){
for (int j=0;j<floor(cols);j++){
painter.setBrush( QBrush( colors[rand() % color_size] ) );
//QPainter painter(this);
points[0] = QPointF(rec_size*(j),rec_size*(i+0.5));
points[1] = QPointF(rec_size*(j+0.5),rec_size*(i));
points[2] = QPointF(rec_size*(j+1),rec_size*(i+0.5));
points[3] = QPointF(rec_size*(j+0.5),rec_size*(i+1));
painter.drawPolygon(points, 4);
}
}
painter.end();
}
You can have a class member variable of boolean type to check in the paintEvent whether a random color should be used. Also a variable to save the index of the last color used is necessary:
bool isRandom;
int lastColor;
The paintEvent should be like :
void Dialog::paintEvent(QPaintEvent *e) {
...
if(isRandom)
{
lastColor = rand() % color_size;
painter.setBrush( QBrush( colors[lastColor] ) );
}
else
painter.setBrush( QBrush( colors[lastColor] ) );
...
}
The variable has true when drawing the widget regularly. When you want to save it's image, assign the variable to false, save the image and assign it to true again :
isRandom = false;
QPixmap pixmap(this->size());
this->render(&pixmap);
pixmap.save("test.png");
isRandom = true;

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

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...

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);
}