Bug 688655 - stroking: Incorrect stroking of very wide curves.
Summary: stroking: Incorrect stroking of very wide curves.
Status: RESOLVED FIXED
Alias: None
Product: Ghostscript
Classification: Unclassified
Component: Graphics Library (show other bugs)
Version: master
Hardware: PC Windows XP
: P4 normal
Assignee: Robin Watts
URL:
Keywords: bountiable
Depends on:
Blocks:
 
Reported: 2006-04-17 07:53 UTC by leonardo
Modified: 2009-11-25 16:48 UTC (History)
0 users

See Also:
Customer:
Word Size: ---


Attachments
cur.ppm.zip (4.46 KB, application/octet-stream)
2006-04-17 07:55 UTC, leonardo
Details
Diagram of why the problem happens (8.73 KB, image/png)
2009-10-12 10:09 UTC, Robin Watts
Details
Possible (sub optimal) solution (17.98 KB, image/png)
2009-10-12 10:50 UTC, Robin Watts
Details
Better solution (9.25 KB, image/png)
2009-10-12 10:56 UTC, Robin Watts
Details
Proposed patch to implement the "better" solution. (6.41 KB, patch)
2009-10-13 05:16 UTC, Robin Watts
Details | Diff
jointest2.ps - an example that shows why this solution is no good. (310 bytes, application/postscript)
2009-10-14 07:05 UTC, Robin Watts
Details
jointest2 rendered in acrobat (7.26 KB, image/png)
2009-10-14 07:06 UTC, Robin Watts
Details
unpatched ghostscripts rendering (2.80 KB, image/png)
2009-10-14 07:10 UTC, Robin Watts
Details
patched ghostscripts rendering (2.61 KB, image/png)
2009-10-14 07:13 UTC, Robin Watts
Details
cur.png (3.69 KB, image/png)
2009-10-14 10:08 UTC, Ralph Giles
Details
jointest3.ps: Show a range of joins between short strokes (2.27 KB, application/postscript)
2009-10-16 08:07 UTC, Robin Watts
Details
jointest3_ApplePreview.png (92.25 KB, image/png)
2009-10-16 09:04 UTC, Henry Stiles
Details
jointest3-evince.png (69.51 KB, image/png)
2009-10-16 09:13 UTC, Ralph Giles
Details

Note You need to log in before you can comment on or make changes to this bug.
Description leonardo 2006-04-17 07:53:37 UTC
This bug occasionaly detected while working on the bug 688363.With 
GraphicAlphaBits=1 -r72 on any raster device the following program gives a 
brush-like spot instead the right one :

0 setlinecap
0 setlinejoin
0.001 setflat
700 setlinewidth
300 350 50 90 95 arc
stroke
showpage

The problem happens due to the gx_stroke_path_only is incomplete. It gives 
defects when the line width is 2+ times grater than the local curvity radius.
Comment 1 leonardo 2006-04-17 07:55:10 UTC
Created attachment 2160 [details]
cur.ppm.zip

A zipped raster from ppmraw.
Comment 2 Robin Watts 2009-10-09 12:23:45 UTC
Reduced example that shows the same problems:

0 setlinecap
0 setlinejoin
0.001 setflat
700 setlinewidth
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3242 lineto
stroke
showpage
Comment 3 Robin Watts 2009-10-12 10:09:31 UTC
Created attachment 5472 [details]
Diagram of why the problem happens

This diagram attempts to explain why the problem occurs.

When stroking the 2 segment red line, we thicken each line segment to the cyan
areas. We then adjust the first area we plot with a mitre join to the second
area.

In the line_join_points routine, we correctly determine which side of the curve
is 'outside', and add the green region. Unfortunately, we ignore the
possibility that the stroking is wide enough to expose the area between the
lines at the bottom.
Comment 4 Robin Watts 2009-10-12 10:50:36 UTC
Created attachment 5473 [details]
Possible (sub optimal) solution

The simplest solution I can see to this bug would be to add another point to
the join, which would result in the area shown in this attachment being filled.


Clearly this is much closer to the 'correct' result, and does not require many
changes to the current code. For 'normal' cases the extra area would be
entirely hidden in the area being filled anyway.

The only downsides of this is that a) more pixels might get plotted twice
(don't know if this is true - I'd have to follow the code through a bit further
to see whether this is the case or not), and b) the bottom edge of the curve
isn't quite smooth.
Comment 5 Robin Watts 2009-10-12 10:56:33 UTC
Created attachment 5474 [details]
Better solution

The 'ideal' solution, if I can figure out how, would be to spot that a
secondary join is required (possibly by intersecting the two 'outer' edges of
the 'inner' region, and to add the indicated region to the shape.

I have no idea how much effort this will actually be though. Will continue
investigating.
Comment 6 Robin Watts 2009-10-13 05:16:50 UTC
Created attachment 5477 [details]
Proposed patch to implement the "better" solution.

This patch extends the line_join_points function to return points for a
"secondary" join too. Normally this will be empty, but in cases such as shown
here, it will be populated with the region needing to be filled under the
curve.

Callers to line_join_points have been modified to fill this area as required
too.
Comment 7 Robin Watts 2009-10-14 07:05:24 UTC
Created attachment 5483 [details]
jointest2.ps - an example that shows why this solution is no good.

This attachment shows the effect of different joins with short lines. When
rendered in ghostscript (patched/unpatched) and acrobat, we get different
results. The proposed fix makes this significantly worse.
Comment 8 Robin Watts 2009-10-14 07:06:49 UTC
Created attachment 5484 [details]
jointest2 rendered in acrobat

This shows how acrobat renders the file. This version can be taken as
definitive.
Comment 9 Robin Watts 2009-10-14 07:10:02 UTC
Created attachment 5485 [details]
unpatched ghostscripts rendering

Here we see that unpatched, ghostscripts rendering is pretty much on the money,
with the exception of the 'round' join, which is a known bug (bug # 688269).
Comment 10 Robin Watts 2009-10-14 07:13:42 UTC
Created attachment 5486 [details]
patched ghostscripts rendering

Here we see that both sides of the join are filled in - clearly not the same as
acrobat.

This suggests that filling the opposite side of the stroke is the wrong
solution. An alternative approach to fix the problem is therefore required -
possibly by boosting the degree of flatness used for the initial stroke in
cases where the stroke is wide.
Comment 11 Robin Watts 2009-10-14 07:41:46 UTC
> An alternative approach to fix the problem is therefore required -
> possibly by boosting the degree of flatness used for the initial stroke in
> cases where the stroke is wide.

And no, the answer can't be found purely in flattening better, because, as the 
reduced example in comment 2 shows, it happens with already flattened things 
too.
Comment 12 Ralph Giles 2009-10-14 10:08:26 UTC
Created attachment 5487 [details]
cur.png

Attaching a png version of Igor's original ppm.zip for easier browsing.
Comment 13 Ralph Giles 2009-10-14 10:09:47 UTC
FWIW, the original example looks ok with x11alpha, but not with x11. Robin's
flattened example from comment #2 shows the problem with both devices.
Comment 14 Robin Watts 2009-10-14 11:15:38 UTC
Some further observations:

1) Render the postscript in the original bug report in acrobat 9 pro and 
ghostscript, and both give the same 'bow tie' shape. (By which I mean, it 
looks like a line rotated by a few degrees around it's centre, so the ends are 
wider than the middle). The difference is that ghostscript leaves one end of 
the bow tie looking "ragged".

2) Render the cutdown example from comment #2, and we see more difference; 
ghostscript draws it as the same 'bow tie' shape as before (albeit less thick 
at the ends, as we only have 2 line segments). Acrobat draws it with even 
thickness along the length.

This leads me to suspect that Acrobat is 'fudging' the stroke somehow to avoid 
the widening at the end.

As a test, consider this modified example.

---->8-----
0 setlinecap
0 setlinejoin
/DeviceRGB setcolorspace
700 setlinewidth

0 0 0 setcolor
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3242 lineto
stroke

0 1 0 setcolor
400.0000 533.3320 moveto
399.6328 533.3281 lineto
stroke

0 0 1 setcolor
399.6328 533.3281 moveto
399.2695 533.3242 lineto
stroke

showpage
---->8-----

This renders the same shape as before in black, then renders the individual 
line segments in blue/green on top. View this in Acrobat 9, and black pixels 
can be seen outside the bottom half of the shape - indicating that Acrobat has 
adjusted the combine stroke region outwards there.

Alternatively, render this in Acrobat 9:

---->8-----
0 setlinecap
0 setlinejoin
/DeviceRGB setcolorspace
700 setlinewidth

0 1 0 setcolor
400.0000 533.3320 moveto
399.6328 533.3281 lineto
stroke

0 0 1 setcolor
399.6328 533.3281 moveto
399.2695 533.3242 lineto
stroke

0 0 0 setcolor
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3242 lineto
stroke

showpage
---->8-----

and it shows the opposite adjustment being done in the top half of the shape 
(blue and green pixels can be seen poking out from the black shape that is now 
plotted on top).

I suspect that Acrobat is using some undocumented heuristic to identify when 
the outside edge of line segments should be shortened. As such, we may be best 
to ignore the cutdown example and revert to the original one.

This means that the earlier discarded idea of flattening more with wide curves 
might indeed be a solution here (leaving us with a separate problem of 
matching adobes 'fudge' later).
Comment 15 Robin Watts 2009-10-14 12:20:37 UTC
The following example:

0 setlinecap 0 setlinejoin 700 setlinewidth
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3236 lineto
stroke showpage

Shows the problem in gs, and appears to render in acrobat as a line of 
constant thickness.

Tweak just one number very slightly, thus:

0 setlinecap 0 setlinejoin 700 setlinewidth
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3235 lineto
stroke showpage

and Acrobat stops rendering it as a line of constant thickness. Call 
this 'Change A'.

Tweak that number a bit further, and as we change it from 533.3205 to 
533.3204, thus:

0 setlinecap 0 setlinejoin 700 setlinewidth
400.0000 533.3320 moveto
399.6328 533.3281 lineto
399.2695 533.3204 lineto
stroke showpage

... Acrobat starts to show the same 'void' in the bottom half of the shape as 
ghostscript does. Call this 'Change B'.

Comparing the renderings given either side of change B it is clear that they 
do not differ just by the void starting to show up - something has 
deliberately gone to the trouble of moving the line ends and filling the void. 
So maybe the code in the patch above *is* required, but only if some obscure 
condition (perhaps relating to the difference in angle of the 2 line segments) 
is satisfied.

Both Change A and B happen irrespective of the strokewidth (same changes occur 
at the same values with strokewidth set to 600 or 800).

In this particular example at least, change A appears to happen as the 
difference in the angle of the two lines passes 0.1degrees. Change B occurs as 
the difference in the angle of the two lines passes 0.6degrees.
Comment 16 Robin Watts 2009-10-14 13:44:29 UTC
(Thanks to Ralph who suggested I look for what exactly triggers this 
behaviour).

Testing another couple of examples suggests it's not purely the relative angle 
of the lines. Scaling an example up by a factor of 2 changes the angle at 
which the changes happen.
Comment 17 Robin Watts 2009-10-15 05:28:42 UTC
One solution that occurs to me; if we make use of the segment_notes, we can 
ensure that we always fill 'under' the curve where required, if the line 
segment comes from a flattened curve.

This would solve the original problem.

It would leave us behaving slightly differently to Acrobat in the case where 
Acrobat is adjusting the ends of strokes between the cutdown example, but this 
may be an acceptable step forwards.

If we figure out exactly what Acrobat is doing in the future, we can extend 
the conidition to make it fill under the curve then too.
Comment 18 Robin Watts 2009-10-16 08:07:00 UTC
Created attachment 5497 [details]
jointest3.ps: Show a range of joins between short strokes

This shows a range of joins between short strokes:

Column 1: Left to right
Column 2: Right to left
Column 3: Left to right, inverted
Column 4: Right to left, inverted

Row 0 (Bottom): Mitre join (0)
Row 1: Round join (1)
Row 2: Bevel join (2)
Row 3: Very small curve, with mitre joins
Row 4: Very small curve, with round joins
Row 5 (Top): Very small curve, with bevel joins.
Comment 19 Henry Stiles 2009-10-16 09:04:34 UTC
Created attachment 5498 [details]
jointest3_ApplePreview.png
Comment 20 Ralph Giles 2009-10-16 09:13:58 UTC
Created attachment 5499 [details]
jointest3-evince.png

attaching the results of running jointest3.ps through pdfwrite, and then
rendered with evince (libpoppler + cairo). Looks substantially the same as
Apple Preview.
Comment 21 Robin Watts 2009-11-11 13:02:12 UTC
Reassigning to me, as I have a plan to fix this.
Comment 22 Robin Watts 2009-11-25 16:48:26 UTC
Fixed in revision 10385.