[Interest] QPainterPathStroker's curve threshold

Ch'Gans chgans at gna.org
Thu Dec 8 13:09:51 CET 2016


On 8 December 2016 at 20:40, Elvis Stansvik <elvstone at gmail.com> wrote:
> Den 8 dec. 2016 1:02 fm skrev "Ch'Gans" <chgans at gna.org>:
>>
>> Hi,
>>
>> The documentation of QPainterPathStroker's curve threshold says:
>> ----------------------------------------------------------------
>> Specifies the curve flattening threshold, controlling the granularity
>> with which the generated outlines' curve is drawn.
>>
>> The default threshold is a well adjusted value (0.25), and normally
>> you should not need to modify it. However, you can make the curve's
>> appearance smoother by decreasing its value.
>> ----------------------------------------------------------------
>>
>> However i have noticed that when used in a graphics scene with objects
>> of small size, the default threshold value is obviously too big, eg a
>> circle becomes a square!
>>
>> As the doc states that "normally you should not need to modify it", i
>> was reluctant to change it, but then I realised that maybe this
>> default value is actually well adjusted in the context of QWidget,
>> where the unit of measure is "pixel". In a QGraphicsScene the unit is
>> arbitrary.
>> If I'm right, there could be potentially a problem as well if the
>> scene object is too big, eg, a circle will be rendered as a regular
>> polygon with thousands of edges. So it all boil down to find the
>> correct threshold so that both small and big (in scene coordinates)
>> will be rendered in the view (pixel coordinates), using say 26 edges
>> polygon (icosikaihexagon [1][2] ;))
>>
>> Could anyone shed a bit of light on what "well adjusted" mean and how
>> to actually adjust it to s specific use case? Is this threshold
>> actually expressed in a pixel-related unit of measure? Is it a ratio
>> of something (0.0 to 1.0) ?
>
> I've been wondering the same actually, though it was a long time ago.
>
> The QPainterPathStroker has no information about which scale you intend to
> paint the path, so I guess like you say, it must make some assumption based
> on pixel units and a regular screen size.
>
> For details of how it adjusts this value I think you'll have to look at the
> code. But maybe the docs should be extended a bit.

Default value is indeed 0.25, but settings the stroke width will
adjust as follow:
threshold = strokeWidth > 4 ? 1.0/strokeWidth : 0.25

Tracking the usage of curve threshold lead me to QStroker, QTransform
and QBezier, but man, the code is hairy, even more than me! ;)

The threshold seems to be adjusted dynamically too in some recursive
algorithm...

Anyway, there's this interesting comment in QBezier::toPolygon():

    // flattening is done by splitting the bezier until we can replace
the segment by a straight
    // line. We split further until the control points are close
enough to the line connecting the
    // boundary points.
    //
    // the Distance of a point p from a line given by the points (a,b)
is given by:
    //
    // d = abs( (bx - ax)(ay - py) - (by - ay)(ax - px) ) / line_length
    //
    // We can stop splitting if both control points are close enough
to the line.
    // To make the algorithm faster we use the manhattan length of the line.

[Note: AFAIK, any non-linear element in a QPainterPath is handled as a
bezier curve, read: circular and ellipsoidal arcs are stored as bezier
curves.]

If i got it right, at the end of the day, this distance 'd' is
compared to the 'bezier_flattening_threshold' (which is the
curveThreshold from the stroker). Which means that the curve threshold
is actually a ratio b/w 2 distances, so it is unit-less.

I have observed this problem twice (circle becomes square, couldn't
find the message in the archive).
The first time was due to performing QPainterPath set operations
(union, intersection) on small features (in the order of 10E-3), and
set operations first flatten the path.
Second time is quite similar, small features in a QGraphicsScene, no
set operations, but the path is flatten by the path stroker...

So if the curve threshold is really unitless, then the only
explanation i can come with is that the flattening algorithm
deteriorate as the input numbers get smaller, due to limitation of
arithmetic on floating point numbers.

Still, in the formula 'threshold = strokeWidth > 4 ? 1.0/strokeWidth :
0.25', 0.25 and 4 are obviously magic numbers that seem to consider
the stroke width being expressed in pixels. And - if i'm not wrong -
the documentation is lying: the default is 0.25, *if* the stroke with
is greater than 4.

To be continued...

Chris




>
> Elvis
>
>>
>> Thanks,
>> Chris
>>
>> [1] https://www.voltage.com/math-2/approximating-a-circle-with-a-polygon/
>> [2] http://mathforum.org/dr.math/faq/faq.polygon.names.html
>> _______________________________________________
>> Interest mailing list
>> Interest at qt-project.org
>> http://lists.qt-project.org/mailman/listinfo/interest



More information about the Interest mailing list