Using QRubberBand to crop image in Qt - c++

I want to be able to use rubberband to select an area of an image, then remove the parts of the image outside of the rubberband and display the new image. However when I currently do it, it doesnt crop the correct areas and gives me the wrong image.
#include "mainresizewindow.h"
#include "ui_mainresizewindow.h"
QString fileName;
MainResizeWindow::MainResizeWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainResizeWindow)
{
ui->setupUi(this);
ui->imageLabel->setScaledContents(true);
connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(open()));
}
MainResizeWindow::~MainResizeWindow()
{
delete ui;
}
void MainResizeWindow::open()
{
fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty()) {
QImage image(fileName);
if (image.isNull()) {
QMessageBox::information(this, tr("Image Viewer"),
tr("Cannot load %1.").arg(fileName));
return;
}
ui->imageLabel->setPixmap(QPixmap::fromImage(image));
ui->imageLabel->repaint();
}
}
void MainResizeWindow::mousePressEvent(QMouseEvent *event)
{
if(ui->imageLabel->underMouse()){
myPoint = event->pos();
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->show();
}
}
void MainResizeWindow::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(myPoint, event->pos()).normalized());
}
void MainResizeWindow::mouseReleaseEvent(QMouseEvent *event)
{
QRect myRect(myPoint, event->pos());
rubberBand->hide();
QPixmap OriginalPix(*ui->imageLabel->pixmap());
QImage newImage;
newImage = OriginalPix.toImage();
QImage copyImage;
copyImage = copyImage.copy(myRect);
ui->imageLabel->setPixmap(QPixmap::fromImage(copyImage));
ui->imageLabel->repaint();
}
Any help appreciated.

There are two issues here - the position of the rect relative to the image and the fact that the image is (potentially) scaled in the label.
Position issue:
QRect myRect(myPoint, event->pos());
You should perhaps change this to:
QPoint a = mapToGlobal(myPoint);
QPoint b = event->globalPos();
a = ui->imageLabel->mapFromGlobal(a);
b = ui->imageLabel->mapFromGlobal(b);
Then, the label may be scaling the image because you used setScaledContents(). So you need to work out the actual coordinates on the unscaled image. Something like this maybe (untested/compiled):
QPixmap OriginalPix(*ui->imageLabel->pixmap());
double sx = ui->imageLabel->rect().width();
double sy = ui->imageLabel->rect().height();
sx = OriginalPix.width() / sx;
sy = OriginalPix.height() / sy;
a.x = int(a.x * sx);
b.x = int(b.x * sx);
a.y = int(a.y * sy);
b.y = int(b.y * sy);
QRect myRect(a, b);
...

Related

Cover QLabel with QPixmap in C++

My goal
This is an example of how I want the QPixmap to be contained inside the QLabel. The image keeps its aspect ratio and fills the given dimension. It will be clipped to fit.
Here is the CSS equivalent:
object-fit: cover;
Current code
This almost does what I want, but it stretches the image to cover the QLabel, so the aspect ratio isn't reserved.
QPixmap *img = new QPixmap("image.png");
ui->label->setPixmap(*img);
ui->label->setScaledContents(true);
delete img;
Can be implemented as QWidget subclass using QTransform.
class Label : public QWidget
{
Q_OBJECT
public:
explicit Label(QWidget *parent = nullptr);
void setPixmap(const QPixmap& image);
protected:
void paintEvent(QPaintEvent *event);
QPixmap mPixmap;
};
Label::Label(QWidget *parent) : QWidget(parent) {
}
void Label::setPixmap(const QPixmap &pixmap)
{
mPixmap = pixmap;
update();
}
void Label::paintEvent(QPaintEvent *event)
{
if (mPixmap.isNull()) {
return;
}
double width = this->width();
double height = this->height();
double pixmapWidth = mPixmap.width();
double pixmapHeight = mPixmap.height();
double scale = qMax(width / pixmapWidth, height / pixmapHeight);
QTransform transform;
transform.translate(width / 2, height / 2);
transform.scale(scale, scale);
transform.translate(-pixmapWidth / 2, -pixmapHeight / 2);
QPainter painter(this);
painter.setTransform(transform);
painter.drawPixmap(QPoint(0,0), mPixmap);
}

MouseEvent coordinates on Qlabel with Image

so i dont know how to add a mouseevent ( if i click the label with image selected to add x,y coordinates of the mouse on the tableWidget and the coordinates to be drawed on the image ( green/red dots)
for now i only can open a ascii file with coordinates and image. i need your help with mouseevent and draw the points on the image
void design::on_loadtext1_clicked() {
ui->merge->setText("merge");
QString filename=QFileDialog::getOpenFileName(
this,
tr("Open File"),
"C://",
"Text File(*.txt)"
);
QFile file(filename);
if(!file.open(QIODevice::ReadOnly)) { QMessageBox::information(nullptr,"Info",file.errorString());
}
QTextStream in(&file);
double x = 0.0;
double y = 0.0;
// double xn = 532;
int row=0;
QString line;
ui->tableWidget->setRowCount(30);
ui->tableWidget->setColumnCount(2);
while(!in.atEnd()) {
line = in.readLine();
QStringList s = line.split(" ");
x = s.at(0).toDouble();
y = s.at(1).toDouble();
ui->tableWidget->setItem(row, 0, new TableItem(tr("%1").arg(x)));
ui->tableWidget->setItem(row, 1, new TableItem(tr("%1").arg(y)));
row++;
} }
If you want to draw an ellipse on QLabel at mouse click position, this code should get you started:
First, as proposed, subclass QLabel:
.h file
#include <QLabel>
#include <QPainter>
#include <QMouseEvent>
class MyLabel : public QLabel
{
public:
MyLabel(QWidget* parent=nullptr);
void mousePressEvent(QMouseEvent * e);
private:
QPixmap *pix;
};
Then create pixmap in constructor and implement drawing on mouseClickEvent:
.cpp file
MyLabel::MyLabel(QWidget *parent) :
QLabel(parent)
{
pix = new QPixmap(200,200);
pix->fill(Qt::white);
setPixmap(*pix);
}
void MyLabel::mousePressEvent(QMouseEvent *e)
{
int xPos, yPos;
if(e->button() == Qt::LeftButton)
{
xPos = e->pos().x();
yPos = e->pos().y();
}
QPainter painter(pix);
painter.setPen(Qt::black);
painter.drawEllipse(xPos, yPos, 3, 3);
setPixmap(*pix);
QLabel::mousePressEvent(e);
}
Then just create an instance of MyLabel in MainWindow:
label = new MyLabel(this);
label->setGeometry(0,0,200,200);
Don't forget declaration in mainwindow.h:
MyLabel *label;
Hope this helps.

How to get Image pixel position loaded in QGraphicsView - Strange MapToScene() behaviour

I am originally loading image in QGraphicsView and using this method for basic zoom out and zoom in functionality.
However, I am unable to retrieve actual image pixel position using mapToScene functionality in eventFilter function of Graphics_view_zoom class. The below code produces behaviour exactly as windows photo viewer zooming only selected region.
MapToScene() returns same Point as mouse event position.
Here is the class which deals with zooming.
#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>
Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
: QObject(view), _view(view)
{
_view->viewport()->installEventFilter(this);
_view->setMouseTracking(true);
_modifiers = Qt::ControlModifier;
_zoom_factor_base = 1.0015;
}
void Graphics_view_zoom::gentle_zoom(double factor) {
_view->scale(factor, factor);
_view->centerOn(target_scene_pos);
QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
_view->viewport()->height() / 2.0);
QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
_view->centerOn(_view->mapToScene(viewport_center.toPoint()));
emit zoomed();
}
void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
_modifiers = modifiers;
}
void Graphics_view_zoom::set_zoom_factor_base(double value) {
_zoom_factor_base = value;
}
bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
QPointF delta = target_viewport_pos - mouse_event->pos();
// Here I want to get absolute image coordinates
if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
target_viewport_pos = mouse_event->pos();
target_scene_pos = _view->mapToScene(mouse_event->pos());
}
} else if (event->type() == QEvent::Wheel) {
QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
if (QApplication::keyboardModifiers() == _modifiers) {
if (wheel_event->orientation() == Qt::Vertical) {
double angle = wheel_event->angleDelta().y();
double factor = qPow(_zoom_factor_base, angle);
gentle_zoom(factor);
return true;
}
}
}
Q_UNUSED(object)
return false;
In mainwindow.cpp,
I am creating object of this class and loading an image as below:
m_GraphicsScene = new QGraphicsScene();
pixmapItem = new QGraphicsPixmapItem();
m_GraphicsScene->addItem(multiview[i].pixmapItem);
view_wrapper = new Graphics_view_zoom(ui->GraphicsView);
ui->GraphicsView->setScene(multiview[i].m_GraphicsScene);
pixmapItem->setPixmap(QPixmap::fromImage("img.jpg"));
multiview[view].m_GraphicsView->fitInView(QRectF(0,0,640,320),Qt::KeepAspectRatio);
Can anyone help with how do I achieve this ?
Keep in mind that the scaling you use only scales the scene, not the items. Given this, the position of the pixel can be obtained, so the algorithm is:
Obtain the mouse position with respect to the QGraphicsView
Transform that position with respect to the scene using mapToScene
Convert the coordinate with respect to the scene in relation to the item using mapFromScene of the QGraphicsItem.
Considering the above, I have implemented the following example:
#include <QtWidgets>
#include <random>
static QPixmap create_image(const QSize & size){
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::blue);
std::random_device rd;
std::mt19937_64 rng(rd());
std::uniform_int_distribution<int> uni(0, 255);
for(int i=0; i< image.width(); ++i)
for(int j=0; j < image.height(); ++j)
image.setPixelColor(QPoint(i, j), QColor(uni(rng), uni(rng), uni(rng)));
return QPixmap::fromImage(image);
}
class GraphicsView : public QGraphicsView
{
Q_OBJECT
Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers WRITE setModifiers)
public:
GraphicsView(QWidget *parent=nullptr): QGraphicsView(parent){
setScene(new QGraphicsScene);
setModifiers(Qt::ControlModifier);
auto item = scene()->addPixmap(create_image(QSize(100, 100)));
item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
item->setPos(40, 40);
fitInView(QRectF(0, 0, 640, 320),Qt::KeepAspectRatio);
resize(640, 480);
}
void setModifiers(const Qt::KeyboardModifiers &modifiers){
m_modifiers = modifiers;
}
Qt::KeyboardModifiers modifiers() const{
return m_modifiers;
}
signals:
void pixelChanged(const QPoint &);
protected:
void mousePressEvent(QMouseEvent *event) override{
if(QGraphicsPixmapItem *item = qgraphicsitem_cast<QGraphicsPixmapItem *>(itemAt(event->pos()))){
QPointF p = item->mapFromScene(mapToScene(event->pos()));
QPoint pixel_pos = p.toPoint();
emit pixelChanged(pixel_pos);
}
QGraphicsView::mousePressEvent(event);
}
void wheelEvent(QWheelEvent *event) override{
if(event->modifiers() == m_modifiers){
double angle = event->orientation() == Qt::Vertical ? event->angleDelta().y(): event->angleDelta().x();
double factor = qPow(base, angle);
applyZoom(factor, event->pos());
}
}
private:
void applyZoom(double factor, const QPoint & fixedViewPos)
{
QPointF fixedScenePos = mapToScene(fixedViewPos);
centerOn(fixedScenePos);
scale(factor, factor);
QPointF delta = mapToScene(fixedViewPos) - mapToScene(viewport()->rect().center());
centerOn(fixedScenePos - delta);
}
Qt::KeyboardModifiers m_modifiers;
const double base = 1.0015;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
GraphicsView *view = new GraphicsView;
QLabel *label = new QLabel;
QObject::connect(view, &GraphicsView::pixelChanged, label, [label](const QPoint & p){
label->setText(QString("(%1, %2)").arg(p.x()).arg(p.y()));
});
label->setAlignment(Qt::AlignCenter);
QWidget w;
QVBoxLayout *lay = new QVBoxLayout(&w);
lay->addWidget(view);
lay->addWidget(label);
w.show();
return a.exec();
}
#include "main.moc"
It may be better to use a custom graphics scene subclassed from QGraphicsScene as this makes extracting the necessary coordinates much simpler. The only snag is you have to have the QGraphicsPixmapItem::pos available in the custom QGraphicsScene class - I have included a full working example which uses Graphics_view_zoom.h and Graphics_view_zoom.cpp from the linked question. The position of the QGraphicsPixmapItem is passed to a member of the QGraphicsScene subclass, Frame, in order to make the necessary correction.
#include <QPixmap>
#include <QGraphicsPixmapItem>
#include <QGraphicsTextItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <qfont.h>
#include "Graphics_view_zoom.h"
class Frame : public QGraphicsScene {
Q_OBJECT
public:
QGraphicsTextItem * coords;
QPointF pic_tl;
Frame::Frame(QWidget* parent)
: QGraphicsScene(parent) {
coords = new QGraphicsTextItem();
coords->setZValue(1);
coords->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
addItem(coords);
}
void Frame::tl(QPointF p) {
pic_tl = p;
}
protected:
void Frame::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
QPointF pos = event->scenePos();
coords->setPlainText("(" + QString("%1").arg(int(pos.x() - pic_tl.x())) + ", "
+ QString("%1").arg(int(pos.y() - pic_tl.y())) + ")");
coords->setPos(pos);
coords->adjustSize();
}
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QMainWindow* main = new QMainWindow();
QGraphicsView* GraphicsView = new QGraphicsView(main);
Graphics_view_zoom* view_wrapper = new Graphics_view_zoom(GraphicsView);
Frame* frame = new Frame(main);
QGraphicsPixmapItem* pixmapItem = new QGraphicsPixmapItem();
frame->addItem(pixmapItem);
GraphicsView->setScene(frame);
// Loads a 497x326 pixel test image
pixmapItem->setPixmap(QPixmap(":/StackOverflow/test"));
// small offset to ensure it works for pictures which are not at
// (0,0). Larger offsets produce the same result but require manual
// adjustments of the view, I have neglected those for brevity as
// they are not in the scope of the question.
pixmapItem->setPos(-20, 20);
frame->tl(pixmapItem->pos());
GraphicsView->fitInView(QRectF(0, 0, 640, 320), Qt::KeepAspectRatio);
GraphicsView->centerOn(pixmapItem->pos());
main->resize(1920, 1080);
main->show();
GraphicsView->resize(main->width(), main->height());
return a.exec();
}
This will display the image coordinates of the pixel under the mouse relative to (0,0) at the top left corner.
The mouse is not visible in these screenshots but in the first it is in exactly the upper left corner and the second it is in exactly the lower right corner. If these coordinates are needed inside the Graphics_view_zoom object then you simply have to scope the Frame instance appropriately, or pass the value as needed.
Note - the exact coordinates displayed may not precisely represent the position of the mouse in this example since they are cast to ints for demonstration, but the floating point values can be easily accessed since QGraphicsSceneMoveEvent::scenePos() returns a QPointF. Additionally, note that in running this demonstration there may be some (hopefully very small) variation on where the mouse appears to be relative to it's 'actual' position - I recommend using Qt::CrossCursor to allay this. For example on my system the default cursor is off by about a pixel for certain areas on my smaller display, this is also affected by the zoom level - higher zoom will produce more accurate results, less zoom will be less accurate.

QTimer won't advance screen

I create my scene in my dialog.cpp and draw some QGraphicsItem's in my scene.cpp. When I add my QTimer to my dialog.cpp it makes whenever I move the cursor over the scene to crash.
dialog.cpp
#include "dialog.h"
#include "scene.h"
#include "ui_dialog.h"
#include "instructions.h"
#include "settings.h"
#include "highscore.h"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
// Create and configure scene
scene = new Scene;
scene->setBackgroundBrush(Qt::black);
scene->setItemIndexMethod(QGraphicsScene::NoIndex);
ui->graphicsView->setScene(scene);
scene->setSceneRect(-200, -150, 400, 300);
ui->graphicsView->setMouseTracking(true);
QPixmap tankbase1(":/images/tankbase.jpg");
ui->tankbaseplay1->setPixmap(tankbase1);
//\/\/\/This is my problem. And not sure why\/\/\/\/\/\/
// timer = new QTimer(this);
// QObject::connect(timer, SIGNAL(timeout()), scene, SLOT(advance()));
// timer->start(10);
}
Dialog::~Dialog()
{
delete ui;
}
//void Dialog::shoot()
//{
//}
void Dialog::on_startButton_clicked()
{
ui->settingsButton->hide();
ui->titlescreen->hide();
ui->highscoreButton->hide();
ui->instructionButton->hide();
ui->startButton->hide();
QGraphicsTextItem *FirstP;
QString P1 = "Player1";
FirstP = scene->addText(P1);
FirstP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
FirstP->setDefaultTextColor(Qt::white);
FirstP->setPos(-300, -220);
QGraphicsTextItem *SecondP;
QString P2 = "Player2";
SecondP = scene->addText(P2);
SecondP->setFont(QFont("Nimbus Mono L", 12,QFont::Bold));
SecondP->setDefaultTextColor(Qt::white);
SecondP->setPos(230, -220);
}
void Dialog::on_instructionButton_clicked()
{
Instructions intDialog;
intDialog.setModal(true);
intDialog.exec();
}
void Dialog::on_settingsButton_clicked()
{
settings intDialog;
intDialog.setModal(true);
intDialog.exec();
}
void Dialog::on_highscoreButton_clicked()
{
highscore intDialog;
intDialog.setModal(true);
intDialog.exec();
}
scene.cpp
#include "scene.h"
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsSceneMouseEvent>
#include <QTimer>
#include "qmath.h"
#include <math.h>
class GraphicsCircle : public QGraphicsEllipseItem
// class for the fire bullets
{
public:
GraphicsCircle(qreal dirx, qreal diry)
: m_Speed(3)
, m_DirX(dirx)
, m_DirY(diry)
{
setRect(-3.0,-3.0,8.0,8.0);
setPos(-195, 130);
QRadialGradient rGrad( 0.0, 0.0, 20.0, 0.0, 0.0);
rGrad.setColorAt(0.0, QColor(255,255,255));
rGrad.setColorAt(0.7, QColor(255,255,225));
rGrad.setColorAt(1.0, QColor(255,0,0,0));
setBrush(QBrush(rGrad) );
setPen(QPen(Qt::NoPen));
}
virtual ~GraphicsCircle() {}
void advance(int phase)
{
if(!phase) return;
setPos(x()+m_Speed*m_DirX, y()+m_Speed*m_DirY);
}
private:
qreal m_Speed;
qreal m_DirX;
qreal m_DirY;
};
Scene::Scene() : QGraphicsScene()
{
// added the lines below to setup an item, pointing in the positive x direction
int x1 = 0;
int y1 = 0;
cannon = new QGraphicsLineItem(x1, y1, x1 + 50, y1);
cannon->setPen(QPen(Qt::white, 6));
this->addItem(cannon);
cannon->setPos(-195, 130);
//Create bullets
m_FireTimer= new QTimer();
QObject::connect(m_FireTimer, SIGNAL(timeout()), this, SLOT(fire()));
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *e)
{
m_FireTarget = e->scenePos();
m_FireTimer->start();
QGraphicsScene::mousePressEvent(e);
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *e)
{
// emit mouseMoving(e->scenePos());
// FirstPlayer->setPos(e->scenePos());
// qAtan2(cannon->pos(), e->scenePos());
m_FireTarget = e->scenePos();
QGraphicsScene::mouseMoveEvent(e);
QLineF arm(cannon->pos(), e->scenePos());
cannon->setRotation(360 - arm.angle());
}
void Scene::mouseReleaseEvent ( QGraphicsSceneMouseEvent * e )
{
m_FireTimer->stop();
QGraphicsScene::mouseReleaseEvent(e);
}
void Scene::fire()
// creates a fire bullet
// the bullet will move in the direction of the mouse cursor
// the trajectory is sligthly perturbated by a random small angle
{
qreal dirx = m_FireTarget.x()-195;
qreal diry = m_FireTarget.y()-195;
qreal length = sqrt(dirx*dirx+diry*diry);
if (length!=0)
{
// normalized direction vector
qreal invLength= 1.0/length;
dirx *= invLength;
diry *= invLength;
// creating an angle perturbation of +/- 3°
qreal alphaPerturbation = static_cast<qreal>(qrand()%6-3) * M_PI / 180.0;
qreal xPerturbation = cos(alphaPerturbation);
qreal yPerturbation = sin(alphaPerturbation);
dirx = dirx*xPerturbation - diry*yPerturbation;
diry = diry*xPerturbation + dirx*yPerturbation;
GraphicsCircle * circle = new GraphicsCircle(dirx, diry);
addItem(circle);
}
}
void Scene::advance()
{
// first remove the pellet out of the sceneRect
for (int i=0; i<items().count(); ++i)
{
QGraphicsItem * item = items().at(i);
qreal x= item->x();
qreal y= item->y();
qreal sx=sceneRect().width();
qreal sy= sceneRect().height();
if ( (x < 0.0) || (y < 0.0) || (x > sx) || (y > sy))
{
removeItem(item);
delete item;
}
}
QGraphicsScene::advance();
}
When I run the code without the QTimer code in dialog.cpp it runs and my QGraphicsItems are displayed and move accordingly. When I add the QTimer the QGraphicsItem disappears. Completely lost to what the issue is.
Also I have taken the scene code and ran it separately and it works. The only difference is the scene and QTimer is created in main.cpp.
Help greatly needed!!!!!
You are iterating through a list of items while you are deleting items in the list. That sounds like trouble.
http://qt-project.org/doc/qt-5/qrect.html#intersects
http://qt-project.org/doc/qt-5/qgraphicsitem.html#boundingRect
http://qt-project.org/doc/qt-5/qrectf.html#contains
I think this might be a little cleaner in your advance() function.
QList <QGraphicsItem *> itemsToRemove;
foreach( QGraphicsItem * item, this->items() )
{
if( !this->sceneRect().intersects(item->boundingRect()) )
{
// The item is no longer in the scene rect, get ready to delete it
itemsToRemove.append(item);
}
}
foreach( QGraphicsItem * item, itemsToRemove )
{
this->removeItem(item);
delete(item);
}
Also reading the description of QGraphicsScene,
http://qt-project.org/doc/qt-5/qgraphicsscene.html#details
there a number of helper methods that can make finding items in an area or one item colliding with another, much easier.
Hope that helps.

QLabel positioning woes

I have made the following basic image viewer in Qt, however it does not behave as expected and for the love of what is holy, I cannot find out what the problem is. Basically, it's a QLabel for which I have set the movie as a jpeg/gif and then resized and centered it on the screen. The problem is, when it starts up, it neither resizes nor centers it on the screen, but instead it fills the window and stretches the image.
example:http://i.imgur.com/6l9wA.jpg
If I put it in the QVBoxLayout, it's the same thing. However, if I perform a scaling/zoom (by pressing "+" or "-", as defined in the keyRelease event), it centers the image and resizes it perfectly.
example:http://i.imgur.com/jGeUV.png
So the problem only seems to be when it loads the image, but I can't find out where. I know it must be something simple, but I've searched the Qt docs, google, tried combinations of commenting lines and adding different functions and I've come up with nothing.
By the way, the output from the bottom of the loadImage function prints the correct size and position, but it doesn't seem to respect them.
#include <QtGui>
#include "imageviewer.h"
ImageViewer::ImageViewer(QString imagePath)
{
setWindowTitle(imagePath + " - Simple Image Viewer");
currentImageSize=new QSize(0,0);
fillScreen();
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Base);
imageLabel->setScaledContents(true);
QVBoxLayout* layout=new QVBoxLayout();
layout->addWidget(imageLabel);
QFrame* frame=new QFrame();
frame->setLayout(layout);
//setCentralWidget(frame);
setCentralWidget(imageLabel);
loadImage(imagePath);
}
void ImageViewer::loadImage(QString imagePath){
currentImagePath=imagePath;
open(imagePath);
fitImage();
centerImage();
QTextStream out(stdout) ;
//for debugging:
out << QString("size: %1 %2 pos: %3 %4")
.arg(imageLabel->width())
.arg(imageLabel->height())
.arg(imageLabel->x())
.arg(imageLabel->y());
}
void ImageViewer::open(QString imagePath){
if (!imagePath.isEmpty()) {
QImage image(imagePath);
currentImageSize=new QSize(image.size().width(),image.size().height());
if (image.isNull()) {
QMessageBox::information(this, tr("Image Viewer"),
tr("Cannot load %1.").arg(imagePath));
return;
}
imageLabel->resize(currentImageSize->width(),
currentImageSize->height());
QMovie* movie=new QMovie(imagePath);
imageLabel->setMovie(movie);
movie->start();
scaleFactor = 1.0;
imageLabel->adjustSize();
}
}
void ImageViewer::fitImage(){
int windowHeight, windowWidth;
int imageHeight, imageWidth;
windowHeight = this->height();
windowWidth = this->width();
imageHeight = currentImageSize->height();
imageWidth = currentImageSize->width();
if(imageHeight > windowHeight || imageWidth > windowWidth){
imageLabel->resize((windowHeight-40)*imageWidth/imageHeight,
windowHeight-40);
}
}
void ImageViewer::centerImage(){
int windowHeight, windowWidth;
int imageHeight, imageWidth;
windowHeight = this->height();
windowWidth = this->width();
imageHeight = imageLabel->height();
imageWidth = imageLabel->width();
int x,y;
x=(windowWidth-imageWidth)/2;
y=(windowHeight-imageHeight)/2;
imageLabel->move(x,y);
}
void ImageViewer::fillScreen(){
this->showMaximized();
}
void ImageViewer::scaleImage(double factor)
{
double newScale = scaleFactor + factor;
if(newScale>MAX_SCALE || newScale<MIN_SCALE){
return;
}
else{
scaleFactor=newScale;
}
QTextStream out(stdout);
imageLabel->resize(scaleFactor * currentImageSize->width(),
scaleFactor * currentImageSize->height());
out<< scaleFactor << " "
<< imageLabel->height() << ","
<< imageLabel->width() <<endl;
centerImage();
}
void ImageViewer::keyReleaseEvent(QKeyEvent *event){
if(event->key()==Qt::Key_Plus){
scaleImage(SCALE_STEP);
}
if(event->key()==Qt::Key_Minus){
scaleImage(0-(SCALE_STEP));
}
}
for which the header is:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QLabel>
#include <QMainWindow>
class ImageViewer : public QMainWindow
{
Q_OBJECT
public:
ImageViewer(QString imagePath);
void open(QString imagePath);
void loadImage(QString imagePath);
private:
void fitImage();
void centerImage();
void fillScreen();
void scaleImage(double);
void adjustScrollBar(QScrollBar*,double);
void keyReleaseEvent(QKeyEvent *);
private:
QLabel* imageLabel;
double scaleFactor;
QSize* currentImageSize;
QString currentImagePath;
QString currentDir;
QStringList neighbourImages;
static const double MAX_SCALE=2.0;
static const double MIN_SCALE=0.2;
static const double SCALE_STEP=0.1;
};
#endif // MAINWINDOW_H
EDIT: this is the difference between the QLabel after it first loads and the QLabel after zoomed in once: diffchecker.com/1uDcb83
I believe the problem is that you are trying to calculate your sizes in the main window constructor. These sizes are not established at this point. You need to override QWidget::resizeEvent() (in ImageViewer) and do your sizing calculations there. That function is called after the geometry for the widget has been set.