Accelerating color gradients

While looking for ways to improve the KDE color selection dialog, I stumbled upon the slow rendering of the hue/saturation color gradient. Using a simple trick such gradients can be rendered using hardware facilities.

The goal is to render a two-dimensional gradient as depicted below. The current implementation allocates a properly sized QImage, and computes the RGB color values for each individual pixel.

Hue/saturation two-dimensional gradient
Hue/saturation two-dimensional gradient

This requires, however, nearly hundred thousand conversions from HSV to RGB color values, and is the primary cause for the slowness of the dialog.

When looking at the resulting RGB values, you will notice that each row (“scanline”) is actually a one-dimensional linear gradient, so it could be rendered using QLinearGradient.

Looking further, you will also see that there is a linear gradient in each column. Such a gradient is called “bilinear”, but Qt does not offer a two-dimensional QGradient class.

The trick is to create this bilinear gradient by scaling up a very small QImage. The image you see above is actually only 7×2 pixels large. The image is then rendered to a larger QPixmap with scaling applied, so that possible hardware scaling can be used.

A requirement is that pixmap scaling supports bilinear interpolation. This mode, however, is even available with the software scaling algorithms built into Qt.


8 thoughts on “Accelerating color gradients”

  1. This may be obvious to you but why it is not possible to use a simple png to display this gradient ?

  2. @Heller, the gradient changes depending on the value in the slider next to it, so you would have to store 256 images (actually 1536, because there are 6 “modes” the gradient can be in).

  3. @Alex, the code is in KDE svn 🙂 It basically goes like this (not tested):

    QImage image(2, 2); // 4-point gradient
    image.setPixel(0, 0, QColor(Qt::black).rgb());
    image.setPixel(0, 1, QColor(Qt::green).rgb());
    image.setPixel(1, 0, QColor(Qt::red).rgb());
    image.setPixel(1, 1, QColor(Qt::yellow).rgb());

    QRect destRect(0, 0, 200, 200);
    painter.setRenderHint(QPainter::SmoothPixmapTransforms, true);
    painter.drawImage(destRect, image, QRectF(0.5, 0.5, 1, 1));

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s