Bug 689222 - Clip operator producing null clip region when path has int coords
Summary: Clip operator producing null clip region when path has int coords
Status: RESOLVED FIXED
Alias: None
Product: Ghostscript
Classification: Unclassified
Component: PS Interpreter (show other bugs)
Version: 8.56
Hardware: PC Linux
: P4 major
Assignee: Robin Watts
URL:
Keywords: bountiable
Depends on:
Blocks:
 
Reported: 2007-05-10 13:47 UTC by Ryan
Modified: 2021-01-02 02:33 UTC (History)
5 users (show)

See Also:
Customer:
Word Size: ---


Attachments
clip.ps (779 bytes, application/postscript)
2009-12-16 14:47 UTC, Robin Watts
Details
patch (6.88 KB, patch)
2009-12-22 16:19 UTC, Robin Watts
Details | Diff
out.pdf (7.97 KB, application/pdf)
2009-12-29 06:38 UTC, Robin Watts
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ryan 2007-05-10 13:47:04 UTC
This peculiar bug is triggered only when the clip operator is executed 
against a perfectly horizontal single line path, where the Y coordinate is an 
integer, or has a fracional component of 0.00196 or less. 

The bug is particularly severe for my application, coz I use "clip clippath" to
basically reduce the current path to the effective path after consulting with 
the existing clip region, and then I print out the resulting path with 
coordinates. So when this bug is triggerd, my application prints out a 
null path.

The bug has been triggered by a real customer PS document, but I'm only 
producing a few lines of sample code to demonstrate the bug visually in 
Ghostscript.

To reproduce the bug visually, copy the following lines of PS into a file
(say clipbug.ps):
-------------------------------------------
clipsave

newpath
20  300.01 moveto
700 300.01 lineto
clip
stroke

cliprestore
clipsave

newpath
20  200.00196 moveto
700 200.00196 lineto
% to demonstrate the bug, change 200.00196 to 200.00197 in the above two lines 
clip
stroke

cliprestore

showpage
-------------------------------------------

Then run gs from the commandline like so:

    gs -r72 clipbug.ps

You will notice just one line on the display instead of two. Now if you change
200.00196 to 200.00197 as indicated in the comment in that file, and then 
run gs again, you will see that both lines are displayed as they should be.

Is there a simple fix for this?
Comment 1 Ryan 2007-05-24 13:29:28 UTC
Added May 24th:

The bug is also triggered when the line is perfectly vertical and the X co'ds
are very close to the integer.  I'm attaching a Ps file that demonstrates both
the vertical and horizontal line bug.


I've also confirmed that the bug occurs when the value of the constant
co-ordinate is close to the integer from both sides. (i.e. 200.00197 will
trigger it as well as 199.99804 )


This bug is occuring for us in documents containing tables which use vertical
hairline-rules to seperate columns and delimit the table boundries.


Copy the following lines into ps file and run it with gs. Instructions on how to
change values and trigger the bug are in PS comments.
---------------------------------------------------------------------
clipsave


% Demo of the bug in a Vertical situation
newpath
200.00195 400 moveto
200.00195 600 lineto
% in the present form no vertical line is displayed.
% change 200.00194 to 200.00195 or more   =>  the vertical line appears
% if you change the value to 199.99804 no vertical line will be displayed
% change 199.99804 to 199.99803 or less   =>  the vertical line appears
clip
stroke

cliprestore
clipsave


% Demo of the bug in a Horizontal situation
newpath
100 200.00196 moveto
300 200.00196 lineto
% in the present form, no horizontal line is displayed.
% change 200.00196 to 200.00197 or more  => the horizontal line appears
% change the value to 199.99806 and the horizontal line will not be displayed
% change 199.99806 to 199.99805 or less  => the horizontal line appears
clip
stroke

cliprestore

showpage
----------------------------------------------------------------------
Comment 2 Ryan 2007-06-14 13:04:03 UTC
As a result of further experimentation, here is the most accurate test case and
numbers that we can come up with so far.

Based on the new findings, I'll redefine the conditions when the problem 
happens:

1) The path contains a perfectly horizontal or perfectly vertical line. 
    The path is a single moveto followed by one or more linetos with any after
    the first one continuing forward in the same direction.

    AND

2) The constant coordinate (Y for horizontal lines, X for vertical lines) falls
    exactly on an integral pixel position in user space.

    AND

3) The line starts out inside the clip region and extends beyond the clip region

   AND

4) The clip operator is used to get the intersection of the current clip
   region with the current path.
    

Here's the "script" listing from the series of commands I ran. It also
includes the test file that I used.

The bug can be seen on the lines that end with "M 0.0 0.0"

If the test script is modified to do a "1.5 1.5 scale" just before drawing the
lines, then the bug still occurs on the same lines, indicating that it's the
user space pixel values that matter and not device space.
------------------------------------------------------------------------------
GPL Ghostscript 8.57 (2007-05-11)
Copyright (C) 2007 artofcode LLC, Benicia, CA.  All rights reserved.
[rep@dinar bin]$ ARGS='-sDEVICE=bbox -dBATCH -dNOPAUSE -q /tmp/cliplinesbug.ps'
[rep@dinar bin]$ cat /tmp/cliplinesbug.ps 

% stdout reporting routines
/std (%stdout) (w) file def
/sw { std exch writestring } def % str write
/aw { 30 string cvs sw } def     % any write
/nl { (\n) sw } def              % newline

% report resolution
currentpagedevice /HWResolution get {} forall pop /res exch def
(RESOLUTION: ) sw res aw nl
(POINTS -> PIXELS:        PATH .... ) sw nl

0 0 600 600 rectclip

/ds{
  200 add
  dup aw ( -> ) sw   % display Y coord 
  dup res 72 div mul aw (:) sw % and equiv in pixels
  clipsave

  newpath
  dup 100 exch moveto
      700 exch lineto

  clip

  gsave
    clippath

    { ( M ) sw exch aw ( ) sw aw }
    { ( L ) sw exch aw ( ) sw aw }
    { (curveto: not reached)   sw }
    { ( C ) sw }
    pathforall

    nl
  grestore

  stroke

  cliprestore
} def


%  /incr  comes from the commandline   e.g. -Dincr=0.2
0 25 { dup ds incr add } repeat
pop

showpage
[rep@dinar bin]$ ./gs -r72  -Dincr=0.1 $ARGS 2>/dev/null
RESOLUTION: 72.0
POINTS -> PIXELS:        PATH .... 
200 -> 200.0: M 0.0 0.0
200.1 -> 200.1: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.2 -> 200.2: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.3 -> 200.3: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.4 -> 200.4: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.5 -> 200.5: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.6 -> 200.6: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.7 -> 200.7: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.8 -> 200.8: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
200.9 -> 200.9: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 
201.0 -> 201.0: M 0.0 0.0
201.1 -> 201.1: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.2 -> 201.2: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.3 -> 201.3: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.4 -> 201.4: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.5 -> 201.5: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.6 -> 201.6: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.7 -> 201.7: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.8 -> 201.8: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
201.9 -> 201.9: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 
202.0 -> 202.0: M 0.0 0.0
202.1 -> 202.1: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 
202.2 -> 202.2: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 
202.3 -> 202.3: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 
202.4 -> 202.4: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 
[rep@dinar bin]$ ./gs -r144 -Dincr=0.1 $ARGS 2>/dev/null
RESOLUTION: 144.0
POINTS -> PIXELS:        PATH .... 
200 -> 400.0: M 0.0 0.0
200.1 -> 400.2: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 
200.2 -> 400.4: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 
200.3 -> 400.6: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 
200.4 -> 400.8: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 
200.5 -> 401.0: M 0.0 0.0
200.6 -> 401.2: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 
200.7 -> 401.4: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 
200.8 -> 401.6: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 
200.9 -> 401.8: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 
201.0 -> 402.0: M 0.0 0.0
201.1 -> 402.2: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 
201.2 -> 402.4: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 
201.3 -> 402.6: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 
201.4 -> 402.8: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 
201.5 -> 403.0: M 0.0 0.0
201.6 -> 403.2: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 
201.7 -> 403.4: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 
201.8 -> 403.6: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 
201.9 -> 403.8: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 
202.0 -> 404.0: M 0.0 0.0
202.1 -> 404.2: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 
202.2 -> 404.4: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 
202.3 -> 404.6: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 
202.4 -> 404.8: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 
[rep@dinar bin]$ ./gs -r720 -Dincr=0.1 $ARGS 2>/dev/null
RESOLUTION: 720.0
POINTS -> PIXELS:        PATH .... 
200 -> 2000.0: M 0.0 0.0
200.1 -> 2001.0: M 0.0 0.0
200.2 -> 2002.0: M 0.0 0.0
200.3 -> 2003.0: M 0.0 0.0
200.4 -> 2004.0: M 0.0 0.0
200.5 -> 2005.0: M 0.0 0.0
200.6 -> 2006.0: M 0.0 0.0
200.7 -> 2007.0: M 0.0 0.0
200.8 -> 2008.0: M 0.0 0.0
200.9 -> 2009.0: M 0.0 0.0
201.0 -> 2010.0: M 0.0 0.0
201.1 -> 2011.0: M 0.0 0.0
201.2 -> 2012.0: M 0.0 0.0
201.3 -> 2013.0: M 0.0 0.0
201.4 -> 2014.0: M 0.0 0.0
201.5 -> 2015.0: M 0.0 0.0
201.6 -> 2016.0: M 0.0 0.0
201.7 -> 2017.0: M 0.0 0.0
201.8 -> 2018.0: M 0.0 0.0
201.9 -> 2019.0: M 0.0 0.0
202.0 -> 2020.0: M 0.0 0.0
202.1 -> 2021.0: M 0.0 0.0
202.2 -> 2022.0: M 0.0 0.0
202.3 -> 2023.0: M 0.0 0.0
202.4 -> 2024.0: M 0.0 0.0
[rep@dinar bin]$ ./gs -r720 -Dincr=0.01 $ARGS 2>/dev/null
RESOLUTION: 720.0
POINTS -> PIXELS:        PATH .... 
200 -> 2000.0: M 0.0 0.0
200.01 -> 2000.1: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.02 -> 2000.2: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.03 -> 2000.3: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.04 -> 2000.4: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.05 -> 2000.5: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.06 -> 2000.6: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.07 -> 2000.7: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.08 -> 2000.8: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.09 -> 2000.9: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 
200.1 -> 2001.0: M 0.0 0.0
200.11 -> 2001.1: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.12 -> 2001.2: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.13 -> 2001.3: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.14 -> 2001.4: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.15 -> 2001.5: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.16 -> 2001.6: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.17 -> 2001.7: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.18 -> 2001.8: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.19 -> 2001.9: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 
200.2 -> 2002.0: M 0.0 0.0
200.21 -> 2002.1: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 
200.22 -> 2002.2: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 
200.23 -> 2002.3: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 
200.24 -> 2002.4: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 
[rep@dinar bin]$ ./gs -r143 -Dincr=0.179 $ARGS 2>/dev/null
RESOLUTION: 143.0
POINTS -> PIXELS:        PATH .... 
200 -> 397.222: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L 600.168
199.888 C 
200.179 -> 397.578: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L
600.168 199.888 C 
200.358 -> 397.933: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L
600.168 199.888 C 
200.537 -> 398.289: M 99.6923 200.392 L 99.6923 200.895 L 600.168 200.895 L
600.168 200.392 C 
200.716 -> 398.644: M 99.6923 200.392 L 99.6923 200.895 L 600.168 200.895 L
600.168 200.392 C 
200.895 -> 399.0: M 0.0 0.0
201.074 -> 399.355: M 99.6923 200.895 L 99.6923 201.399 L 600.168 201.399 L
600.168 200.895 C 
201.253 -> 399.711: M 99.6923 200.895 L 99.6923 201.399 L 600.168 201.399 L
600.168 200.895 C 
201.432 -> 400.066: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L
600.168 201.399 C 
201.611 -> 400.422: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L
600.168 201.399 C 
201.79 -> 400.777: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L
600.168 201.399 C 
201.969 -> 401.133: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L
600.168 201.902 C 
202.148 -> 401.488: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L
600.168 201.902 C 
202.327 -> 401.844: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L
600.168 201.902 C 
202.506 -> 402.199: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L
600.168 202.406 C 
202.685 -> 402.555: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L
600.168 202.406 C 
202.864 -> 402.91: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L
600.168 202.406 C 
203.043 -> 403.266: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L
600.168 202.909 C 
203.222 -> 403.621: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L
600.168 202.909 C 
203.401 -> 403.977: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L
600.168 202.909 C 
203.58 -> 404.333: M 99.6923 203.413 L 99.6923 203.916 L 600.168 203.916 L
600.168 203.413 C 
203.759 -> 404.688: M 99.6923 203.413 L 99.6923 203.916 L 600.168 203.916 L
600.168 203.413 C 
203.938 -> 405.044: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L
600.168 203.916 C 
204.117 -> 405.399: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L
600.168 203.916 C 
204.296 -> 405.755: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L
600.168 203.916 C 
[rep@dinar bin]$ exit

Script done on Thu 14 Jun 2007 11:04:41 AM EDT

Comment 3 Henry Stiles 2009-11-19 07:50:33 UTC
Reassigning path and fill problems to Robin Watts.
Comment 4 Robin Watts 2009-12-13 10:31:42 UTC
I've investigated this a bit, and the problem is related to our use of fill_adjust.

The clip operation works by clipping the current path against the current
clipping path. This is implemented in ghostscript by plotting the clipping path
through a device that clips against the clipping path.

Unfortunately this uses the current values of fill_adjust. To give consistent
results, I suspect we should override the values of fill_adjust for the duration
of the call.

If we take fill adjust figures of 0,0 then completely horizontal/vertical paths
clip to give completely empty paths irrespective of pixel positions. This is, I
think, a valid implementation of the spec, as the enclosed area of the path is
empty (i.e. the same).

Unfortunately this does not match the results that Adobe gives.

Adobe clips these paths so as to give the same completely horizontal/vertical
paths, just restricted to being within the same area. We can get the same
results as this (almost) by using fill adjust figures of 0.5,0.5. The only
difference occurs when the path is precisely aligned to pixel boundaries,
whereupon we get the same empty path as given by 0,0.

I believe the difference here is due to the fact that we treat 0.5,0.5 as a
special value meaning left/bottom adjust of -.5 and a top/right adjust of
0.49999 so as to give an open region.
Comment 5 Robin Watts 2009-12-16 14:47:57 UTC
Created attachment 5786 [details]
clip.ps

A file to show the problem.
Comment 6 Robin Watts 2009-12-22 16:19:34 UTC
Created attachment 5802 [details]
patch

A patch to solve this problem.

fill_adjust already has one special case value (-1, meaning 0,0,0,0 for the
adjustments). This introduces another such value, -2, meaning the closed
0.5,0.5,0.5,0.5 region, rather than the more usual half open region.

This patch cures the problem with clip.ps.

Regression testing gives results which differ slightly (as you'd expect). They
aren't a complete match, but them *seem* like an improvement or neutral.

I know this propogates (and further complicates) the hated fill_adjust, but
uploading here for comments.
Comment 7 Robin Watts 2009-12-29 06:36:47 UTC
It has been suggested that an alternative (simpler) fix for this would simply 
be to enable the existing code protected by #ifdef FILL_ZERO_WIDTH. That code 
was introduced by Ray a while ago in response to a customer bug.

It seems like a tempting solution, as mathematically I think it will do the 
right thing.

Sadly, testing has revealed some problems with it, in that it exposes a 
mismatch between different output devices. I'll attach a file to show this 
problem up.
Comment 8 Robin Watts 2009-12-29 06:38:39 UTC
Created attachment 5817 [details]
out.pdf

A cutdown file that shows distinct differences when rendered at 300dpi using
the display device and the bmp16m device.

I suspect the problem is to do with the fact that the bmp16m device vertically
flips the output.
Comment 9 Robin Watts 2010-01-28 17:22:15 UTC
Reassigning to new email address.
Comment 10 James Cloos 2013-12-07 12:26:38 UTC
This seems to have been fixed as of 9.10.

The initial example, at least shows two lines now.
Comment 11 Peter Cherepanov 2021-01-02 02:33:38 UTC
All these problems were fixed in v.9.22 but the bug report was not updated.