Posts Tagged ‘pycairo’

pycairo и сплайны

Июнь 24, 2009

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

curve_

Правильно нарисован только верхний ряд. Нижний и вертикальный сгладились как-то шиворот-навыворот.

Исправил функцию 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

И вот результат:

curve

Программа, рисующая эту картинку:


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')