Skip to content

Gaps sometimes visible between filled paths which share an edge #542

@patternspandemic

Description

@patternspandemic

Greetings, and thank you for sharing Pixie!

I am not sure if this is to be expected or not (or even whether cairo shows similar behavior), although there are inconsistencies within pixie's behavior at minimum. Here is a 4x2 grid showcasing the behavior. Each cell is essentially filled with two white triangles, and sometimes a gap is visible between them even though their shared edge is specified from the same points. In case it is not obvious which cells due to monitor dpi or brightness:

|--------|-----------|-----------|--------|
| no gap | light gap | light gap | no gap |
|--------|-----------|-----------|--------|
|   gap  | light gap | light gap |   gap  |
|--------|-----------|-----------|--------|

fillIssue

For the top row, cw, and ccw refers to whether a triangle's path was specified in a clockwise or counter-clockwise manner. Each top row example is a single filled path.

For the second row, each example left to right:

  • Two paths separately filled (one for each triangle).
  • The same two paths added to another path which is filled.
  • The triangles defined in a single SVG string which is filled.
  • Each triangle filled from a separate SVG string.

Vertical and horizontal fills don't seem to exhibit this behavior, only diagonals.

The code which produced this image:

import pixie

var
  image = newImage(800, 400)
  cwcw, cwccw, ccwcw, ccwccw, p1, p2, p = newPath()
  font = readFont("Hack-Regular.ttf")

font.size = 18
image.fill("black")

#[ ROW 1 ]#

# cw, cw: no gap visible
cwcw.moveTo(0.0, 0.0)
cwcw.lineTo(200.0, 0.0)
cwcw.lineTo(200.0, 200.0)
cwcw.lineTo(0.0, 0.0)
cwcw.moveTo(200.0, 200.0)
cwcw.lineTo(0.0, 200.0)
cwcw.lineTo(0.0, 0.0)
cwcw.lineTo(200.0, 200.0)
image.fillPath(cwcw, "white")
image.fillText(font.typeset("cw, cw", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(0, 100)))

# cw, ccw: light gap visible
cwccw.moveTo(0.0, 0.0)
cwccw.lineTo(200.0, 200.0)
cwccw.lineTo(0.0, 200.0)
cwccw.lineTo(0.0, 0.0)
cwccw.moveTo(200.0, 200.0)
cwccw.lineTo(200.0, 0.0)
cwccw.lineTo(0.0, 0.0)
cwccw.lineTo(200.0, 200.0)
image.fillPath(cwccw, "white", translate(vec2(200, 0)))
image.fillText(font.typeset("cw, ccw", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(200, 100)))

# ccw, cw: light gap visible
ccwcw.moveTo(0.0, 0.0)
ccwcw.lineTo(0.0, 200.0)
ccwcw.lineTo(200.0, 200.0)
ccwcw.lineTo(0.0, 0.0)
ccwcw.moveTo(200.0, 200.0)
ccwcw.lineTo(0.0, 0.0)
ccwcw.lineTo(200.0, 0.0)
ccwcw.lineTo(200.0, 200.0)
image.fillPath(ccwcw, "white", translate(vec2(400, 0)))
image.fillText(font.typeset("ccw, cw", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(400, 100)))

# ccw, ccw: no gap visible
ccwccw.moveTo(0.0, 0.0)
ccwccw.lineTo(0.0, 200.0)
ccwccw.lineTo(200.0, 200.0)
ccwccw.lineTo(0.0, 0.0)
ccwccw.moveTo(200.0, 200.0)
ccwccw.lineTo(200.0, 0.0)
ccwccw.lineTo(0.0, 0.0)
ccwccw.lineTo(200.0, 200.0)
image.fillPath(ccwccw, "white", translate(vec2(600, 0)))
image.fillText(font.typeset("ccw, ccw", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(600, 100)))


#[ ROW 2 ]#

# p1, p2
# clear gap visible
p1.moveTo(0.0, 0.0)
p1.lineTo(0.0, 200.0)
p1.lineTo(200.0, 200.0)
p1.lineTo(0.0, 0.0)
p2.moveTo(200.0, 200.0)
p2.lineTo(0.0, 0.0)
p2.lineTo(200.0, 0.0)
p2.lineTo(200.0, 200.0)
image.fillPath(p1, "white", translate(vec2(0, 200)))
image.fillPath(p2, "white", translate(vec2(0, 200)))
image.fillText(font.typeset("p1, p2", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(0, 300)))

# p
# light gap visible
p.addPath(p1)
p.addPath(p2)
image.fillPath(p, "white", translate(vec2(200, 200)))
image.fillText(font.typeset("p", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(200, 300)))

# 1 SVG string
# light gap visible
image.fillPath(
  """
    M 0.0 0.0
    L 200.0 200.0
    L 0.0 200.0
    L 0.0 0.0
    M 200.0 200.0
    L 200.0 0.0
    L 0.0 0.0
    L 200.0 200.0
  """, "white", translate(vec2(400, 200)))
image.fillText(font.typeset("1 SVG string", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(400, 300)))

# 2 SVG string
# clear gap visible
image.fillPath(
  """
    M 0 0
    L 200 200
    L 0 200
    L 0 0
  """, "white", translate(vec2(600, 200)))
image.fillPath(
  """
    M 200 200
    L 200 0
    L 0 0
    L 200 200
  """, "white", translate(vec2(600, 200)))
image.fillText(font.typeset("2 SVG string", vec2(200, 100), hAlign = CenterAlign, vAlign = MiddleAlign), translate(vec2(600, 300)))

image.writeFile("fillIssue.png")

In an image full of filled paths, this behavior makes it look as though strokes were applied when they were not. For example:

example

If you look closely, there are a few spots in the above image where 'strokes' seem to be missing. These are areas where shared edges are closely vertical or horizontal.

I assume this has everything to do with AA of fills. Thanks for taking a look.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions