Tinting a QPixmap using QPainter (CompositionMode_Overlay)



  • I'm having some issues tinting a QPixmap.

    My code is:

          image.load(imagePath); // load the image
          QPainter *paint = new QPainter(&image);
          paint->setCompositionMode(QPainter::CompositionMode_Overlay); // I want the box to blend into the image
          QRectF rectangle(0.0, 0.0, 45, 45); // the rectangle
          color.setAlpha(brightness); // I am setting the alpha so that the colour is actually visible
          paint->fillRect(rectangle, color); // this is the issue - it changes the alpha on the image, I want the alpha 255 pixels to be coloured in, not alpha 0.
          delete paint;
          color = colors;
          brightness = brightnessSlider;
          return image;
    

    This is my source image: http://i.imgur.com/ZkcYJfg.png
    This is the result: http://i.imgur.com/gPKbY9l.png (please ignore the size difference)

    The problem is that the source image has a transparent background (alpha 0), and my sprite, has pixels which are opaque (alpha 255). However, the overlay mode ignores this, and decides to tint all of my source image.
    The mode 'CompositionMode_Atop' sort of works, however it does not have the same desired effect as the Overlay mode.

    Ideally, I would like the result to have a transparent background, and also to be tinted using the Overlay mode.

    Thanks, VOT.


  • Qt Champions 2017

    @Votato said:
    Hi
    Maybe you have tried it, but i wonder if you could use the
    http://doc.qt.io/qt-4.8/qimage.html#createAlphaMask
    with painter setClipRegion
    so you only affect the area you want.
    Like here:
    http://stackoverflow.com/questions/7539257/how-to-use-a-mask-with-qpainter


  • Moderators

    You can do this in two steps:

    QPainter p;
    
    QImage img(imagePath);
    QImage mask(img);
    
    p.begin(&mask);
    p.setCompositionMode(QPainter::CompositionMode_SourceIn);
    p.fillRect(rect, color);
    p.end();
    
    p.begin(&img);
    p.setCompositionMode(QPainter::CompositionMode_Overlay);
    p.drawImage(0, 0, mask);
    p.end();
    
    return img;
    

    Btw. Don't create painters on the heap. Dynamic allocation is slower than stack values. It also requires lifetime management (delete) which makes your code unnecessary complex.



  • This is an old thread, but still comes up in related Google searches.

    Chris Kawa's answer is fine for most cases, but doesn't work if the tint color itself has alpha (eg. if you want to make the original image more transparent). It's bananas to me that Qt doesn't have a composition mode that multiplies the alpha channels together.

    Given that, the simplest and most efficient approach is probably to multiply the pixel colors manually using a QImage (QPixmap can be converted to/from a QImage).

    Here's the code for my approach:

    ////////////////////////////////////////////////////////////////////////////////
    // Multiplies all pixels in the given image by the specified color
    //	- Alpha channel is also multiplied
    //	- The images format is converted if it is not Format_ARGB32 or Format_ARGB32_Premultiplied
    //		- FUTURE: Support additional format types without conversion if needed
    void TintImage(QImage& inoutImage, const QColor& tintColor)
    {
    	if (tintColor == Qt::white)
    		return;
    
    	// Convert to 4-channel 32-bit format if needed
    	auto format = inoutImage.format();
    	if (format != QImage::Format_ARGB32 && format != QImage::Format_ARGB32_Premultiplied)
    	{
    		format = QImage::Format_ARGB32_Premultiplied;
    		inoutImage = inoutImage.convertToFormat(format);
    	}
    
    	const bool isPremultiplied = (format == QImage::Format_ARGB32_Premultiplied);
    	const auto tint = tintColor.rgba();
    
    	// Convert scanline by scanline (a bit tricker than using setPixelColor, but much more efficient)
    	const int sizeX = inoutImage.width();
    	const int sizeY = inoutImage.height();
    	for (int y = 0; y < sizeY; ++y)
    	{
    		// Note: Qt documentation explicitly recommends this cast for 32-bit images
    		auto* scanline = (QRgb*)inoutImage.scanLine(y);
    		for (int x = 0; x < sizeX; ++x)
    		{
    			auto color = scanline[x];
    			if (isPremultiplied)
    				color = qUnpremultiply(color);
    
    			color = qRgba(
    				(qRed(color) * qRed(tint)) / 255
    				, (qGreen(color) * qGreen(tint)) / 255
    				, (qBlue(color) * qBlue(tint)) / 255
    				, (qAlpha(color) * qAlpha(tint)) / 255
    			);
    
    			if (isPremultiplied)
    				color = qPremultiply(color);
    
    			scanline[x] = color;
    		}
    	}
    }
    

  • Moderators

    It's bananas to me that Qt doesn't have a composition mode that multiplies the alpha channels together.

    Of course it does. At least couple of modes do that. But they do it in a more sensible way.

    Multiplying values channel wise is a very weird operation when you talk about colors and I haven't seen it in any graphics API. You could argue that multiplying alphas has some sense in some situations, but color channels? To put it simply: what does "red times red" mean? That's the bananas. You might be getting something that you like by accident but it has little sense computationally. In most common composition modes you mix color channels with alpha values in one form or another.

    eg. if you want to make the original image more transparent)

    If you want to apply source alpha to the destination you would multiply the source alpha channel with the destination color channels, not its alpha channel alone. That's what for example QPainter::CompositionMode_DestinationIn mode does.



Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.