curve_to renders incorrectly
morpho-matters opened this issue · 1 comments
morpho-matters commented
Pycairo seems to be incorrectly rendering certain cubic Bezier splines. The joins are incorrect and the line width changes.
Here is the code to reproduce the effect:
import cairo, ctypes
pts = [(5.1502081, 3.69203566),
(5.15072707, 3.88650738), (4.98411476, 3.88754198), (4.82523962, 3.88857803),
(4.82529145, 3.88868164), (1.92531232, 3.88878525), (1.92536413, 3.88888889),
(1.72264052, 3.88888889), (1.42242387, 3.88888889), (1.01697664, 3.45713401)
]
def main():
width, height = 600, 600
renderData = (ctypes.c_ubyte * (width*height*4))()
stride = width*4
surface = cairo.ImageSurface.create_for_data(renderData, cairo.FORMAT_ARGB32,
width, height, stride
)
context = cairo.Context(surface)
context.set_line_join(cairo.LINE_JOIN_ROUND)
context.set_source_rgba(0,0,0,1)
context.set_operator(cairo.OPERATOR_SOURCE)
context.paint()
context.scale(100, 100)
context.move_to(*pts[0])
for n in range(1, len(pts), 3):
x1,y1 = pts[n]
x2,y2 = pts[n+1]
x,y = pts[n+2]
context.curve_to(x1,y1, x2,y2, x,y)
context.set_line_width(0.1)
context.set_source_rgba(1,1,1,1)
context.stroke()
context.get_target().write_to_png("bug.png")
main()
This is running on Python 3.8.1 on Windows 7 with Pycairo 1.23.0.
I suspect this has something to do with how the middle segment is approximately linear (two of the tangent points are almost exactly coincident with their corresponding positional points) but I don't really know. Any help appreciated!
psychon commented
Tried to minimize this a bit. Dunno if this is much better than the original. I guess cairo-the-C-library has some kind of over/underflow somewhere.
import cairo, ctypes, math
def main():
width, height = 600, 600
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
context = cairo.Context(surface)
context.scale(100, 100)
# The actual curve that breaks
context.curve_to(4.82529145, 3.88868164,
1.92531232, 3.88878525,
1.92536413, 3.88888889)
# Just for comparison, a "proper" line with this line width
if False:
context.rel_line_to(0, -1)
context.set_line_width(0.1)
context.set_source_rgb(1,1,1)
context.stroke()
# Show the control points for the curve
if False:
for i, (x, y) in enumerate(
[(4.82529145, 3.88868164),
(1.92531232, 3.88878525),
(1.92536413, 3.88888889)]):
color = [0, 0, 0, 0.5]
color[i] = 1
context.set_source_rgba(*color)
context.arc(x, y, 0.05, 0, 2*math.pi)
context.fill()
context.get_target().write_to_png("bug.png")
main()