Qgraphicview resizing when windows resizing - c++

I would like to ask you about an issue I do not succeed to fix.
I have a QApplication which load an image in the first mainwindow.
The code is the following:
QGraphicsScene *scene = new QGraphicsScene;
QPixmap pixmap(QString::fromStdString("image.png");
scene->addPixmap(pixmap);
ui->graphview->setScene(scene);
ui->graphview->show();
I am trying to fit the image to scale in the QGraphivsView, even when I resized my windows.
However, the image is displayed with its own size at running time and this size is not changing when windows is resized. For example, increasing my windows does not increase the image size and similarly for decreasing.
I tried even by addind the following code:
ui->graphview->fitInView(pixmap, Qt::KeepAspectRatioByExpanding);
But nothing is working.
I provide you an example on the following image of what is happening.

I have found a way however, it is not 100% satisfying.
I have replaced the resizeEvent function as on the example below:
void QMainWindows::resizeEvent(QResizeEvent *){
QRectF bounds = ui->graphQSYS->scene()->sceneRect();
ui->graphQSYS->fitInView(bounds, Qt::KeepAspectRatioByExpanding);
ui->graphQSYS->centerOn(bounds.center());
}
However, when resizing, the image quality is becoming really bad. Text written on it cannot be read anymore. Do you know another way to keep picture quality ?
Thank you very much.

Related

Painting with rotated QPainter clips to incorrect region of QImage

I have a QImage that represents a blank piece of paper and a QPainter, which is used to paint onto this image.
Sometimes, I will want to rotate/translate the QPainter before any paint operations, in order to paint onto this image in "Landscape" orientation.
Here is a simplified snippet of the code:
_image = new QImage(paperRect().size(), QImage::Format_RGB888);
_painter->begin(_image);
if (_orientation == QPrinter::Landscape)
{
_painter->translate(0, _image->height());
_painter->rotate(270);
}
// Painting operations here.
Unfortunately, this is not working as I had expected. It seems that even though the painter has been rotated, it is unaware of the "new" bounds it can paint within, thereby clipping to the "Portrait" size.
I have tried the following to no avail: Turning off clipping (_painter->setClipping(false);), setting a new clip rect (_painter->setClipRect(0, 0, _image.height(), _image.width());), and adjusting the window and viewport in various ways.
I have looked through the documentation of QPainter and QImage, and scoured the internet, but I haven't found this particular issue discussed before.
As it turns out, the problem was unrelated to my posted code. Here is my solution, in case anyone runs into this issue in the future.
The problem originally came about during implementation of a custom QPrintEngine/QPaintEngine class. The code posted in the question works--however, I had forgotten to update the QPrintEngine::property() function to return the new dimensions corresponding to the PPK_PageRect and PPK_PaperRect keys, when the orientation was set to Landscape.
Note that the QPrintEngine::metric() function does not appear to need to be updated in this way (in my project). I'm assuming this is because the metric function is mostly used when the QPrinter that utilizes this QPrintEngine implementation is used as a paint device, and that never happens in my project.
In any case, fixing this issue allows the QImage to be properly painted on "sideways".

(Qt C++) Resize pixmap and KEEP pixelation?

In my project I have a QLabel that I change the pixmap frequently like this:
ui->frameLabel->setPixmap(slot_pic[blockId[currentSlot]][damageId[currentSlot]]);
slot_pic is simply a 2d map. So you can look at it clearer like this:
ui->frameLabel->setPixmap(pixmap);
The image is 16x16 in size and my label is 32x32. I have scaledContents checked so when the pixmap changes, the image is double in size. However, the image is now blurry. I understand why, but I was wondering if there is a way to make it stay pixelated. I want to just have a bigger pixelated image. (The image is from Minecraft if that helps you understand what I mean)
Thanks for your time :)
Don't let the QLabel do the scaling. Instead, do the scaling by yourself using QPixmap::scaled(). Something like this:
ui->frameLabel->setPixmap(
pixmap.scaled(32, 32, Qt::IgnoreAspectRatio, Qt::FastTransformation));
The important parameter is the last one, transformMode, which tells whether bilinear filtering is used or not.

How to effectively scroll 1024x90000 image in a window?

I have the following UI, where the sonogram (freq+time sound representation) is shown. So the image is not loaded from somewhere, it is drawn by QPainter while reading WAV file.
My current implementation is a single huge QImage object, where the image is drawn. And on paintEvent(), I draw part of the large QImage on the widget:
QPainter painter(this);
// (int, int, QImage*, int, int)
painter.drawImage(0, 0, *m_sonogram, 0, m_offset);
But, as i know, the QPixmap is optimized for displaying pixmaps on the screen, so should I convert the QImage to a QPixmap after the drawing of the sonogram is done?
Also, is it worth to keep large image as some kind of a linked list of separate QPixmap objects of smaller size and make paintEvent() smarter to operate on a list of smaller objects to avoid Qt's auto-cutting procedures and so on?
When my QImage is large enough, each paintEvent() consuming a lot of CPU.
All kinds of advices are welcome :)
Yes, in my limited experience of Qt app development, if you have a static image (or an infrequently updated image) it's well worth (for performance purposes) creating a QPixmap from it and keeping it around to use via QPainter::drawPixmap in your paintEvent handler.
However, I've never tried doing this with anything larger than about 4Kx4K images, so whether it will work for your enormous image or fall over horribly when you start to stress your graphics memory I couldn't say. I'd certainly try it out before considering adding a complicated tiling system.

How to show part of an image using QT?

So, this is my problem: I have this very big image, and I want to show only a specific part of it. After the user pressing a specific key I want the image to move, showing another part of it. The transition from one part of the image to another have to be smooth, animated.
I tried using a QLabel to show the image but it always shows the center of the image, and I do not really know how to make the animation. What would you guys suggest?
Interesting question. Here is something I just tested and seems to work.
Add a QGraphicsView with dimensions the dimensions of the part of the image you want to display, eg 100x100. Create a QGraphicsScene and add it to the view:
QGraphicsScene* pScene = new QGraphicsScene(this);
ui->graphicsView->setScene(pScene);
Now add your image into the scene. In my case I has an image in my resource file. The trick is to set the sceneRect to the position you want to display. I wanted to display an 100x100 part of the image starting from 0,300 :
pItem = pScene->addPixmap(QPixmap::fromImage(QImage(":/photos/image")));
pScene->setSceneRect(0,300,100,100);
In order to test the smooth moving I added a button which when clicked is triggering a slot called move. This slot simply updates the sceneRect. In my simple example I just move the image 100 pixels right. In a real world scenario you could also move it diagonally or vertically and check the image limits.
void move()
{
for (unsigned i=currentX; i<currentX + 100; i++)
{
ui->graphicsView->scene()->setSceneRect(i,300,100,100);
qApp->processEvents();
}
currentX += 100;
}
Notice the currentX variable. It is nothing more than the last image position. Also we must call the processEvents in order to "see" the image moving smoothly.
You could use QPixmap::copy( int x, int y, int width, int height ) to copy a region of the image and display that.
Few options:
Try using a Q3CanvasSprite (within in a Q3Canvas). It is designed more for splitting one image into multiple ones but not for animating between them. You could try abusing it and declaring (say) 100 frames (10 per digit, which would be used as animation steps) or just use the move() method.
Try QGraphicsPixmapItem::setOffset() (within a QGraphicsScene). This might be overkill as QGraphicsScene is made for large number of images).
I'm not sure, but maybe this can be done with QStateMachine and QAbstractAnimation.

Tiling with QGraphicsScene and QGraphicsView

I'm creating a image visualizer that open large images(2gb+) in Qt.
I'm doing this by breaking the large image into several tiles of 512X512. I then load a QGraphicsScene of the original image size and use addPixmap to add each tile onto the QGraphic Scene. So ultimately it looks like a huge image to the end user when in fact it is a continuous array of smaller images stuck together on the scene.First of is this a good approach?
Trying to load all the tiles onto the scene takes up a lot of memory. So I'm thinking of only loading the tiles that are visible in the view. I've already managed to subclass QGraphicsScene and override its drag event thus enabling me to know which tiles need to be loaded next based on movement. My problem is tracking movement on the scrollbars. Is there any way I can create an event that get called every time the scrollbar moves. Subclassing QGraphicsView in not an option.
QGraphicsScene is smart enough not to render what isn't visible, so here's what you need to do:
Instead of loading and adding pixmaps, add classes that wrap the pixmap, and only load it when they are first rendered. (Computer scientists like to call this a "proxy pattern"). You could then unload the pixmap based on a timer. (They would be transparently re-loaded if unloaded too soon.) You could even notify this proxy path of the current zoom level, so that it loads lower resolution images when they will be rendered smaller.
Edit: here's some code to get you started. Note that everything that QGraphicsScene draws is a QGraphicsItem, (if you call ::addPixmap, it's converted to a ...GraphicsItem behind the scenes), so that's what you want to subclass:
(I haven't even compiled this, so "caveat lector", but it's doing the right thing ;)
class MyPixmap: public QGraphicsItem{
public:
// make sure to set `item` to nullptr in the constructor
MyPixmap()
: QGraphicsItem(...), item(nullptr){
}
// you will need to add a destructor
// (and probably a copy constructor and assignment operator)
QRectF boundingRect() const{
// return the size
return QRectF( ... );
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget){
if(nullptr == item){
// load item:
item = new QGraphicsPixmapItem( ... );
}
item->paint(painter, option, widget);
}
private:
// you'll probably want to store information about where you're
// going to load the pixmap from, too
QGraphicsPixmapItem *item;
};
then you can add your pixmaps to the QGraphicsScene using QGraphicsScene::addItem(...)
Although an answer has already been chosen, I'd like to express my opinion.
I don't like the selected answer, especially because of that usage of timers. A timer to unload the pixmaps? Say that the user actually wants to take a good look at the image, and after a couple of seconds - bam, the image is unloaded, he will have to do something in order the image to reappear. Or may be you will put another timer, that loads the pixmaps after another couple of seconds? Or you will check among your thousand of items if they are visible? Not only is this very very irritating and wrong, but that means that your program will be using resources all the time. Say the user minimizes you program and plays a movie, he will wonder why on earth my movie is freezing every couple of seconds...
Well, if I misunderstood the proposed idea of using timers, execuse me.
Actually the idea that mmutz suggested is better. It reminded me of the Mandelbrot example. Take a look at it. Instead of calculating what to draw you can rewrite this part to loading that part of the image that you need to show.
In conclusion I will propose another solution using QGraphicsView in a much simpler way:
1) check the size of the image without loading the image (use QImageReader)
2) make your scene's size equal to that of the image
3) instead of using pixmap items reimplement the DrawBackground() function. One of the parameters will give you the new exposed rectangle - meaning that if the user scrolls just a little bit, you will load and draw only this new part(to load only part of an image use setClipRect() and then read() methods of the QImageReader class). If there are some transformations you can get them from the other parameter(which is QPainter) and apply them to the image before you draw it.
In my opinion the best solution will be to combine my solution with the threading shown in the Mandelbrot example.
The only problem that I can think of now is if the user zooms out with a big scale factor. Then you will need a lot of resources for some time to load and scale a huge image. Well I see now that there is some function of the QImageReader that I haven't tried yet - setScaledSize(), which maybe do just what we need - if you set a scale size and then load the image maybe it won't load first the entire image – try it. Another way is just to limit the scale factor, a thing that you should do anyway if you stick to the method with the pixmap items.
Hope this helps.
Unless you absolutely need the view to be a QGraphicsView (e.g. because you place other objects on top of the large background pixmap), I'd really recommend just subclassing QAbstractScrollArea and reimplementing scrollContentsBy() and paintEvent().
Add in a LRU cache of pixmaps (see QPixmapCache for inspiration, though that one is global), and make the paintEvent() pull used pixmaps to the front, and be set.
If this sounds like more work than the QGraphicsItem, believe me, it's not :)