Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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.


  • Lifetime Qt Champion

    @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.



  • It seems odd for a moderator to be so belligerent, and act so condescending when they don't even seem to understand the issue.

    Overlay mode would be useful for tinting e.g. icons with color, except it writes over transparent areas. To be useful, it should multiply the alpha of the source and destination. There's nothing sensible about Qt is doing.

    Geez.


  • Moderators

    @Matt-Chaput said:

    It seems odd for a moderator to be so belligerent, and act so condescending

    I'm sorry you feel that way but there's nothing I can do about it. I did try to provide a solution and explain why the other one was incorrect mathematically.

    Overlay mode would be useful for tinting e.g. icons with color, except it writes over transparent areas.

    That shortcoming of overlay mode is exactly the reason I suggested a two step approach instead. It breaks the problem into an alpha step and a color step.

    To be useful, it should multiply the alpha of the source and destination.

    If you wanted to do it in one step, yes. But, since there's no such mode in Qt, you can use the two step approach I suggested.

    composition mode

    First is the original.
    Second is the mask created using the alpha of the source (CompositionMode_SourceIn).
    Third is the final result using overlay with the mask (CompositionMode_Overlay).

    As far as I can tell this is the result OP wanted, so can you explain what I got wrong?

    There's nothing sensible about Qt is doing.

    Qt is providing a pretty common set of composition modes implemented by various APIs, described e.g. here, here and here. If you know of a graphics API that does SourceIn+Overlay in one step could you point us to it?