def get_cap_segment(startpoint, endpoint, captype): """ This returns a list of bezier segments that form the end cap of a line. Valid captypes are: CAP_BUTT, CAP_ROUND, CAP_SQUARE """ # =====| if captype == sk2const.CAP_BUTT: return [startpoint, endpoint] # =====) elif captype == sk2const.CAP_ROUND: f = CIRCLE_CONSTANT d = mult_point(sub_points(endpoint, startpoint), 0.5) o = [d[1], -d[0]] new_p1 = add_points(startpoint, o) new_p2 = mult_point(o, f) return [ startpoint, add_points(startpoint, new_p2), add_points(new_p1, mult_point(d, 1 - f)), add_points(new_p1, d), add_points(new_p1, mult_point(d, 1 + f)), add_points(endpoint, new_p2), endpoint ] # =====] elif captype == sk2const.CAP_SQUARE: d = mult_point(sub_points(endpoint, startpoint), 0.5) o = [d[1], -d[0]] new_p1 = add_points(startpoint, o) new_p2 = add_points(endpoint, o) return [ startpoint, startpoint, new_p1, new_p1, new_p1, new_p2, new_p2, new_p2, endpoint, endpoint ] else: raise 'Unknown captype %d' % captype
def create_stroke_outline(path, radius, linejoin=sk2const.JOIN_MITER, captype=sk2const.CAP_BUTT, miter_limit=MITER_LIMIT): """ Outlines a single stroke. Returns two lists of lists of bezier segments for both sides of the stroke. """ fw_segments = [] bw_segments = [] last_point = None segs = [path[0], ] + path[1] startpoint = [] + path[0] for i in range(len(segs)): segment = unpack_seg(segs[i], startpoint) startpoint = bezier_base_point(segs[i]) if not segment[0]: if last_point: c1 = sub_points(segment[2], last_point) if not c1 == [0.0]: t1 = mult_point(normalize(c1), radius) fw_segments.append( [add_points(last_point, [t1[1], -t1[0]]), add_points(segment[2], [t1[1], -t1[0]])]) bw_segments.insert(0, [sub_points(segment[2], [t1[1], -t1[0]]), sub_points(last_point, [t1[1], -t1[0]])]) last_point = segment[2] else: segments = build_parallel([last_point, segment[1][0], segment[1][1], segment[2]], radius) fw_segments.append(segments) segments = build_parallel([segment[2], segment[1][1], segment[1][0], last_point], radius) bw_segments.insert(0, segments) last_point = segment[2] # Connect segments if necessary for item in [fw_segments, bw_segments]: join_segs(item, radius, linejoin, miter_limit, path[2] == sk2const.CURVE_CLOSED) # Set caps for unclosed paths if not path[2] == sk2const.CURVE_CLOSED: fw_segments.insert(0, get_cap_segment(bw_segments[-1][-1], fw_segments[0][0], captype)) bw_segments.insert(0, get_cap_segment(fw_segments[-1][-1], bw_segments[0][0], captype)) return fw_segments, bw_segments
def get_cap_segment (startpoint, endpoint, captype): """ This returns a list of bezier segments that form the end cap of a line. Valid captypes are: CAP_BUTT, CAP_ROUND, CAP_SQUARE """ # =====| if captype == sk2_const.CAP_BUTT: return [startpoint, endpoint] # =====) elif captype == sk2_const.CAP_ROUND: f = CIRCLE_CONSTANT d = mult_point(sub_points(endpoint, startpoint), 0.5) o = [d[1], -d[0]] new_p1 = add_points(startpoint, o) new_p2 = mult_point(o, f) return [startpoint, add_points(startpoint, new_p2), add_points(new_p1, mult_point(d, 1 - f)), add_points(new_p1, d), add_points(new_p1, mult_point(d, 1 + f)), add_points(endpoint, new_p2), endpoint] # =====] elif captype == sk2_const.CAP_SQUARE: d = mult_point(sub_points(endpoint, startpoint), 0.5) o = [d[1], -d[0]] new_p1 = add_points(startpoint, o) new_p2 = add_points(endpoint, o) return [startpoint, startpoint, new_p1, new_p1, new_p1, new_p2, new_p2, new_p2, endpoint, endpoint] else: raise "Unknown captype %d" % captype
def build_parallel(p, radius, recursionlimit=6): """ This builds a list of bezier segments that are "sufficiently" close to a given source segment. It recursively subdivides, if the check for parallelity fails. """ # find tangent to calculate orthogonal neighbor of endpoint for i in p[1:]: c1 = sub_points(i, p[0]) if c1: break if c1 == [0, 0]: return [] t1 = mult_point(normalize(c1), radius) p0 = add_points(p[0], [t1[1], -t1[0]]) c1 = sub_points(p[1], p[0]) for i in [p[2], p[1], p[0]]: c2 = sub_points(p[3], i) if not c2 == [0, 0]: break t2 = mult_point(normalize(c2), radius) p3 = add_points(p[3], [t2[1], -t2[0]]) c2 = sub_points(p[3], p[2]) sd = subdivide_seg(p) center = sd[3] ccenter = mult_point(normalize(sub_points(sd[4], sd[3])), radius) new_center = add_points(center, [ccenter[1], -ccenter[0]]) seg = [p0, add_points(p0, c1), sub_points(p3, c2), p3] now_center = subdivide_seg(seg)[3] offset = mult_point(sub_points(new_center, now_center), 8.0 / 3) det = c1[0] * c2[1] - c1[1] * c2[0] if det: ndet = det / length(c1) / length(c2) else: ndet = 0 if math.fabs(ndet) >= 0.1: # "sufficiently" linear independent, cramers rule: oc1 = mult_point(c1, ((offset[0] * c2[1] - offset[1] * c2[0]) / det)) oc2 = mult_point(c2, ((c1[0] * offset[1] - c1[1] * offset[0]) / det)) else: # don't bother to try to correct the error, will figure out # soon if subdivision is necessary. oc1 = [0.0, 0.0] oc2 = [0.0, 0.0] new_p1 = add_points(add_points(p0, c1), oc1) new_p2 = add_points(sub_points(p3, c2), oc2) proposed_segment = [p0, new_p1, new_p2, p3] if check_parallel(p, proposed_segment, radius) or recursionlimit <= 0: return proposed_segment else: # "Not parallel enough" - subdivide. return (build_parallel(sd[:4], radius, recursionlimit - 1) + build_parallel(sd[3:], radius, recursionlimit - 1)[1:])
def build_parallel(p, radius, recursionlimit=6): """ This builds a list of bezier segments that are "sufficiently" close to a given source segment. It recursively subdivides, if the check for parallelity fails. """ # find tangent to calculate orthogonal neighbor of endpoint for i in p[1:]: c1 = sub_points(i, p[0]) if c1: break if c1 == [0, 0]: return [] t1 = mult_point(normalize(c1), radius) p0 = add_points(p[0], [t1[1], -t1[0]]) c1 = sub_points(p[1], p[0]) for i in [p[2], p[1], p[0]]: c2 = sub_points(p[3], i) if not c2 == [0, 0]: break t2 = mult_point(normalize(c2), radius) p3 = add_points(p[3], [t2[1], -t2[0]]) c2 = sub_points(p[3], p[2]) sd = subdivide_seg(p) center = sd[3] ccenter = mult_point(normalize(sub_points(sd[4], sd[3])), radius) new_center = add_points(center, [ccenter[1], -ccenter[0]]) seg = [p0, add_points(p0, c1), sub_points(p3, c2), p3] now_center = subdivide_seg(seg)[3] offset = mult_point(sub_points(new_center, now_center), 8.0 / 3) det = c1[0] * c2[1] - c1[1] * c2[0] if det: ndet = det / length(c1) / length(c2) else: ndet = 0 if math.fabs(ndet) >= 0.1: # "sufficiently" linear independent, cramers rule: oc1 = mult_point(c1, ((offset[0] * c2[1] - offset[1] * c2[0]) / det)) oc2 = mult_point(c2, ((c1[0] * offset[1] - c1[1] * offset[0]) / det)) else: # don't bother to try to correct the error, will figure out # soon if subdivision is necessary. oc1 = [0.0, 0.0] oc2 = [0.0, 0.0] new_p1 = add_points(add_points(p0, c1), oc1) new_p2 = add_points(sub_points(p3, c2), oc2) proposed_segment = [p0, new_p1, new_p2, p3] if check_parallel (p, proposed_segment, radius) or recursionlimit <= 0: return proposed_segment else: # "Not parallel enough" - subdivide. return (build_parallel (sd[:4], radius, recursionlimit - 1) + build_parallel (sd[3:], radius, recursionlimit - 1)[1:])
def create_stroke_outline (path, radius, linejoin=sk2_const.JOIN_MITER, captype=sk2_const.CAP_BUTT, miter_limit=MITER_LIMIT): """ Outlines a single stroke. Returns two lists of lists of bezier segments for both sides of the stroke. """ fw_segments = [] bw_segments = [] last_point = None segs = [path[0], ] + path[1] startpoint = [] + path[0] for i in range (len(segs)): segment = unpack_seg(segs[i], startpoint) startpoint = bezier_base_point(segs[i]) if not segment[0]: if last_point: c1 = sub_points(segment[2], last_point) if not c1 == [0.0]: t1 = mult_point(normalize(c1), radius) fw_segments.append( [add_points(last_point, [t1[1], -t1[0]]), add_points(segment[2], [t1[1], -t1[0]])]) bw_segments.insert(0, [sub_points(segment[2], [t1[1], -t1[0]]), sub_points(last_point, [t1[1], -t1[0]])]) last_point = segment[2] else: segments = build_parallel([last_point, segment[1][0], segment[1][1], segment[2]], radius) fw_segments.append(segments) segments = build_parallel([segment[2], segment[1][1], segment[1][0], last_point], radius) bw_segments.insert(0, segments) last_point = segment[2] # Connect segments if necessary for item in [fw_segments, bw_segments]: join_segs(item, radius, linejoin, miter_limit, path[2] == sk2_const.CURVE_CLOSED) # Set caps for unclosed paths if not path[2] == sk2_const.CURVE_CLOSED: fw_segments.insert(0, get_cap_segment (bw_segments[-1][-1], fw_segments[0][0], captype)) bw_segments.insert(0, get_cap_segment (fw_segments[-1][-1], bw_segments[0][0], captype)) return fw_segments, bw_segments
def check_parallel(source, parallel, radius, tolerance=0.01): """ This function checks, if two bezier segments are "sufficiently" parallel. It checks, if the points for the parameters 0.25, 0.5 and 0.75 of the tested segment are orthogonal to the resp. points of the source segment. 1% tolerance is default. It does not check the start and endpoints, since they are assumed to be correct by construction. """ for t0 in [0.25, 0.5, 0.75]: s = subdivide_seg(source, t0) t = subdivide_seg(parallel, t0) ccenter = mult_point(normalize(sub_points(s[4], s[3])), radius) orig = add_points(s[3], [ccenter[1], -ccenter[0]]) if length(sub_points(orig, t[3])) >= tolerance * radius: return False return True
def split_bezier_curve(start_point, end_point, t=0.5): p0 = [] + start_point if len(start_point) > 2: p0 = [] + start_point[2] p1, p2, p3, flag = deepcopy(end_point) p0_1 = add_points(mult_point(p0, (1.0 - t)), mult_point(p1, t)) p1_2 = add_points(mult_point(p1, (1.0 - t)), mult_point(p2, t)) p2_3 = add_points(mult_point(p2, (1.0 - t)), mult_point(p3, t)) p01_12 = add_points(mult_point(p0_1, (1.0 - t)), mult_point(p1_2, t)) p12_23 = add_points(mult_point(p1_2, (1.0 - t)), mult_point(p2_3, t)) p0112_1223 = add_points(mult_point(p01_12, (1.0 - t)), mult_point(p12_23, t)) new_point = [p0_1, p01_12, p0112_1223, flag] new_end_point = [p12_23, p2_3, p3, flag] return new_point, new_end_point
def subdivide_seg(seg, t=0.5): """ This subdivides a bezier segment at the (optional) Parameter t """ assert len(seg) == 4 t2 = 1.0 - t p01 = add_points(mult_point(seg[0], t2), mult_point(seg[1], t)) p12 = add_points(mult_point(seg[1], t2), mult_point(seg[2], t)) p23 = add_points(mult_point(seg[2], t2), mult_point(seg[3], t)) p012 = add_points(mult_point(p01, t2), mult_point(p12, t)) p123 = add_points(mult_point(p12, t2), mult_point(p23, t)) p0123 = add_points(mult_point(p012, t2), mult_point(p123, t)) return seg[0], p01, p012, p0123, p123, p23, seg[3]
def split_seg(self, base_point, point, t=0.5): if is_bezier(point): p0 = base_point p1, p2, p3 = point[:3] t2 = 1.0 - t r = add_points(mult_point(p1, t2), mult_point(p2, t)) q1 = add_points(mult_point(p0, t2), mult_point(p1, t)) q2 = add_points(mult_point(q1, t2), mult_point(r, t)) q5 = add_points(mult_point(p2, t2), mult_point(p3, t)) q4 = add_points(mult_point(r, t2), mult_point(q5, t)) q3 = add_points(mult_point(q2, t2), mult_point(q4, t)) return [[q1, q2, [] + q3, 0], q3, [q4, q5, [] + p3, 0]] else: new_point = add_points(mult_point(base_point, (1.0 - t)), mult_point(point, t)) return [new_point, [] + new_point, None]
def get_join_segment(startpoint, endpoint, radius, jointype, miter_limit=MITER_LIMIT): """ This returns a list of bezier segments that joins two points with a given radius (fails if the radius is smaller than the distance between startpoint) and endpoint). jointype is one of JOIN_MITER, JOIN_ROUND, JOIN_BEVEL """ if jointype == sk2const.JOIN_MITER: d = mult_point(sub_points(endpoint, startpoint), 0.5) if d == [0, 0]: return [] o = normalize([d[1], -d[0]]) if radius < length(d): return [startpoint, endpoint] h = math.sqrt(radius**2 - length(d)**2) h2 = length(d)**2 / h if h2 + h > miter_limit * radius: # Hit miter limit return [startpoint, endpoint] edge = add_points(add_points(startpoint, d), mult_point(o, h2)) new_seg1 = line_to_curve(startpoint, edge) new_seg2 = line_to_curve(edge, endpoint) return [ startpoint, ] + new_seg1 + new_seg2 elif jointype == sk2const.JOIN_ROUND: f = CIRCLE_CONSTANT d = mult_point(sub_points(endpoint, startpoint), 0.5) if d == [0, 0]: return [] o = mult_point(normalize([d[1], -d[0]]), radius) if radius < length(d): return [startpoint, endpoint] h = math.sqrt(radius**2 - length(d)**2) / radius center = sub_points(add_points(startpoint, d), mult_point(o, h)) d = mult_point(normalize(d), radius) t0 = circleparam(h) quadseg = [ sub_points(center, d), add_points(sub_points(center, d), mult_point(o, f)), add_points(sub_points(center, mult_point(d, f)), o), add_points(center, o) ] ret = [startpoint] + list(subdivide_seg(quadseg, t0)[4:]) quadseg = [ add_points(center, o), add_points(add_points(center, o), mult_point(d, f)), add_points(add_points(center, d), mult_point(o, f)), add_points(center, d) ] ret = ret + list(subdivide_seg(quadseg, 1 - t0)[1:3]) + [endpoint] return ret elif jointype == sk2const.JOIN_BEVEL: return [startpoint, endpoint] else: raise "Unknown join type %d" % jointype
def split_bezier_curve(start_point, end_point, t=0.5): p0 = start_point[2] if len(start_point) > 2 else start_point p1, p2, p3 = end_point[:3] flag = end_point[3] if len(end_point) == 4 else sk2const.NODE_CUSP p0_1 = add_points(mult_point(p0, (1.0 - t)), mult_point(p1, t)) p1_2 = add_points(mult_point(p1, (1.0 - t)), mult_point(p2, t)) p2_3 = add_points(mult_point(p2, (1.0 - t)), mult_point(p3, t)) p01_12 = add_points(mult_point(p0_1, (1.0 - t)), mult_point(p1_2, t)) p12_23 = add_points(mult_point(p1_2, (1.0 - t)), mult_point(p2_3, t)) p0112_1223 = add_points(mult_point(p01_12, (1.0 - t)), mult_point(p12_23, t)) new_point = [p0_1, p01_12, p0112_1223, flag] new_end_point = [p12_23, p2_3, p3, flag] return new_point, new_end_point
def split_seg(self, base_point, point, t=0.5): if is_bezier(point): p0 = base_point p1, p2, p3 = point[:3] t2 = 1.0 - t r = add_points(mult_point(p1, t2) , mult_point(p2, t)) q1 = add_points(mult_point(p0, t2), mult_point(p1, t)) q2 = add_points(mult_point(q1, t2), mult_point(r, t)) q5 = add_points(mult_point(p2, t2), mult_point(p3, t)) q4 = add_points(mult_point(r, t2), mult_point(q5, t)) q3 = add_points(mult_point(q2, t2), mult_point(q4, t)) return [[q1, q2, [] + q3, 0], q3, [q4, q5, [] + p3, 0]] else: new_point = add_points(mult_point(base_point, (1.0 - t)), mult_point(point, t)) return [new_point, [] + new_point, None]
def subdivide_seg(seg, t=0.5): """ This subdivides a bezier segment at the (optional) Parameter t """ assert len(seg) == 4 t2 = 1.0 - t p01 = add_points(mult_point(seg[0], t2) , mult_point(seg[1], t)) p12 = add_points(mult_point(seg[1], t2), mult_point(seg[2], t)) p23 = add_points(mult_point(seg[2], t2), mult_point(seg[3], t)) p012 = add_points(mult_point(p01, t2), mult_point(p12, t)) p123 = add_points(mult_point(p12, t2), mult_point(p23, t)) p0123 = add_points(mult_point(p012, t2), mult_point(p123, t)) return (seg[0], p01, p012, p0123, p123, p23, seg[3])
def get_join_segment(startpoint, endpoint, radius, jointype, miter_limit=MITER_LIMIT): """ This returns a list of bezier segments that joins two points with a given radius (fails if the radius is smaller than the distance between startpoint) and endpoint). jointype is one of JOIN_MITER, JOIN_ROUND, JOIN_BEVEL """ if jointype == sk2_const.JOIN_MITER: d = mult_point(sub_points(endpoint, startpoint), 0.5) if d == [0, 0]: return [] o = normalize([d[1], -d[0]]) h = math.sqrt(radius ** 2 - length(d) ** 2) h2 = length(d) ** 2 / h if h2 + h > miter_limit * radius: # Hit miter limit return [startpoint, endpoint] edge = add_points(add_points(startpoint, d), mult_point(o, h2)) new_seg1 = line_to_curve(startpoint, edge) new_seg2 = line_to_curve(edge, endpoint) return [startpoint, ] + new_seg1 + new_seg2 elif jointype == sk2_const.JOIN_ROUND: f = CIRCLE_CONSTANT d = mult_point(sub_points(endpoint, startpoint), 0.5) if d == [0, 0]: return [] o = mult_point(normalize([d[1], -d[0]]), radius) h = math.sqrt(radius ** 2 - length(d) ** 2) / radius center = sub_points(add_points(startpoint, d), mult_point(o, h)) d = mult_point(normalize(d), radius) t0 = circleparam(h) quadseg = [sub_points(center, d), add_points(sub_points(center, d), mult_point(o, f)), add_points(sub_points(center, mult_point(d, f)), o), add_points(center, o)] ret = [startpoint] + list (subdivide_seg(quadseg, t0)[4:]) quadseg = [add_points(center, o), add_points(add_points(center, o), mult_point(d, f)), add_points(add_points(center, d), mult_point(o, f)), add_points(center, d)] ret = ret + list (subdivide_seg(quadseg, 1 - t0)[1:3]) + [endpoint] return ret elif jointype == sk2_const.JOIN_BEVEL: return [startpoint, endpoint] else: raise "Unknown join type %d" % jointype