[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