Previous article: One of the Qt desktop whiteboard tools (solve the problem of uneven curves – Bezier curve)
1. Foreword:
I’m quite talkative and may talk a lot of nonsense. In the previous article, I mainly shared about the problem of non-smooth curves solved by the Bessel algorithm. Simply put, we can use the algorithm to redraw a series of collected points and draw the QPaintPath we need in the canvas. path. During this process, I have been thinking about the implementation of highlighters. A highlighter pen, simply speaking, is to draw a translucent line segment, which is often used to highlight some displayed text. And using the above-mentioned Bezier entire path line drawing method can indeed be achieved. However, once you give up drawing the entire path and instead use line segment superposition to draw lines, it is inevitable that the repeated points produced by the superposition of multiple opaque line segments will be darker, as shown in the figure:
After trying, once the entire path, that is, too many line segments are added to QPaintPath, it will take a serious time to draw in a single time. This is intolerable in mouse drag moveevent and refresh display paintevent. If I need to use the Bessel algorithm, I will choose to use the line segment superposition method first during the mouse move process, and then draw the collected point sets and line segments uniformly when the mouse is released. Or you can take the line segments apart, but there will inevitably be bumps and overlapping points.
The above are all digressions. What I want to achieve in this article is to achieve the effect of a highlighter even with line segment superposition during the move process. This requirement requires me to avoid overlapping areas with darker colors when superimposing line segments. This problem has troubled me for a long time in the past, but now it is finally solved.
2. Mixed mode
First, share a concept QPainter.CompositionMode (link attached), which is the drawing blending mode of QPainter.
The canvas being drawn is called the “target”, and the thing that needs to be drawn is called the “source”.
Generally speaking, QPainter’s default is CompositionMode_SourceOver, which is the overlay mode. If there is a solid red line at the bottom and you draw a translucent green line, it will create an overlay color.
Figure 1
And if CompositionMode_Source is selected, only the “source” will be displayed in the overlapping parts, while the “target” part will be ignored.
Figure II
It can be seen that although the green line is translucent, there is actually no red in the overlapping part.
Therefore, the green overlapping point in the first picture of this article can be perfectly solved, that is, the superposition of translucent green line segments. The overlapping part will only display the color of the latter line segment, and the transparency channel will not be superimposed, thus forming a complete semi-transparent green line segment. Transparent lines with unified transparency.
Okay, the above is actually just for drawing green line segments. Don’t be confused by the red and green lines in these two pictures. In fact, what we need to achieve is the effect of Figure 1.
3. Simple code sharing
Here are my steps
//Canvas resources QImage *draw_image = nullptr; //qimage canvas, final result QImage last_image; //Record the original image before the action starts QImage temp_image; //Temporary picture in the middle QImage layer_image; //Middle layer, single action layer QVector<QPointF> ployline_points; //Point set collected during movement void xxx::mousePressEvent(QMouseEvent *event) {<!-- --> last_image = draw_image->copy(); //Record the original image before the action starts temp_image = last_image; //The image layer temporarily displayed during the move process layer_image.fill(Qt::transparent); //Layer layer, fill transparent plotline_points.clear(); plotline_points.append(event->pos()); } void xxx::mouseMoveEvent(QMouseEvent *event) {<!-- --> //Filter dense points (will affect the effect) if(qAbs(ployline_points.last().x()-event->pos().x())>15 || qAbs(ployline_points.last().y()-event->pos().y( ))>15) {<!-- --> plotline_points.append(event->pos()); if(ployline_points.count()>=2){<!-- --> //Draw layer QPainter painter( & amp;layer_image); //Draw on the transparent layer without disturbing the original image painter.setCompositionMode(QPainter::CompositionMode_Source); //Here it is directly overwritten with the source and will not be affected by the target. painter.setRenderHint(QPainter::Antialiasing, true); painter.setBackgroundMode(Qt::TransparentMode); //Set the background mode Set the painter's background mode to the given mode painter.setPen(current_draw_mode->pen); //Only draw the last two dotted lines (short line segments are superimposed and no overlapping dark points will be produced) painter.drawLine(ployline_points[ployline_points.count()-2],ployline_points.last()); painter.end(); //Copy the original image temp_image = last_image; //Copy the original image painter.begin( & temp_image); //Draw on the temporary image layer painter.setCompositionMode(QPainter::CompositionMode_SourceOver); //Adjust to normal blending mode QRect rect = layer_image.rect(); painter.drawImage(rect, layer_image); //Draw the layer in the temporary picture layer } } } void xxx::mouseReleaseEvent(QMouseEvent *event) {<!-- --> QImage image = last_image; //Copy the original image QPainter painter( & amp;image); //Redraw the collected points on the copy layer painter.setRenderHint(QPainter::Antialiasing, true); if(isBezier){<!-- --> //Bessel optimization (you don’t need to optimize, the code is not provided here) QVector<QPointF> points; for(int i = 0; i < plot_points.count(); i + + ){<!-- --> QPointF pt = plotline_points[i]; if(points.count()<2){<!-- --> points.append(pt); }else{<!-- --> points.append((points.last() + pt)/2); points.append(pt); } } drawBezierPath( & amp;painter,points); }else{<!-- --> drawPolyline( & amp;painter,ployline_points); } //Assign the redrawn picture to the final picture *draw_image = image; last_image = draw_image->copy(); plotline_points.clear(); } void xxx::paintEvent(QPaintEvent *event) {<!-- --> if(is_drawing){<!-- -->//Flags in 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());//Display temporary images during move painter.end(); }else{<!-- --> //Canvas source drawing 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 ends and displays the final image, which is the actual canvas source painter.end(); } } }
The paintevent is triggered by the timer responsible for update to avoid too many paint events, too few move events triggered due to time-consuming drawing, and too few collection points, resulting in unsmooth lines. I won’t go into details here.
If you have any questions, you can ask them in the comment area. It should be clear about how to implement the highlighter.