PDA

View Full Version : Adding Scale in Qt Painting



avanindra
16th July 2012, 16:24
Hi Folks,

I would need some advice regarding the below problem.

Problem Description:

I have to create a painting widget , which loads a image , on which I can put marker with paint brush. The widget need to have mouse pointer centralized scaling with mouse scroll.

I had two choices to implement it:

1 ) Using QGraphicsView framework.

2) implementing a simple paintarea by extending QWidget and displaying QImage on it using QPainter.

The problem I am facing is by using either of the choices I can not implement all the features I need , the first method has nice features for implementing scaling with mouse scroll , but seems like QGraphicsView
has it's own internal painter , so I can not use a new painter for the QPixmap inside QgraphicsView , as it gives run time exception that "QPainter::begin: A paint device can only be painted by one painter at a time." . Also the painting due to QGraphicsView is not permanent on the widget , i.e. if in next paint event I do not redraw all the previously marked content , the framework removes all the painting and restores the original image , i.e the marking is not permanent to the pixmap of the graphics item.

In the second method I am not able to implement scaling properly and I could not find any opensource example doing the same.

I would be very grateful for any advice solving my problem.

Many thanks and regards

Avanindra Singh

Cruz
18th July 2012, 12:04
Using QTransform is a good way to implement translation and scaling features. The documentation that comes with it tells you everything you need to know, but here you go I will copy and paste some code snippets together (untested!) to give you a head start.



ScalableWidget.h:

#include <QtGui>

class ScalableWidget: public QWidget
{
Q_OBJECT

double screenScale;
QPointF screenOffset;
QTransform screenTransform;

QPointF mouse; // current mouse location in pixel coordinates
QPointF mappedMouse; // current mouse location in logical coordinates
QPointF lastMouse; // last mouse location in pixel coordinates
QPointF mouseDiff; // last mouse motion in pixel coordinates
QPointF mappedMouseDiff; // last mouse motion in logical coordinates

public:
ScalableWidget(QWidget* parent=0);
~ScalableWidget(){};

protected:
void paintEvent(QPaintEvent*);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void resizeEvent(QResizeEvent *event);

private:
void updateMouse(QPointF mousePos);
void updateScreenTransform();

};




ScalableWidget.cpp:

#include "ScalableWidget.h"

ScalableWidget::ScalableWidget(QWidget *parent)
: QWidget(parent)
{
screenScale = 0.01; // 1 px is worth 0.01 value
screenOffset = QPointF(0,0);
}

void ScalableWidget::mousePressEvent(QMouseEvent *event)
{
mouseClick = event->posF();
mouseClickTimeStamp = stopWatch.programTime();
setCursor(Qt::ClosedHandCursor);
}

void ScalableWidget::mouseMoveEvent(QMouseEvent *event)
{
updateMouse(event->posF());

if (event->buttons() & (Qt::LeftButton | Qt::RightButton))
{
screenOffset -= mappedMouseDiff;
updateScreenTransform();
}

update();
}

void ScalableWidget::mouseReleaseEvent(QMouseEvent *event)
{
updateMouse(event->posF());
unsetCursor();
update();
}

void ScalableWidget::wheelEvent(QWheelEvent *event)
{
if (event->delta() > 0)
{
screenScale /= 1.2;
}
else
{
screenScale *= 1.2;
}

updateScreenTransform();
updateMouse(QPointF(event->pos()));
update();
}

void ScalableWidget::resizeEvent(QResizeEvent* event)
{
updateScreenTransform();
}


void ScalableWidget::updateMouse(QPointF mousePos)
{
mouse = mousePos;
mappedMouse = screenTransform.inverted().map(mouse);
mouseDiff = (mouse - lastMouse);
mappedMouseDiff = mouseDiff*screenScale;
mappedMouseDiff.ry() = -mappedMouseDiff.y();
lastMouse = mouse;
}

// Updates the transformation from screen to logical coordinates.
// This should be called each time when the scaling factor or the
// screen translation changes.
void ScalableWidget::updateScreenTransform()
{
screenTransform = QTransform();
screenTransform.translate(width()/2, height()/2); // Place the origin in the center of the screen.
screenTransform.scale(1.0/screenScale, -1.0/screenScale); // Flip it upside down and scale (zoom) it.
screenTransform.translate(-screenOffset.x(), -screenOffset.y()); // Apply the offset.
mappedMouse = screenTransform.inverted().map(mouse); //Calculate the logical mouse coordinates.
}

void ScalableWidget::paintEvent(QPaintEvent*)
{
// Start the widget painter.
QPainter painter(this);

// Apply the coordinate transformation from device coordinates ((0,0) is in the top left corner)
// to logical coordinates, where the origin of the coordinate system is in the middle
// and y grows upwards. A scaling factor converts from pixel values to logical units
// (e.g. 100 px = 1 second).
painter.setTransform(screenTransform);

// Draw anything you want here.

}

avanindra
18th July 2012, 19:40
Thanks Cruz for the reply. QTransform was quite helpful for me. Though I am not through with my problem yet.

I am facing problem while implementing scroll while scaling of the image. For implementing scroll for variable size widget , I used the method mentioned in Qt example http://doc.qt.nokia.com/4.7-snapshot/widgets-imageviewer.html , with one difference , I am trying to update the scroll area limits from the child widget ( ScalableWidget ) :




void ScalableWidget::wheelEvent(QWheelEvent *event)
{

float factor = qPow( 1.001 , event->delta() );

// screenScale *= factor;



if (event->delta() > 0)
{
screenScale /= 1.2;

factor = 1.0 / 1.2;
}
else
{
screenScale *= 1.2;

factor = 1.2;
}

updateMouse(QPointF(event->pos()));

updateScreenTransform();

update();

ImageScrollArea *scrollArea = ( ImageScrollArea* )this->parent();

QScrollBar *horizontalScrollBar = scrollArea->horizontalScrollBar();
QScrollBar *verticalScrollBar = scrollArea->verticalScrollBar();


adjustScrollBar( horizontalScrollBar , factor );
adjustScrollBar( verticalScrollBar , factor );

scrollArea->update();

}


void ScalableWidget::adjustScrollBar(QScrollBar *scrollBar, double factor)
{

int value = int(factor * scrollBar->value()
+ ((factor - 1) * scrollBar->pageStep()/2));

std::cout<<scrollBar->value()<<" "<<value<<" "<<scrollBar->pageStep()<<" -- ";

scrollBar->setValue( value );


std::cout<<scrollBar->value()<<" "<<value<<" "<<scrollBar->pageStep()<<std::endl;
}


The strange thing is scrollBar->setValue( value ) is not updating the new value , instead it keeps the old value. Is this beacuse of I am trying to update it from child widget?... Is this standard Qt widget property that I can not change its attribute values from inside child widgets?...


Thanks a lot for the help.

Avanindra Singh

Cruz
19th July 2012, 09:30
Hello!

setValue() should work. Did you call setWidget() to set the ScalableWidget into the scroll area? Oh and just because you change the QTransform of the painter it doesn't mean that the size of the widget changes and as far as I know the scroll bars are laid out according to the size of the contained widget. Also, try to set the scrollbar values hard to 0 or a large number to check if they do anything at all and see if the problem is in that part or maybe in the calculation of the factor.