赞
踩
上一篇文章:Qt桌面白板工具其一(解决曲线不平滑的问题——贝塞尔曲线)
一、前言:
我比较啰嗦,可能会说很多废话。在上一篇文章中,我主要分享了有关贝塞尔算法解决曲线不平滑的问题,简单来说我们可以通过算法,对所搜集的一系列点进行重新绘制,在画布中绘制我们需要的QPaintPath路径。这个过程中,我一直有在考虑荧光笔的实现问题。荧光笔,简单来说就是绘制一条半透明的线段,常见于需要对一些显示文本进行高亮提示。而采用上述贝塞尔整条路径线条绘制的方法,确实能实现。不过一旦放弃整段路径绘制,转而采用线段叠加的方式绘制线条,就难免会出现因为多条不透明线段叠加,所产生的重复点颜色较深的情况,如图:
经过尝试,一旦整段路径,也就是QPaintPath中添加的线段过多,单次绘制的时候会产生比较严重的耗时。这在鼠标拖动moveevent和刷新显示paintevent中,是不能忍受的。如果需要采用贝塞尔算法,我会选择在鼠标move过程中先采用线段叠加法,在鼠标release释放的时候再统一绘制所采集的点集和线段。又或者将线段拆开,但难免会有凸点和重合点。
上面都是题外话了,本篇文章中想要实现的,是在move过程中,即便采用线段叠加,也能实现荧光笔的效果。这个要求,需要我实现线段叠加的时候,不能产生重合区域颜色较深现象。这个问题在过往很长一段时间内困扰了我很久,但现在终于是解决了。
二、混合模式
首先,分享一个概念QPainter.CompositionMode(附带链接),这是QPainter的绘制混合模式。
被绘制的画布叫做“目标”,需要绘制上去的东西叫做“源”。
而一般来说,QPainter默认的是CompositionMode_SourceOver,即覆盖模式。如果底部有一条实色的红线,你绘制一条半透明绿线,将会产生叠加色。
图一
而如果选择CompositionMode_Source,则是重合部分都只显示“源”,而忽略“目标”的部分。
图二
可以看出,虽然绿线是半透明的,但实际上重合部分已经没有了红色。
所以,本文第一张图的绿色重合点,就能完美解决了,即半透明绿色线段的叠加,重合部分只会显示后一条线段的颜色,透明度通道不会产生叠加,进而形成一条完整的半透明统一透明度的线条。
好啦,以上其实只是为了绘制绿色线段而已,不要被这两张图的红线绿线混淆了,实际上我们需要实现的是图一的效果。
三、简单代码分享
以下是我的操作步骤
//画布资源 QImage *draw_image = nullptr; //qimage画布,最终结果 QImage last_image; //记录动作开始前的原图 QImage temp_image; //中间的临时图片 QImage layer_image; //中间图层,单个动作的图层 QVector<QPointF> ployline_points; //移动中搜集的点集 void xxx::mousePressEvent(QMouseEvent *event) { last_image = draw_image->copy(); //记录动作开始前的原图 temp_image = last_image; //move过程中临时显示的图片层 layer_image.fill(Qt::transparent); //图层层,填充透明 ployline_points.clear(); ployline_points.append(event->pos()); } void xxx::mouseMoveEvent(QMouseEvent *event) { //过滤密集点(会影响效果) if(qAbs(ployline_points.last().x()-event->pos().x())>15 || qAbs(ployline_points.last().y()-event->pos().y())>15) { ployline_points.append(event->pos()); if(ployline_points.count()>=2){ //画图层 QPainter painter(&layer_image); //绘制在透明图层上,不干扰原图 painter.setCompositionMode(QPainter::CompositionMode_Source); //这里直接用源来覆盖,不会收到目标的影响 painter.setRenderHint(QPainter::Antialiasing, true); painter.setBackgroundMode(Qt::TransparentMode); //设置背景模式 将画师的背景模式设置为给定模式 painter.setPen(current_draw_mode->pen); //只画最后两个点线(短线段叠加,不会产生重合深色点) painter.drawLine(ployline_points[ployline_points.count()-2],ployline_points.last()); painter.end(); //复制原图 temp_image = last_image; //拷贝原图 painter.begin(&temp_image); //在临时图片层绘制 painter.setCompositionMode(QPainter::CompositionMode_SourceOver); //调整为正常的混合模式 QRect rect = layer_image.rect(); painter.drawImage(rect, layer_image); //将图层绘制在临时图片层中 } } } void xxx::mouseReleaseEvent(QMouseEvent *event) { QImage image = last_image; //拷贝原图 QPainter painter(&image); //在拷贝层重新绘制搜集点 painter.setRenderHint(QPainter::Antialiasing, true); if(isBezier){ //贝塞尔优化(也可以不优化,这里不提供代码了) QVector<QPointF> points; for(int i = 0; i < ployline_points.count(); i++){ QPointF pt = ployline_points[i]; if(points.count()<2){ points.append(pt); }else{ points.append((points.last()+pt)/2); points.append(pt); } } drawBezierPath(&painter,points); }else{ drawPolyline(&painter,ployline_points); } //将重新绘制的图片赋给最终图片 *draw_image = image; last_image = draw_image->copy(); ployline_points.clear(); } void xxx::paintEvent(QPaintEvent *event) { if(is_drawing){//move中的标志 QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); QRect rect = this->rect(); painter.drawImage(rect, temp_image, temp_image.rect());//move期间显示临时图片 painter.end(); }else{ //画布源绘制 if(draw_image) { QPainter painter(this); painter.setBackgroundMode(Qt::TransparentMode); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); QRect rect = this->rect(); painter.drawImage(rect, *draw_image);//move结束显示最终图片,即实际的画布源 painter.end(); } } }
paintevent交由负责update的定时器触发,避免paint事件产生过多,绘制耗时导致move事件触发过少,采集点数过少,导致线条不流畅。这里不再赘述。
有什么问题可以在评论区提出,有关荧光笔怎么实现,应该是已经清楚啦。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。