Qt桌面白板工具其二(荧光笔的实现方法并解决透明度导致的重合深色点问题)

上一篇文章: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事件触发过少,采集点数过少,导致线条不流畅。这里不再赘述。
有什么问题可以在评论区提出,有关荧光笔怎么实现,应该是已经清楚啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值