I created QPropertyAnimation and connected it to my SonogramWidget that scroll a long picture vertically on animation events. The 'long picture' is composed of 100 pre-calculated QPixmap objects 1024x128 placed one after another vertically. They displayed in SonogramWidget::paintEvent() with QPainter. Drawing procedure paint not all QPixmap at once, but only visible of them, considering widget height and current vertical offset. CPU is almost free, because QPixmap is a fastest way to display a picture. There is no big calculations during scrolling, because all the 100 QPixmaps are pre-calculated and stored in memory.
I see strange effect: pulsating movement: 2 times a second the entire image slightly speed-up and moves up by 1..2 pixels faster than usual motion. The same effect when i replace Qt Animation Framework with single 60 fps QTimer and scroll the image in its SLOT.
Video: http://www.youtube.com/watch?v=KRk_LNd7EBg#t=8 (watch from 00:08; My firefox adds more chopping to video playing itself, google chrome plays the video much better).
I see the same effect for my Linux and Windows build.
SOLUTION
i figured out the issue: the "chopping" was not a bug, it was a feature! It is a feature of integer-number calculations, so sometimes we had to have different numbers for animations, like: 16,16,16,16,16,16,17,16,16,16,16,16,17,....
In the paintEvent add the following assert:
Q_ASSERT(m_animation->currentValue() == m_animatedPropertyValue);
If it triggers, then you know you must use currentValue() instead of the property value. This might be the case. Let me know.
Related
I wrote a little application, which replaces the cursor with a hand drawn cursor. Therefor i used a QOpenGlWidget.
For the animation i use the frameSwapped signal:
connect(this, SIGNAL(frameSwapped()), this, SLOT(update()));
Till now i don't use any specific OpenGl function, so i just override the paintevent analog to a classic QWidget.
Qt documentation:
When performing drawing using QPainter only, it is also possible to perform the painting like it is done for ordinary widgets: by reimplementing paintEvent().
void Widget::paintEvent(QPaintEvent* event) {
// Draw Cursor
POINT LpPoint;
GetCursorPos(&LpPoint);
QPoint CursorPos(LpPoint.x, LpPoint.y);
CursorPos = mapFromGlobal(CursorPos);
QPainter Painter(this);
Painter.drawEllipse(CursorPos, 20, 20);
}
I filmed the result with 240 fps and recognized that my drawn cursor is 2 frames behind the windows cursor. It is not important, that there's no lag at all. But only one frame would be great. And it would be great if i would be able to quantify the lag. Such that i know it's one Frame +- the duration for rendering. I already read this, but i'm not quite familiar with OpenGl. And i don't think i can use this with a QOpenGlWidget. Maybe somebody has an idea how to decrease the lag to only one Frame.
Update 1:
I did some research and tried a lot with moderate success.
My latest version:
connect(this, SIGNAL(frameSwapped()), this, SLOT(animate()));
void Widget::animate() {
makeCurrent();
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
f->glFinish();
//std::this_thread::sleep_for(std::chrono::milliseconds(12));
update();
}
I use glFinish() to sync CPU and GPU. Now the drawn cursor is about one frame behind. Sometimes it is even better than one frame. But there are skipped frames, so the drawn cursor does not move at all. Overall there is no consistency. I still have some trouble understandig exactly how OpenGl updates. Maybe some more information: setIntervall is set to 1 and it is double buffered. I think maybe it is a problem not know what Qt exactly does. Calling update only schedules an update and i don't have controll on how the buffers are swaped. Maybe somebody has more experience on these issues. I can say that the paintevent/paintGL is called every 16 ms. I added std::this_thread::sleep_for(std::chrono::milliseconds(12)); to have less delay between render and the actual mouse position. That helps to reduce the average latency. But for me it is nearly impossible to make a solid prediction on what will happen in the next frame.
Update 2:
I posted a similar issue on Qt forum. Even though it's not a direct answer, it contains some helpfull information.
Has anyone figured out how to display smooth video (i.e. a series of bitmaps) in a FireMonkey application, HD or 3D? In VCL you could write to a canvas from a thread and this would work perfectly, but this does not work in FMX. To make things worse, the apparently only reliable way is to use TImage, and that seems to be updated from the main thread (open a menu and video freezes temporarily). All EMB examples I could find all either write to TImage from the main thread, or use Synchronize(). These limitations make FMX unusable for decent video display so I am looking for a hack or possibly bypass of FMX. I use XE5/C++ but welcome any suggestions. Target OS is both Windows 7+ & OS X. Thanks!
How about putting a TPaintbox on your form to hold the video. In the OnPaint method you simply draw the next frame to the paintbox canvas. Now put a TTimer on the form, set the interval to the frame rate required. In the OnTimer event for the timer just write paintbox1.repaint
This should give you regular frames no matter what else the program is doing.
For extra safety, you could increment a frame number in the OnTimer event. Now in the paintbox paint method you know which frame to paint. This means you won't jump frames if something else calls the paint method as well as the timer - you will just end up repainting the same frame for the extra call to OnPaint.
I use this for marching ants selections although I go one step further and use an overlaid canvas so I can draw independently to the selection and the underlying paintbox canvas to remove the need to repaint the main canvas when the selection changes. That requires calls to API but I guess you won't need it unless you are doing videos with a transparent colour.
Further research, including some talks with the Itinerant developer, has unfortunately made it clear that, due to concurrency restrictions, FM has been designed so that all GPU access goes through the main thread and therefore painting will always be limited. As a result I have decided FM is not suitable for my needs and I am re-evaluating my options.
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 :)
I'm thinking like text fade in and slide effects. I imagine implementing this would be rather trivial and plan to do so myself, but wanted to make sure I'm not reinventing the wheel first. If it doesn't exist then I'm looking on advice on the best way to implement these.
The 2 things I'm looking to do are fade in text and have the window slide down when resizing, eg if I show a label that was previously hidden it would slide down ~20 pixels instead of just instantly growing 20 pixels larger.
The way I was thinking to implement the first one is, assuming it's possible, get the window/bg color and start it at that and transition it to the font color, if there's alpha channel support that would be even simpler to do (I'm not sure if there is since I haven't messed with colors yet). To do this I'd just choose a transition time period and process it with a for loop or something once the color increments have been determined.
Similarly to do the window transitions I would get the height of the change (not sure how to do that yet), determine the increments of change based on the transition time and in a for loop gradually adjust the size. Sorry if I didn't explain those very clear, I'm trying to get this in before I go to work and figure most of you will know what I'm trying to explain. As always thanks for the help!
For window resize transition effect, QPropertyAnimation may be the easiest to do since height is a widget property. Fading text might work the same way if the foreground color can be coerced into a property.
I have a QGraphicsView for a very wide QGraphicsScene. I need to draw the background in drawBackground() and the background is a bit complicated (long loop) although it doesn't need to be repainted constantly. I store it in a static QPixmap (I tried QImage too) inside the function drawBackground() and that pixmap is what I draw onto the painter of the view. Only when needed is the QPixmap painted on again.
If I didn't use a static pixmap, the complicated background would be generated every time I scroll sideways for example. The problem is that apparently there is a maximum width for pixmaps on Windows, on my computer it's 32770. I could store a list of pixmaps and draw them side by side but it would make the code uglier and I also don't know what the maximum width of a pixmap is for every Windows machine. Since this might be a well-known problem I was wondering if anyone has a better solution.
Thanks.
You can probably avoid the windows limit by using unaccelerated raster paint device, but 32770*1024 is 100MiB of pixmap; you probably don't want to do that even if Windows would let you.
You've already thought of the usual answer (tile it in more reasonably-sized chunks and load/generate them on demand). The other piece of the usual solution is to use something like QPixmapCache to keep the recently-used tiles so you don't regenerate them too often (only when the user scrolls a long way).
You didn't say how complex your complex background is, but you might also want to look at the Mandelbrot set example for how to do piecewise rendering of an (infinitely) large background pixmap on-demand, without blocking the UI.
This is the common use case for the tiling pattern. Basically you split the background into small images.
I'm not sure why you think "it would make the code uglier". It is certainly not a one-liner. Depending whether you have fixed size background image or not, the tiling code is usually pretty straightforward.