Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw a line and curves with fading edges with QPainter

QPainter is very easy to use and to draw a line one would simply do this:

QPainter painter(&image);
QPen pen;
pen.setWidth(5);
pen.setColor("black");
painter.setPen(pen);
painter.drawLine(QPointF(0,0), QPointF(200,250));

Now this works well, but I would like to create a "special" pen that produce lines with "smoothed" edges. For example, suppose the line I want to draw has a thickness of 10 pixels, then I would like the middle of the line (by middle I mean in terms of thickness and not length) to be fully opaque and at the edges the line should become semitransparent. I believe this could be obtained for example having the picture below as my point and then "drag" and paint the line so I will obtain the effect I desire. I know that Qt provides you with QBrush and gradients, but I can't figure out how to to do this.

Pic

like image 854
reckless Avatar asked Jan 04 '20 14:01

reckless


2 Answers

While Qt definitely has many drawing functions, there is none to let you draw using a pixmap brush along a path like you describe.

I can think of 2 ways to achieve what you want:

  1. Draw the path using standard non-fuzzy brush multiple times with a varying brush width and a transparent color. With enough iterations this will approximate the "fuzzy line" you are looking for.

  2. Draw the pixmap repeatedly along the path. This is usually how drawing software like photoshop or gimp will do it as it allows for some flexibility in the parameters such as different brush pixmaps and orientations etc.

I will try to provide example code (untested for now as I am currently away from my dev computer) for the second way of doing it here:

void drawPathWithPixmapBrush(QPainter painter, QPainterPath path, QPixmap pixmapBrush, qreal spacing=1.0) {
    qreal length = path.length();
    qreal pos = 0.0;
    // Adjust the spacing to be relative to brush size
    spacing=(spacing * pixmapBrush.width() );
    while (pos < length) {
        qreal percent = path.percentAtLength(pos);
painter.drawPixmap(path.pointAtPercent(percent), pixmapBrush);
pos += spacing;
    }
}

As you can see, this code will iteratively move along the given QPainterPath, and for each step draw the QPixmap brush to the given QPainter, resulting in what can be preceived as one continuous line drawn along the path with the pixmap brush.

QPainterPath supports all drawing operations that QPainter does, such as polygons, splines, lines, arches etc., so it will function as a dropin replacement for your existing QPainter draw calls for the most part (see the full list of drawing operations).

Here is example code to construct a simple straight line segment using QPainterPath:

QPainterPath path
QPointF lastPosition(10, 10);
QPointF currentPosition(100, 100);
path.moveTo(lastPosition);
path.lineTo(currentPosition);

Possible improvements include calculating spacing so that it ends exactly on the end of the line, and also maybe more correctly calculating the required number of draws will best give the impression of a line.

like image 145
Lennart Rolland Avatar answered Nov 18 '22 04:11

Lennart Rolland


One way to achieve this is to use a QRadialGradient as a brush for your QPen :

QPointF centerPoint(400, 400);
qreal centerRadius = 200;

QRadialGradient radialGrad(centerPoint, centerRadius);
radialGrad.setColorAt(0.000, QColor(0, 0, 0, 255));
radialGrad.setColorAt(0.8, QColor(0, 0, 0, 0.8 * 255));
radialGrad.setColorAt(1.000, QColor(0, 0, 0, 0.000));

QPen pen;
pen.setWidth(400);
pen.setColor("black");
pen.setBrush(radialGrad);

QPainter painter(this);
painter.setPen(pen);
painter.drawPoint(centerPoint);

Gradient brush screenshot result

The downside of this technique is the fact that the gradient is not smooth near the GradientStop. You should add several GradientStop to ease your radial gradient.

Another (and prettier) way to achieve this could be to create a custom QBrush with a dedicated texture or texture image (using the method QBrush::setTexture or QBrush::setTextureImage) representing the wanted brush pattern.

like image 20
Alaenix Avatar answered Nov 18 '22 05:11

Alaenix