QIcon Recoloring: Best Approach?
-
I'm in a situation where I want to dynamically recolor all of the QIcons used according to
QPalette::ButtonText.
Not only does this include my custom icons, but also the icons applied to widgets/buttons by default,QStandardIcon, etc...Firstly, what is the best approach to recoloring existing icons?QGraphicsColorizeEffectmay not do what I need, since black will stay black.
Secondly, what is the best way to "intercept" these icons so I can do the above? Would I have to do someQProxyStyletrickery, or is there a better way?Edit: I have found a way to solve the first problem, and I have reimplemented QPixmapColorizeFilter's
draw(). Second question still remains.If you're curious (Googlers):
#include <QPainter> //maybe also include QPixmap and stuff static void grayscale(const QImage &image, QImage &dest, const QRect &rect = QRect()) { QRect destRect = rect; QRect srcRect = rect; if (rect.isNull()) { srcRect = dest.rect(); destRect = dest.rect(); } if (&image != &dest) { destRect.moveTo(QPoint(0, 0)); } const unsigned int *data = (const unsigned int *)image.bits(); unsigned int *outData = (unsigned int *)dest.bits(); if (dest.size() == image.size() && image.rect() == srcRect) { // a bit faster loop for grayscaling everything int pixels = dest.width() * dest.height(); for (int i = 0; i < pixels; ++i) { int val = qGray(data[i]); outData[i] = qRgba(val, val, val, qAlpha(data[i])); } } else { int yd = destRect.top(); for (int y = srcRect.top(); y <= srcRect.bottom() && y < image.height(); y++) { data = (const unsigned int *)image.scanLine(y); outData = (unsigned int *)dest.scanLine(yd++); int xd = destRect.left(); for (int x = srcRect.left(); x <= srcRect.right() && x < image.width(); x++) { int val = qGray(data[x]); outData[xd++] = qRgba(val, val, val, qAlpha(data[x])); } } } } // This is reimplemented qpixmapfilter.cpp behavior. // https://code.woboq.org/qt5/qtbase/src/widgets/effects/qpixmapfilter.cpp.html#946 void recolor(QPainter *painter, const QPixmap &src, QColor color, unsigned int strength) { // (Use this one) if (src.isNull()) return; // raster implementation QImage srcImage; QImage destImage; QRectF srcRect(0, 0, src.width(), src.height()); if (srcRect.isNull()) { srcImage = src.toImage(); const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; srcImage = std::move(srcImage).convertToFormat(format); destImage = QImage(srcImage.size(), srcImage.format()); } else { QRect rect = srcRect.toAlignedRect().intersected(src.rect()); srcImage = src.copy(rect).toImage(); const auto format = srcImage.hasAlphaChannel() ? QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32; srcImage = std::move(srcImage).convertToFormat(format); destImage = QImage(rect.size(), srcImage.format()); } destImage.setDevicePixelRatio(src.devicePixelRatioF()); // do colorizing QPainter destPainter(&destImage); grayscale(srcImage, destImage, srcImage.rect()); destPainter.setCompositionMode(QPainter::CompositionMode_Screen); destPainter.fillRect(srcImage.rect(), color); destPainter.end(); // alpha blending srcImage and destImage QImage buffer = srcImage; QPainter bufPainter(&buffer); bufPainter.setOpacity(strength); bufPainter.drawImage(0, 0, destImage); bufPainter.end(); destImage = std::move(buffer); // if (srcImage.hasAlphaChannel()) destImage.setAlphaChannel(srcImage.alphaChannel()); painter->drawImage(QPoint(0, 0), destImage); } // This is reimplemented QPixmapColorizeFilter behavior. // https://code.woboq.org/qt5/qtbase/src/widgets/effects/qpixmapfilter.cpp.html#1089 -
I don't quite understand what you mean with "intercept".
Below is a small method to re-size and re-color an icon, taken from its path.
It may not be the most elegant way but it works.
Curious to see optimization proposals ;-)QImage shape_icon(const QString &icon, const QColor &color, const qreal &strength=1.0, const int &w=40, const int &h=40) { // Resize icon and put it into QImage QImage src = QIcon(icon).pixmap(QSize(w,h)).toImage(); if(src.isNull()) return QImage(); // prepare graphics scene and pixmap QGraphicsScene scene; QGraphicsPixmapItem item; item.setPixmap(QPixmap::fromImage(src)); // create an effect with color and strength QGraphicsColorizeEffect effect; effect.setColor(color); effect.setStrength(strength); item.setGraphicsEffect(&effect); scene.addItem(&item); QImage res = src; QPainter ptr(&res); scene.render(&ptr, QRectF(), src.rect() ); return res; } -
Depending on the style, icons will be set on buttons (QDialogButtonBox, QMessageBox::StandardButtons, etc.). These icons are never guaranteed to match the "theme" that you have in place, and I'm wondering if there's a virtual method somewhere that would allow me to process the QIcon and return the processed. I've already come up with functionality to resize & reshape an icon, but I appreciate the suggestion.
My methodology takes code from a private class used byQGraphicsColorizeEffectcalledQPixmapColorizeFilter. I simply make a new pixmap from the icon at a certain size & run it through the recolor function, and as long as the accompanying QPainter works as it should then my pixmap will be replaced with the colorized one.Usage looks like this.
QIcon function() { QPixmap px = QIcon(/* whatever icon */).pixmap(/* whatever size */); QPainter p(&px); recolor(&p, px, QColor(/* whatever color */), 1 /* whatever strength */); return QIcon(px); }