我有这样一个需求。
有一张图片,这张图片上面被我用不同的颜色画了不同的区域,然后我想选择这张图片中的某一种颜色,只在这种颜色所在的区域内进行绘制或者用橡皮擦擦除这种颜色,而不会影响其他颜色。
看着这个需求的时候,我首先想到的是图层,就是类似PS的图层一样,就是将这张图片中的每一种颜色单独提取出来另作一种图层,这样就会得到多张只包含一种颜色的图片,这些图片的其他区域是透明的。然后利用 QPainter::CompositionMode_SourceOver
模式将这些图形进行绘制。
如果需要在某种颜色上绘制的时候,就在对应的图片上进行绘制。
最后在绘制完成后,将这些图片进行合并,每个像素都进行重合而得到开始的一张图像。
想法是好的,但测试的过程应该是痛苦的,因为要不停地进行图像的分层,并且在多层像素颜色冲突的时候,无法确定具体应该选择哪种进行显示。所以我没测,转而寻找其他更简单的方法,毕竟人都是要学会偷懒的。
也是在找如何设置图片的透明度的过程中,偶然的发现了QRegion这个类。Qt帮助文档中这样说:
The QRegion class specifies a clip region for a painter.
QRegion is used with QPainter::setClipRegion() to limit the paint area to what needs to be painted. There is also a QWidget::repaint() function that takes a QRegion parameter. QRegion is the best tool for minimizing the amount of screen area to be updated by a repaint.
This class is not suitable for constructing shapes for rendering, especially as outlines. Use QPainterPath to create paths and shapes for use with QPainter.
QRegion is an implicitly shared class.
说白了,就是这个类提供一个绘制区域的限制功能。
这个类的构造函数有好几种,但是我们理解一下,因为这个类是为QPainter
提供了一个绘制的限制区域。也就不难理解,我们肯定是要给它一个区域类型的参数。看看他的构造函数我们也基本上能明白。
QRegion()
QRegion(int x, int y, int w, int h, QRegion::RegionType t = Rectangle)
QRegion(const QRect &r, QRegion::RegionType t = Rectangle)
QRegion(const QPolygon &a, Qt::FillRule fillRule = Qt::OddEvenFill)
QRegion(const QRegion &r)
QRegion(QRegion &&other)
QRegion(const QBitmap &bm)
下面是我找了其中的一个做了下测试。鼠标左键绘制,右键删除。
void ImageWidget::drawBrush(QMouseEvent *event)
{
QPen pen = QPen(QColor(Qt::red), 10, Qt::DashLine, Qt::RoundCap);
pen.setCapStyle(Qt::RoundCap);
QPainter painter(&m_draw);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.setRenderHint(QPainter::Antialiasing);
painter.setClipRegion(m_region);
if (m_button == Qt::RightButton)
{
painter.setClipRegion(m_backregion);
painter.setCompositionMode(QPainter::CompositionMode_Clear);
}
painter.setPen(pen);
if (event->type() == QEvent::MouseButtonPress)
{
m_brushPt = event->pos();
painter.drawPoint(event->pos());
}
if (event->type() == QEvent::MouseMove)
{
painter.drawLine(m_brushPt, event->pos());
m_brushPt = event->pos();
}
if (event->type() == QEvent::MouseButtonRelease)
{
m_brushPt = QPoint(-1, -1);
}
painter.end();
update();
}
假设我们需要设定一个矩形的限制区域。
QRegion region(QRect( 40, 40, 100, 100));
m_region.swap(region);
我们今天主要要看的是通过QBitmap
构建的绘制区域,QRegion(const QBitmap &bm)
因为我们给定的限制区域是一个QImage
上指定的颜色,并且这些颜色的区域是不规则的。
帮助文档中这样说明:
Constructs a region from the bitmap bm.
The resulting region consists of the pixels in bitmap bm that are Qt::color1, as if each pixel was a 1 by 1 rectangle.
This constructor may create complex regions that will slow down painting when used. Note that drawing masked pixmaps can be done much faster using QPixmap::setMask().
结果区域由位图bm中Qt::color1的像素组成,每个像素都是一个1乘1的矩形。
为什么是位图呢?因为位图只有两种颜色,非黑即白,每个像素的值也只有两种,要么是 0 要么是 1,而通过位图构造的QRegion
对象,在绘制的时候,只有在像素值为 0 的区域生效。这也就让我们的问题发生了变化。
这样, 我们的问题就从刚开始的如何创建一个绘制的限制区域,转换为了如何将一个QImage图像转换为位图。
下面的代码是一个根据给定的图片及设定的颜色来创建一个QRegion
对象的过程。我实际的测试代码中因为有右键删除,所以用的不是这个。但也相差不大,这段代码也是通过测试的。
void ImageWidget::createRegion(const QImage& img, const QColor& color, bool all)
{
QImage imgRegion(img.size(), QImage::Format_MonoLSB);
for (int x = 0, w = imgRegion.width(); x < w; ++x)
{
for (int y = 0, h = imgRegion.height(); y < h; ++y)
{
imgRegion.setPixel(x, y, all ? 0 : (img.pixelColor(x, y) == color ? 0 : 1));
}
}
QBitmap bp = QBitmap::fromImage(imgRegion);
QRegion region(bp);
m_region.swap(region);
}
上面的这个函数我们可以这样理解,参数 color
可以理解为是需要创建区域的颜色, all 是是否不指定颜色,对整张图像都有效。
紧接着首先根据给定的模版构造一个QImage
对象,这个QImage
对象的颜色是透明的,然后通过遍历模版每个像素的颜色,来设定我们构建的QImage
对象的像素值,要注意一点,QRegion
只有在像素值为 0 的像素上生效。
然后通过QBitmap
生成QRegion
。
基本上我们的测试就完成了,但是一般在绘制的过程中,我们有很大的可能性是需要擦除的,擦除的时候也只能擦除我们目前在画的颜色区域,并不会将所有的都擦除。这种情况下,就需要我们对擦除时候生效轨迹做一次限制。
并且在每次绘制完成,也就是鼠标事件 mouseRelease的时候,更新擦除的区域。创建方式跟我们上面的是一样的。
如图所示,首先我们选择背景标签为 alllabels,前景标签为红色在图片上进行绘制,效果如下:
接着我们选择背景标签为红色,前景标签为绿色在图片上进行绘制,效果如下:
擦除的标签其实是和我们当前选择的前景标签是一致的,创建限制区域是一样的。