[Qt-interest] fastest way to draw everchanging pixels

Oliver.Knoll at comit.ch Oliver.Knoll at comit.ch
Mon Mar 23 16:26:12 CET 2009


Reto Glauser wrote on Saturday, March 21, 2009 2:17 PM:

> I'm porting a project from Java (Swing) to QT. I try to visualize a
> gas simulation and have to draw atoms and walls on the screen. I use
> 1 pixel per object, e.g. a blue pixel is an atom and a red pixel is a
> wall. Since this is a simulation I'm looking for the fastest way to
> draw everchanging pixels.    
> ...
>> void RenderArea::paintEvent(QPaintEvent *) {
>> 	QPainter painter(this);
>> 	painter.scale( scale, scale );
>> 
>> 	for( int i=0; i<height; i++ )
>> 		for( int j=0; j<width; j++ )
>> 			painter.drawPoint( ... );
>> }

As it seems you have already been given a blazing-fast solution for your problem: indeed, by calling one single method like "drawPoints" (which takes an *array* of pre-calculated points) it largely reduces the method-call overhead (with high level languages such as Java and C++/Qt people tend to blend out that function/method calls *are* expensive, even today - especially when it comes to calling methods 1000s of times, such as in your nested loop above ;)

Still, I would like to give you another approach which is still pixel-based and similar to your approach. But instead of callling a method for (potentially) each pixel, you do your own address arithmetic and calculate the pixel values. The basic idea is that you first draw into a QImage which gives you direct pixel access, using some easy pointer arithmetic, and then let the QPainter draw this QImage (which will automatically be converted to a QPixmap for fast drawing).

Note that I don't know which approach - QImage vs "drawPoints()" - is faster. I guess it also depends on the "atom/wall density". You have to find out for yourself.

Anyway, here we go (pseudo-code - debugging is left for the reader ;)

QImage *m_image;

RenderArea::RenderArea() {
  // allocate a drawing QImage with 32bit RGB (alpha set to 'ff')
  // note: you might want to consider other formats, see http://doc.trolltech.com/4.5/qimage.html#Format-enum
  m_image = new QImage(1280, 1024, QImage::Format_RGB32);
  ...
}

RenderArea::~RenderArea() {
  // deallocate resources
  delete m_image;
  ...
}

void RenderArea::paintEvent (QPaintEvent *paintEvent) {
  Q_UNUSED(paintEvent);
  QPainter painter(this);
  painter.scale(scale, scale);
  int x, y;

  // fill with black (or your colour of choice)
  m_image->fill(0xff000000);
  for (y = 0; y < 1024; ++y) {
    // get the current scan line (1)
    uchar *scanLine m_image->scanLine(y);
    for (x = 0; x < 1280; ++x) {
      // some pointer arithmetic, as to access the x-th pixel in the current scanline
      // note: every pixel is 32bit "wide", so you need to increment your pointer in "int" steps
      // this code is probably wrong, but you get the idea
      if (isAtom(x, y)) {
        *((uint32)scanLine + x) = Qt::red;
      } else if (isWall(x, y)) {
       *((uint32)scanLine + x) = Qt::green;
      }
    }
  }

  // finally draw the image
  painter.drawImage(m_image, ...);
}

(1) Note: the method scanLine() takes "byte padding" into account: the data is aligned on a 32bit boundary. Since this is the case for your image (its a 32bit per pixel image), you could even operate on the whole data chunk, which is accessible with http://doc.trolltech.com/4.5/qimage.html#bits, hence even avoiding the method calls to scanLine() :)

Another slight optimisation could be that you disable "auto fill" of your QWidget: http://doc.trolltech.com/4.5/qwidget.html#autoFillBackground-prop, assuming that the image draws the entire widget space - and in the above example you are already clearing the QImage.

Cheers, Oliver
-- 
Oliver Knoll
Dipl. Informatik-Ing. ETH
COMIT AG - ++41 79 520 95 22



More information about the Qt-interest-old mailing list