简体   繁体   English

使用 QPainter 绘制带有渐变边缘的直线和曲线

[英]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非常易于使用,画一条线只需这样做:

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.例如,假设我要绘制的线条的粗细为 10 像素,那么我希望线条的中间(我指的是粗细而不是长度)完全不透明,并且线条的边缘应该变成半透明。 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.我知道 Qt 为您提供了QBrush和渐变,但我不知道如何做到这一点。

图片

While Qt definitely has many drawing functions , there is none to let you draw using a pixmap brush along a path like you describe.虽然 Qt 肯定有很多绘图功能,但没有一个可以让您使用像素图画笔沿着您描述的路径进行绘制。

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.这通常是像 photoshop 或 gimp 这样的绘图软件将这样做的,因为它允许参数具有一定的灵活性,例如不同的画笔像素图和方向等。

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迭代移动,并且对于每一步都将QPixmap画笔绘制到给定的QPainter上,从而可以将其视为使用 pixmap 画笔沿着路径绘制的一条连续线。

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 ). QPainterPath 支持 QPainter 所做的所有绘图操作,例如多边形、样条线、直线、拱形等,因此它将在很大程度上替代您现有的 QPainter 绘图调用(请参阅绘图操作的完整列表)。

Here is example code to construct a simple straight line segment using QPainterPath :这是使用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.可能的改进包括计算间距,使其恰好在线条的末端结束,并且可能更正确地计算所需的绘制次数将最好地给出线条的印象。

One way to achieve this is to use a QRadialGradient as a brush for your QPen :实现此目的的一种方法是使用QRadialGradient作为 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);

渐变画笔截图结果

The downside of this technique is the fact that the gradient is not smooth near the GradientStop.这种技术的缺点是渐变在 GradientStop 附近不平滑。 You should add several GradientStop to ease your radial gradient.你应该添加几个 GradientStop 来缓和你的径向渐变。

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.实现此目的的另一种(更漂亮的)方法可能是创建一个自定义 QBrush,其具有表示所需画笔图案的专用纹理或纹理图像(使用方法QBrush::setTextureQBrush::setTextureImage )。

As a tentative answer.作为一个试探性的答案。 I know you said QT, but I am unfamiliar with it.我知道你说 QT,但我不熟悉它。 I will give you some GLSL code I use for my own work and hopefully this helps you.我会给你一些用于我自己工作的 GLSL 代码,希望这对你有所帮助。

In the most absolute naive implementation, line anti aliasing can be coded by selecting the alpha value of a point in a rectangle depending on its distance from the desired line.在最简单的实现中,可以通过根据与所需线的距离选择矩形中点的 alpha 值来对线抗锯齿进行编码。 That is to say, you render a rectangle and for each point in that rectangle you decide how far away from the line it is and select its alpha value based on that distance.也就是说,您渲染一个矩形,并为该矩形中的每个点决定它与线的距离,并根据该距离选择其 alpha 值。 For example in the fragment shader one could write:例如在片段着色器中可以这样写:

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) in vec2 p1;
layout(location = 1) in vec2 p2;
layout(location = 2) in vec2 frag_pos;

layout(location = 0) out vec4 outColor;

layout(binding = 0) uniform LineProperties {
    vec4 offset;
    vec4 color;
    float aspect_ratio;
    float line_width;
};

void main() {

    vec2 line_dir = normalize(p2 - p1);
    vec2 proj = dot(frag_pos - p1, line_dir) * line_dir;
    float distance = length((frag_pos - p1) - proj);
    float coeff = distance / line_width;
    float intensity = 1 - smoothstep(0.2, 0.9, coeff);
    outColor = vec4(intensity * color.xyz, intensity);
}

That can give you lines like this:这可以给你这样的行: 在此处输入图像描述

If you inspect that image more closely you will notice some gaps at the joints between straight segments.如果您更仔细地检查该图像,您会注意到直线段之间的接头处存在一些间隙。 That can be avoided by not calculating the shortest distance to the line, but the shortest distance to a segment that is smaller than the line, this will make your straight regions look somewhat like a 2D pill/capsule, also known as a stadium .这可以通过不计算到直线的最短距离来避免,而是计算到小于直线的段的最短距离,这将使您的直线区域看起来有点像 2D 药丸/胶囊,也称为体育场

The centers of 2 adjacent stadiums should be equal, that way all gaps will be gone.两个相邻体育场的中心应该相等,这样所有的差距都会消失。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM