My application has different color modes. Therefore, I need to render my icons in different colors.
My icons are grayscale SVGs. Each color mode defines two colors for icons. One color shall replace black, the other shall replace white.
The code I use to implement this is as follows:
struct Gradient {
QRgb a, b;
/// index in [0, 255]. alpha from mask modifies dest alpha.
QRgb at(int index, int alpha) const {
const int rindex = 255 - index;
return qRgba(
(qRed(a) * rindex + qRed(b) * index) / 255,
(qGreen(a) * rindex + qGreen(b) * index) / 255,
(qBlue(a) * rindex + qBlue(b) * index) / 255,
(((qAlpha(a) * rindex + qAlpha(b) * index) / 255) * alpha) / 255
);
}
};
class Icon {
public:
explicit Icon(const QString &path): path{path} {}
/// must set once before rendering pixmaps.
/// generates a mask image from the source SVG with the given size.
void setSize(QSize size) {
if (mask.size() != size) {
QSvgRenderer renderer(path);
if (renderer.isValid()) {
mask = QImage(size, QImage::Format_ARGB32_Premultiplied);
mask.fill(Qt::transparent);
QPainter svgPainter(&mask);
renderer.render(&svgPainter);
}
}
}
/// renders a pixmap that uses primary where the mask is black,
/// secondary where the mask is white. Mask defines transparency.
QPixmap paint(const QColor &primary, const QColor &secondary) const {
QImage buffer(mask.size(), QImage::Format_ARGB32_Premultiplied);
Gradient gradient{primary.rgb(), secondary.rgb()};
for (int y = 0; y < mask.height(); ++y) {
const QRgb *srcLine = reinterpret_cast<const QRgb*>(mask.constScanLine(y));
QRgb *destLine = reinterpret_cast<QRgb*>(buffer.scanLine(y));
for (int x = 0; x < mask.width(); ++x) {
const QRgb &src = srcLine[x];
// using red as indicator for the grayscale color.
destLine[x] = gradient.at(qRed(src), qAlpha(src));
}
}
QPixmap pixmap;
pixmap.convertFromImage(buffer);
return pixmap;
}
bool isNull() const {
return mask.isNull();
}
private:
QString path;
QImage mask;
};
This code generates strange artifacts in the output:
From this input:
This is rendered with primary and secondary colors both #657590
. The dark blue is the backing widget's background color. The original icon just has the cog outline.
Why are the additional rectangular parts created? They are not part of the source image. I tried to use buffer.setPixel
instead of scanLine
, but that produced the same output.
The problem was the image format QImage::Format_ARGB32_Premultiplied
. The way I modified the image, QImage::Format_ARGB32
was appropriate. Using that format solved the problem.