Вот тут есть неплохой пост про использование pycairo для сглаживания ломанных линий в сплайны Безье. Вот только код не работает если попытаться сгладить вот такую ломанную:

Правильно нарисован только верхний ряд. Нижний и вертикальный сгладились как-то шиворот-навыворот.
Исправил функцию prepare_curve_data с использованием библиотеки vec2d
def prepare_curve_data(graph_data):
from vec2d import *
prepared_data = []
for i in range(len(graph_data)):
x, y = graph_data[i][0], graph_data[i][1]
cur_point = vec2d(graph_data[i][0], graph_data[i][1])
if (i != 0) and (i != len(graph_data) - 1):
back_point = vec2d(graph_data[i - 1][0], graph_data[i - 1][1])
forw_point = vec2d(graph_data[i + 1][0], graph_data[i + 1][1])
back_vect = back_point - cur_point
forw_vect = forw_point - cur_point
back_vect_proj = back_vect / 2.0
forw_vect_proj = forw_vect / 2.0
angle_between = back_vect.get_angle_between(forw_vect) / 2.0
back_vect.rotate(angle_between - 90)
forw_vect.rotate(90 - angle_between)
back_vect = back_vect_proj.projection(back_vect)
forw_vect = forw_vect_proj.projection(forw_vect)
back_cpoint = cur_point + back_vect
forw_cpoint = cur_point + forw_vect
cx1, cy1 = back_cpoint[0], back_cpoint[1]
cx2, cy2 = forw_cpoint[0], forw_cpoint[1]
else:
cx1, cy1, cx2, cy2 = x, y, x, y
prepared_data.append((x, y, cx1, cy1, cx2, cy2))
return prepared_data
И вот результат:

Программа, рисующая эту картинку:
import cairo
from math import pi, sqrt
width = 500
height = 500
graph_data = [
(0, 10),(20, 50),(40, 80),(60, 5),(80, 10),(100, 20),
(120, 30),(140, 60),(160, 95),(180, 30),(200, 50),
(220, 70),(240, 80),(260, 10),(280, 60),(300, 30),
(320, 90),(340, 95),(360, 30),(380, 10),(400, 5),
(420, 20),(440, 80),(460, 70),(480, 20),(500, 40),
(490, 0), (450, 20), (420, 40), (495, 60), (490, 80),
(480, 100), (470, 120), (440, 140), (405, 160),
(470, 180), (450, 200), (430, 220), (420, 240),
(490, 260), (440, 280), (470, 300), (410, 320),
(405, 340), (470, 360), (490, 380), (495, 400),
(480, 420), (420, 440), (430, 460), (480, 480), (460, 500),
(500, 490), (480, 450), (460, 420), (440, 495), (420, 490),
(400, 480), (380, 470), (360, 440), (340, 405), (320, 470),
(300, 450), (280, 430), (260, 420), (240, 490), (220, 440),
(200, 470), (180, 410), (160, 405), (140, 470), (120, 490),
(100, 495), (80, 480), (60, 420), (40, 430), (20, 480), (0, 460)
]
def prepare_curve_data(graph_data):
from vec2d import *
prepared_data = []
for i in range(len(graph_data)):
x, y = graph_data[i][0], graph_data[i][1]
cur_point = vec2d(graph_data[i][0], graph_data[i][1])
if (i != 0) and (i != len(graph_data) - 1):
back_point = vec2d(graph_data[i - 1][0], graph_data[i - 1][1])
forw_point = vec2d(graph_data[i + 1][0], graph_data[i + 1][1])
back_vect = back_point - cur_point
forw_vect = forw_point - cur_point
back_vect_proj = back_vect / 2.0
forw_vect_proj = forw_vect / 2.0
angle_between = back_vect.get_angle_between(forw_vect) / 2.0
back_vect.rotate(angle_between - 90)
forw_vect.rotate(90 - angle_between)
back_vect = back_vect_proj.projection(back_vect)
forw_vect = forw_vect_proj.projection(forw_vect)
back_cpoint = cur_point + back_vect
forw_cpoint = cur_point + forw_vect
cx1, cy1 = back_cpoint[0], back_cpoint[1]
cx2, cy2 = forw_cpoint[0], forw_cpoint[1]
else:
cx1, cy1, cx2, cy2 = x, y, x, y
prepared_data.append((x, y, cx1, cy1, cx2, cy2))
return prepared_data
def draw_point(x, y, opacity):
cr.move_to(x + 2, y)
cr.arc(x, y, 2, 0, 2 * pi)
cr.set_source_rgba(0, 0, 1, opacity)
cr.stroke()
def debug_points(cr, prepared_data):
for i in range(0, len(prepared_data)):
x, y = prepared_data[i][0], prepared_data[i][1]
cx1, cy1 = prepared_data[i][2], prepared_data[i][3]
cx2, cy2 = prepared_data[i][4], prepared_data[i][5]
draw_point(x, y, 1)
if cx1 != x or cy1 != y:
draw_point(cx1, cy1, 0.3)
cr.move_to(x, y)
cr.line_to(cx1, cy1)
cr.set_source_rgb(0.5, 0.5, 0.5)
cr.stroke()
if cx2 != x or cy2 != y:
draw_point(cx2, cy2, 0.3)
cr.move_to(x, y)
cr.line_to(cx2, cy2)
cr.set_source_rgb(0.5, 0.5, 0.5)
cr.stroke()
def poly_curve(cr, prepared_data):
for i in range(0, len(prepared_data) - 1):
x, y = prepared_data[i][0], prepared_data[i][1]
cx1, cy1 = prepared_data[i][4], prepared_data[i][5]
cx2, cy2 = prepared_data[i + 1][2], prepared_data[i + 1][3]
x2, y2 = prepared_data[i + 1][0], prepared_data[i + 1][1]
cr.move_to(x, y)
cr.curve_to(cx1, cy1, cx2, cy2, x2, y2)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
cr = cairo.Context(surface)
cr.set_line_width(1)
cr.set_source_rgb(1, 1, 1)
cr.set_operator (cairo.OPERATOR_SOURCE)
cr.paint()
prepared_data = prepare_curve_data(graph_data)
debug_points(cr, prepared_data)
cr.set_line_width(2)
poly_curve(cr, prepared_data)
cr.set_source_rgb(0, 0, 0)
cr.stroke()
surface.write_to_png('curve.png')




