使用scanLine处理QImage渲染SVG图标时出现伪影的原因
问题:SVG图标渲染出现额外矩形伪影
我的应用支持多种配色模式,需要给灰度SVG图标渲染不同颜色——每种模式定义两种颜色,分别替换图标里的黑色和白色。我写的实现代码如下:
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; };
但这段代码渲染出的图标有奇怪的伪影(额外矩形):
原始SVG图标仅包含齿轮轮廓:
本次渲染用的主副颜色都是#657590,深蓝色是背景控件底色。我试过用buffer.setPixel替换scanLine,结果还是一样。为什么会出现这些不属于源图的额外矩形?
原因分析与解决方案
原因
问题出在生成mask的setSize方法中。你创建的QImage是Format_ARGB32_Premultiplied格式,调用renderer.render(&svgPainter)后,SVG渲染器可能会在图像边缘区域留下RGB值全为0但alpha值不为0的像素。当paint方法处理这些像素时,qRed(src)取到0,结合主副颜色相同的情况,gradient.at(0, alpha)会生成目标颜色,再加上alpha不为0,就显示出了额外的矩形。
解决方案
有两种方式可以解决这个问题:
方案1:生成mask后清理无效像素
在setSize方法中,渲染SVG后遍历mask图像,将所有RGB值全为0的像素强制设为完全透明:
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); // 清理RGB全为0的无效像素 for (int y = 0; y < mask.height(); ++y) { QRgb* line = reinterpret_cast<QRgb*>(mask.scanLine(y)); for (int x = 0; x < mask.width(); ++x) { QRgb& pixel = line[x]; if (qRed(pixel) == 0 && qGreen(pixel) == 0 && qBlue(pixel) == 0) { pixel = Qt::transparent; } } } } } }
方案2:在paint时跳过无效像素
在paint方法的像素循环中,判断源像素的RGB值是否全为0,若是则直接设为透明,不进行gradient计算:
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]; // 跳过RGB全为0的无效像素,直接设为透明 if (qRed(src) == 0 && qGreen(src) == 0 && qBlue(src) == 0) { destLine[x] = Qt::transparent; } else { destLine[x] = gradient.at(qRed(src), qAlpha(src)); } } } QPixmap pixmap; pixmap.convertFromImage(buffer); return pixmap; }
两种方案都能过滤掉SVG渲染残留的无效像素,消除额外矩形伪影。
内容的提问来源于stack exchange,提问作者flyx
相关产品推荐
相关产品推荐

