4

I'm migrating an application to Qt from MFC.

The MFC app would use GDI calls to construct the window (a graph plot, basically). It would draw to a memory bitmap back buffer, and then BitBlt that to the screen. Qt, however, already does double buffering.

When the user clicks and drags in the graph, I'd like that section of the window to be inverted.

I'd like to find the best way to do this. Is there a way to do something like grabWindow() that will grab from the widget's back buffer, not the screen? ... maybe a BitBlt(..., DST_INVERT) equivalent?

I saw setCompositionMode() in QPainter, but the docs say that only works on painters operating on QImage. (Otherwise I could composite a solid rectangle image onto my widget with a fancy composition mode to get something like the invert effect)

I could do the same thing as MFC, painting to a QImage back buffer... but I read that hardware acceleration may not work this way. It seems like it'd be a waste to reimplement the double buffering already provided to you in Qt. I'm also not so sure what the side effects of turning off the widget's double-buffering may be (to avoid triple-buffering).

At one point, I had a convoluted QPixmap::grabWidget() call with recursion-preventing flags protecting it, but that rendered everything twice and is obviously worse than just drawing to a QImage. (and it's specifically warned against in the docs)

Should I give up and draw everything to a QImage doing it basically like I did in MFC?

EDIT:

Okay, a QPixmap painter runs at approximately the same speed as direct now. So, using a QPixmap back-buffer seems to be the best way to do this.

The solution was not obvious to me, but possibly if I looked at more examples (like Ariya's Monster demo) I would have just coded it the way it was expected to be done and it would have worked just fine.

Here's the difference. I saw help system demos using this:

    QPainter painter(this)

in the start of paintEvent(). So, it seemed to naturally follow to me that to double buffer to a QPixmap then paint on the screen, you needed to do this:

    QPainter painter(&pixmap);
    QPainter painterWidget(this);
    ...  draw using 'painter' ...
    painterWidget.drawPixmap(QPoint(0,0), pixmap);

when in fact you are apparently supposed to do this:

    QPainter painter;
    painter.begin(&pixmap);
    ... draw using 'painter' ...
    painter.end();
    painter.begin(this);
    painter.drawPixmap(QPoint(0,0), pixmap);
    painter.end();

I can see that my way had two active painters at the same time. I'm not entirely sure why it's faster, but intuitively I like the latter one better. It's a single QPainter object, and it's only doing one thing at a time. Maybe someone can explain why the first method is bad? (In terms of broken assumptions in the Qt rendering engine)

darron
  • 4,284
  • 3
  • 36
  • 44
  • Your benchmark results are suspicious. Although rendering to an offscreen QPixmap is a bit slower (due to the extra overhead), it won't be as twice as slow. Did you paint straight to screen (skip Qt's backing store) to make the comparison fair? Did you use opaque paint event? Did you also try the raster and OpenGL graphics system? Care to share your benchmark code? – Ariya Hidayat May 24 '09 at 10:49
  • Well, I was assuming the back buffer in Qt was only wasting memory, as a blit from one memory image to another should be virtually instant.. ?? ... I'd disabled the back-buffer at one point, but not during the benchmark. I'll update with my modifications to benchmark. – darron May 25 '09 at 01:05
  • Setting paintOnScreen on both the main window and this widget didn't make any real difference, btw. I also was running debug code, but a re-run with release code made no difference at all. – darron May 25 '09 at 01:18
  • Knowing that X is not synchronous, I wonder which widget updating method did you use? Also, try setting Qt::WA_OpaquePaintEvent. To exclude inter-widget painting interaction, try one-top level widget only. If you did check out the monster demo I referred to, it is easy enough to change its painting routines to whatever you need and rerun the test. – Ariya Hidayat May 25 '09 at 19:54
  • I figured it out. There seems to be a quite large penalty to using two QPainters like I was doing, vs. a single painter with begin() & end() calls. I like the new method, but why was the old one so slow? – darron May 26 '09 at 04:22
  • QPainter::begin() and end() are not too expensive anymore if you use Qt 4.5. Otherwise, it's a bug if you already use 4.5 and the overhead is too big. – Ariya Hidayat May 26 '09 at 13:31
  • It's not the begin() and end() method that is slow, it's the two painter method. This is Qt 4.5.1. – darron May 26 '09 at 17:26
  • Multiple QPainter instances, especially since they are operating on different paint devices (pixmaps, widgets, images) are perfectly fine. This needs further investigation, could be even something weird inside Qt itself. – Ariya Hidayat May 26 '09 at 21:04

3 Answers3

4

Assuming you don't really want to pixel values from your offscreen buffer (but rather, just drawing something again on top of it and blit again to the screen), you should use QPixmap as the buffer, not QImage. Using the latter disables all painting acceleration as Qt falls back using its software raster engine, hence the use QPixmap. If you use OpenGL graphics system, you can still benefit from it.

For an example on how to do this, check my last code on running the Monster demo, it needs to have an offscreen pixmap to due the motion blur effect via repeated painting with source over composition mode.

To disable Qt's backing store (which is generally not a good idea), use the Qt::WA_PaintOnScreen for your top-level widget.

A bit unrelated, but you might want to have a look QRubberBand widget.

Ariya Hidayat
  • 12,523
  • 3
  • 46
  • 39
  • QRubberBand is nice, I hadn't seen that. However, on my display it literally draws the familiar dashed-line rubber band. I need at least some sort of transparent overlay. I wish there was a QRubberBand::SolidRectangle type! :) – darron May 22 '09 at 14:19
  • Since the fastest way currently seems to be double-rendering, and going against the docs (I promise not to complain if future Qt versions break my code! :) I *would* prefer some way to actually get pixel values from my offscreen buffer. Actually, that's by far more useful considering things I've done in the past compared to the way grabWindow() works now. – darron May 22 '09 at 14:43
  • If you want to stick with X11 paint engine, there is no efficient way to read back the pixels (too much X round trip and conversion). This is the window system limitation, not Qt. A workaround is to use the raster graphics system (at the cost of software painting for all routines), since then essentially QPixmap is using QImage as the back-end and thus allowing fast access to the raw pixels. – Ariya Hidayat May 24 '09 at 10:52
4

Drawing on top of the graph area you should be able to use composition modes to invert. Draw white using the Difference composition mode. The following example is a subclass of a QLabel showing a pixmap:

void Widget::paintEvent(QPaintEvent *pe)
{
    // make sure we paint background
    QLabel::paintEvent(pe);

    // paint the overlay
    if (!selectionRect.isNull()) {
        QPainter p(this);
        p.setCompositionMode(QPainter::CompositionMode_Difference);
        p.fillRect(selectionRect,QColor("#FFFFFF"));
    }
}

alt text http://chaos.troll.no/~hhartz/yesManInverted.png

Henrik Hartz
  • 3,677
  • 1
  • 27
  • 28
  • The docs for setCompositionMode() said you can only set composition modes for QImage-backed painters, so I didn't even try it until now. It appears the docs are right... I get a solid white rectangle. – darron May 22 '09 at 14:25
  • This project could easily end up on any one of the following platforms: Windows, Linux, Embedded Linux. Maybe the Mac back end handles the setCompositionMode() ? Is there some other option you set? – darron May 22 '09 at 14:32
  • I've downloaded and run your yesManInverted project, and I still get a solid white rectangle. I guess it is just differences in what the back ends support. – darron May 22 '09 at 14:35
0

The simplist, most straightforward answer I know of is to do it like you were doing before, to a QImage, and use the QImage as the source for your widget on the screen.

Another option might be to add a transparent widget over your graph, which only draws the inverted part of the graph. I don't think this will optimize the drawing at all, however. It will likely cause the underlying graph to be drawn, and then the overlay of the inverted part.

Caleb Huitt - cjhuitt
  • 14,785
  • 3
  • 42
  • 49