def __init__(self, **kwargs): super(byA_CubicBezier, self).__init__(self) self._from = kwargs.get('P1') self._fromcontrol = kwargs.get('C1') self._tocontrol = kwargs.get('C2') self._to = kwargs.get('P2') assert isinstance(self._from, byA_Point) assert isinstance(self._fromcontrol, byA_Point) assert isinstance(self._tocontrol, byA_Point) assert isinstance(self._to, byA_Point) self._svgpathtools = CubicBezier(self._from.toRI(), self._fromcontrol.toRI(), self._tocontrol.toRI(), self._to.toRI()) self._freeze("byA_CubicBezier")
def snap(self, tree, threshold): def process(points): for i, p in enumerate(points): best, _, dist = tree.nearest_neighbor([p.real, p.imag]) if dist < threshold: points[i] = complex(best[0], best[1]) return points path = parse_path(self['d']) newPath = Path() for seg in path: points = process([seg.start, seg.end]) if isinstance(seg, Line): newSeg = Line(*points) newPath.append(newSeg) elif isinstance(seg, CubicBezier): newSeg = CubicBezier(points[0], seg.control1, seg.control2, points[1]) newPath.append(newSeg) self['d'] = newPath.d() return self
def generateInBetweens(poseA, poseB, steps): inv = Inventory() # make pairs pairs = [] for key in ORDER: if key in poseA.inv and key in poseB.inv: partA = poseA.inv[key] partB = poseB.inv[key] if len(partA) != 1 or len(partB) != 1: print('Too many parts {0} - A: {1} B: {2}'.format( key, partA.keys(), partB.keys())) continue pairs.append((key, partA.values()[0], partB.values()[0])) # If there are 3 steps, there are 4 gaps between start and finish # |------1------2------3------| gaps = steps + 1 # process pairs for key, a, b in pairs: pathA = parse_path(a['d']) pathB = parse_path(b['d']) if len(pathA) != len(pathB): print('Unmatched segments {0} - A: {1} B: {2}'.format( key, pathA, pathB)) continue for step in range(1, gaps): newPath = Path() for i in range(len(pathA)): segA = pathA[i] segB = pathB[i] if isinstance(segA, Line): points = _deltaPoints([segA.start, segA.end], [segB.start, segB.end], step, gaps) newPath.append(Line(*points)) elif isinstance(segA, CubicBezier): points = _deltaPoints( [segA.start, segA.control1, segA.control2, segA.end], [segB.start, segB.control1, segB.control2, segB.end], step, gaps) newPath.append(CubicBezier(*points)) newPart = Part(newPath.d()) newPart['x'] = int(_delta(a['x'], b['x'], step, gaps)) newPart['y'] = int(_delta(a['y'], b['y'], step, gaps)) newPart['z'] = int(_delta(a['z'], b['z'], step, gaps)) inv.addPart(key, newPart) print(key, step, newPart) return inv
def glyphToPaths(g, yMul=-1): paths = [] contours = [] yOffs = -font.info.unitsPerEm # decompose components if len(g.components): font.newGlyph('__svgsync') ng = font['__svgsync'] ng.width = g.width ng.appendGlyph(g) ng.decompose() g = ng for c in g: curve = False points = c.points path = Path() currentPos = 0j controlPoints = [] for x in range(len(points)): p = points[x] # print 'p#' + str(x) + '.type = ' + repr(p.type) if p.type == 'move': currentPos = vec2(p.x, (p.y + yOffs) * yMul) elif p.type == 'offcurve': controlPoints.append(p) elif p.type == 'curve': pos = vec2(p.x, (p.y + yOffs) * yMul) if len(controlPoints) == 2: cp1, cp2 = controlPoints path.append(CubicBezier( currentPos, vec2(cp1.x, (cp1.y + yOffs) * yMul), vec2(cp2.x, (cp2.y + yOffs) * yMul), pos)) else: if len(controlPoints) != 1: raise Exception('unexpected number of control points for curve') cp = controlPoints[0] path.append(QuadraticBezier(currentPos, vec2(cp.x, (cp.y + yOffs) * yMul), pos)) currentPos = pos controlPoints = [] elif p.type == 'line': pos = vec2(p.x, (p.y + yOffs) * yMul) path.append(Line(currentPos, pos)) currentPos = pos paths.append(path) if font.has_key('__svgsync'): font.removeGlyph('__svgsync') return paths
def trace_image(filecontents): output = StringIO() output.write(filecontents) _image = Image.open(output) pixels = posturize(_image) output_paths = [] attributes = [] for color in pixels: data = zeros(_image.size, uint32) for pixel in pixels[color]: data[pixel[0], pixel[1]] = 1 # Create a bitmap from the array bmp = potrace.Bitmap(data) # Trace the bitmap to a path path = bmp.trace() # Iterate over path curves for curve in path: svg_paths = [] start_point = curve.start_point true_start = curve.start_point for segment in curve: if true_start is None: true_start = segment.start_point if start_point is None: start_point = segment.start_point if isinstance(segment, BezierSegment): svg_paths.append( CubicBezier( start=start_point[1] + 1j * start_point[0], control1=segment.c1[1] + segment.c1[0] * 1j, control2=segment.c2[1] + segment.c2[0] * 1j, end=segment.end_point[1] + 1j * segment.end_point[0])) elif isinstance(segment, CornerSegment): svg_paths.append( Line(start=start_point[1] + 1j * start_point[0], end=segment.c[1] + segment.c[0] * 1j)) svg_paths.append( Line(start=segment.c[1] + segment.c[0] * 1j, end=segment.end_point[1] + 1j * segment.end_point[0])) else: print("not sure what to do with: ", segment) start_point = segment.end_point # is the path closed? if true_start == start_point: output_paths.append(Path(*svg_paths)) color = pixel[2] rgb = "#%02x%02x%02x" % (color[0], color[1], color[2]) fill = rgb attributes.append({"fill": fill, "stroke": rgb}) true_start = None start_point = None svg_paths = [] return output_paths, attributes
def _paperjs_path_to_polygon(co): ''' Convert Paper.js paths to a polygon (a line composed of consecutive vertices). Used by `download_microdraw_contours_as_polygons`. Note: Paper.js Bézier curves are encoded as px, py, ax, ay, bx, by, where px, py is an anchor point, ax, ay is the previous handle and bx, by the next handle. SVG path tools use a more standard encoding: p1x,p1y, b1x, b1y, a2x, a2y, p2x, p2y, where p1x, p1y is the start anchor point, p2x, p2y the end anchor point, b1x, b1y is the handle coming out from p1x, p1y, and a2x, a2y is the handle entering into the end anchor point. ''' mysegs = [] for i in range(len(co)): c = co[i % len(co)] c1 = co[(i + 1) % len(co)] try: if isinstance(c[0], (list, np.ndarray)): # print("c[0] is list") [s, sj], [_, _], [b, bj] = c else: # print("c[0] is not list:", type(c[0])) s, sj, b, bj = c[0], c[1], 0, 0 if isinstance(c1[0], (list, np.ndarray)): # print("c1[0] is list") [s1, s1j], [a1, a1j], [_, _] = c1 else: # print("c1[0] is not list:", type(c1[0])) s1, s1j, a1, a1j = c1[0], c1[1], 0, 0 # print("c:", c) # print("c1:", c1) # print("s,sj:",s,sj,", b,bj:",b,bj, ", s1,sij:", s1,s1j, ", a1,a1j:",a1,a1j) seg = CubicBezier(complex(s, sj), complex(s + b, sj + bj), complex(s1 + a1, s1j + a1j), complex(s1, s1j)) mysegs.append(seg) except: # ValueError as err: # print(err) pass if len(mysegs) < 5: # print("len(mysegs) is < 5") return p = Path(*mysegs) NUM_SAMPLES = int(p.length()) my_path = [] for i in range(NUM_SAMPLES): x = p.point(i / (NUM_SAMPLES - 1)) my_path.append([x.real, x.imag]) return np.array(my_path)
def augment(path_nested, num): path_list = [] path = Path() for p in path_nested: for segment in p: path.append(segment) end_points_list = [] for segment in path: s = segment.bpoints()[0] e = segment.bpoints()[-1] end_points_list.append((s.real, s.imag)) end_points_list.append((e.real, e.imag)) end_points = np.array(end_points_list) hull_points = end_points[ConvexHull(end_points).vertices] idx_xmin, idx_ymin = np.argmin(hull_points, axis=0) idx_xmax, idx_ymax = np.argmax(hull_points, axis=0) x_range = 0.15 * (hull_points[idx_xmax][0] - hull_points[idx_xmin][0]) y_range = 0.15 * (hull_points[idx_ymax][1] - hull_points[idx_ymin][1]) idx_min_max = np.unique([idx_xmin, idx_ymin, idx_xmax, idx_ymax]) for _ in range(num): # global deformation p = hull_points q = hull_points.copy() for idx in idx_min_max: x, y = p[idx] q[idx] = (x + random.gauss(0, x_range), y + y_range * random.gauss(0, y_range)) path_deformed = Path() for segment in path: points = [] for v in segment.bpoints(): real, imag = moving_least_square_with_rigid_transformation( p, q, np.array([v.real, v.imag]), max(x_range, y_range)) point_xformed = complex(real, imag) points.append(point_xformed) if len(segment.bpoints()) == 2: line = Line(points[0], points[1]) path_deformed.append(line) else: cubic_bezier = CubicBezier(points[0], points[1], points[2], points[3]) path_deformed.append(cubic_bezier) path_list.append(path_deformed) return path_list
def generate(self, u, v, w): seg1 = self.getSegment(self.c, self.k, 0, u) seg2 = self.getSegment(self.c, self.k, 1, u) seg3 = self.getSegment(self.c, self.k, 2, u) seg4 = self.getSegment(self.c, self.k, 3, u) ck = Path(seg1, seg2, seg3, seg4) ll0 = Path( CubicBezier(self.lerp(self.l['start'], self.l0['start'], v), self.lerp(self.l['c1'], self.l0['c1'], v), self.lerp(self.l['c2'], self.l0['c2'], v), self.lerp(self.l['end'], self.l0['end'], v))) self.paths = [ck, ll0] self.width = w
def quantize(self): path = parse_path(self['d']) newPath = Path() for seg in path: if isinstance(seg, Line): newSeg = Line( complex(round(seg.start.real), round(seg.start.imag)), complex(round(seg.end.real), round(seg.end.imag))) newPath.append(newSeg) elif isinstance(seg, CubicBezier): newSeg = CubicBezier( complex(round(seg.start.real), round(seg.start.imag)), complex(round(seg.control1.real), round(seg.control1.imag)), complex(round(seg.control2.real), round(seg.control2.imag)), complex(round(seg.end.real), round(seg.end.imag))) newPath.append(newSeg) self['d'] = newPath.d() return self
def curveTo(self, *points): cpoints = [self.convertPoint(self._lastOnCurve)] cpoints.extend([self.convertPoint(p) for p in points]) self._contour.append(CubicBezier(*cpoints)) self.logger.debug(f"CurveTo({points})") self._lastOnCurve = points[-1]
if type(penult_segment) == CubicBezier: s = copy.copy(penult_segment[4]) e = copy.copy(initial[1]) pathlist[-1] = (['Line', s, e]) elif type(penult_segment) == Line: s = copy.copy(penult_segment[2]) e = copy.copy(initial[1]) pathlist[-1] = (['Line', s, e]) #for i in range(len(pathlist)): # print('-> {}\t{}'.format(pathlist[i][3], pathlist[(i + 1) % (len(pathlist)) ][0])) # print('<-: {}\t{}'.format(pathlist[i][0], pathlist[(i -1) % (len(pathlist)) ][3])) for i in pathlist: if i[0] == 'CubicBezier': newpath.append(CubicBezier(i[1], i[2], i[3], i[4])) elif i[0] == 'Line': newpath.append(Line(i[1], i[2])) newpaths.append(newpath) j += 1 print('\tfinished generating frame {}'.format(FRAME + 1)) # export svg #newpaths.reverse() svg_newfile = svg_file + '_' + str(FRAME + 1) + '.svg' wsvg(newpaths, attributes=attr, svg_attributes=svg_attr, filename=svg_newfile)
def draw_glyph(font, char, ctx, offset=(0, 0), color=(0.6, 0.6, 0.6)): try: face = Face('data/ttfs/{}.ttf'.format(font)) except: face = Face('data/ttfs/{}.otf'.format(font)) face.set_char_size(48*64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append(complex(*outline.points[contours[j-1]+1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment)-1): A, C = segment[k:k+2] B = (A+C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append(Line(segment[-1], complex(*outline.points[contours[j-1]+1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax-xmin, ymax-ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax-xmin)) / 2 ymargin = (1 - (ymax-ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) ctx.set_source_rgb(*color) ctx.new_path() ctx.set_line_width(0.02) x, y = offset for path in paths: ctx.move_to(path[0].bpoints()[0].real + x, path[0].bpoints()[0].imag + y) for seg in path: bpoints = seg.bpoints() if len(bpoints) == 2: ctx.line_to(bpoints[1].real + x, bpoints[1].imag + y) elif len(bpoints) == 3: ctx.curve_to(bpoints[0].real * 1/3 + bpoints[1].real * 2/3 + x, bpoints[0].imag * 1/3 + bpoints[1].imag * 2/3 + y, bpoints[1].real * 2/3 + bpoints[2].real * 1/3 + x, bpoints[1].imag * 2/3 + bpoints[2].imag * 1/3 + y, bpoints[2].real + x, bpoints[2].imag + y) elif len(bpoints) == 4: ctx.curve_to(bpoints[1].real + x, bpoints[1].imag + y, bpoints[2].real + x, bpoints[2].imag + y, bpoints[3].real + x, bpoints[3].imag + y) ctx.fill()
def getSegment(self, c, k, idx, t): return CubicBezier(self.lerp(c[idx]['start'], k[idx]['start'], t), self.lerp(c[idx]['c1'], k[idx]['c1'], t), self.lerp(c[idx]['c2'], k[idx]['c2'], t), self.lerp(c[idx]['end'], k[idx]['end'], t))
from svgpathtools import Path, Line, QuadraticBezier, CubicBezier, Arc, svg2paths, parse_path, wsvg from pcbmode.utils.svg import absolute_to_relative_path print("Build SVG Path from lines") seg1 = CubicBezier(300+100j, 100+100j, 200+200j, 200+300j) # A cubic beginning at (300, 100) and ending at (200, 300) seg2 = Line(200+300j, 250+350j) # A line beginning at (200, 300) and ending at (250, 350) path = Path(seg1, seg2) # A path traversing the cubic and then the line # (svgpathtools only creates absolute path co-ordinates) print(path.d()) print("Create SVG") #(svgpathtools only creates absolute path co-ordinates) outline_path = parse_path("m 10.858333,11 c 2.140681,-0.340482 4.281363,-0.680964 6.422044,-1.0214454 1.69236,-1.0123173 3.384721,-2.0246345 5.077081,-3.0369518 1.2656,-2.1059322 2.531201,-4.2118644 3.796801,-6.31779663 0.663858,-2.34543537 1.327716,-4.69087077 1.991574,-7.03630647 -0.223524,-1.829043 -0.447047,-3.6580857 -0.670571,-5.4871287 -0.600087,-0.758457 -1.200175,-1.516914 -1.800262,-2.275371 -17.1166667,0 -34.2333333,0 -51.35,0 -0.600087,0.758457 -1.200175,1.516914 -1.800262,2.275371 -0.223524,1.829043 -0.447047,3.6580857 -0.670571,5.4871287 0.663858,2.3454357 1.327716,4.6908711 1.991574,7.03630647 1.2656,2.10593223 2.531201,4.21186443 3.796801,6.31779663 1.69236,1.0123173 3.384721,2.0246345 5.077081,3.0369518 2.140681,0.3404814 4.281363,0.6809634 6.422044,1.0214454 7.2388887,0 14.4777773,0 21.716666,0 z", current_pos=0) wsvg(outline_path, filename='svg/out/output1.svg') p = parse_path("m 4.6147194,11.014797 c 0.04717,0.459725 -0.00339,0.582458 0.024033,1.056225", current_pos=0) print p print("Parse SVG") paths, attributes = svg2paths('svg/in/triangle_relative.svg') for path in paths: print absolute_to_relative_path(path.d())
class byA_CubicBezier(byA_FrozenClass): def __init__(self, **kwargs): super(byA_CubicBezier, self).__init__(self) self._from = kwargs.get('P1') self._fromcontrol = kwargs.get('C1') self._tocontrol = kwargs.get('C2') self._to = kwargs.get('P2') assert isinstance(self._from, byA_Point) assert isinstance(self._fromcontrol, byA_Point) assert isinstance(self._tocontrol, byA_Point) assert isinstance(self._to, byA_Point) self._svgpathtools = CubicBezier(self._from.toRI(), self._fromcontrol.toRI(), self._tocontrol.toRI(), self._to.toRI()) self._freeze("byA_CubicBezier") def toSVGWrite(self, drawing, **extra): """to the svgwrite syntax """ return drawing.path(d=self.toStr(), **extra) def toStr(self): return Path(self._svgpathtools).d() def reverse(self): return byA_CubicBezier(P1=self._to, C1=self._tocontrol, C2=self._fromcontrol, P2=self._from) def rotate(self, degre, origin=None): self._svgpathtools = self._svgpathtools.rotated(degre, origin) self._from.rotate(degre, origin) self._fromcontrol.rotate(degre, origin) self._tocontrol.rotate(degre, origin) self._to.rotate(degre, origin) def rotated(self, degre, origin=None): res = byA_CubicBezier(P1=self._from, C1=self._fromcontrol, C2=self._tocontrol, P2=self._to) res.rotate(degre, origin) return res def split(self, t): P12 = self._from + t * (self._fromcontrol - self._from) P23 = self._fromcontrol + t * (self._tocontrol - self._fromcontrol) P34 = self._tocontrol + t * (self._to - self._tocontrol) P123 = P12 + t * (P23 - P12) P234 = P23 + t * (P34 - P23) P1234 = P123 + t * (P234 - P123) return (byA_CubicBezier(P1=self._from, C1=P12, C2=P123, P2=P1234), byA_CubicBezier(P1=P1234, C1=P234, C2=P34, P2=self._to)) def lenght(self): line = byA_Line(P1=self._from, P2=self._to) l = line.lenght() points = self.courbe_bezier_3( [self._from, self._fromcontrol, self._tocontrol, self._to], l * 10) res = 0 for point in range(0, len(points) - 1): line = byA_Line(P1=points[point], P2=points[point + 1]) res += line.lenght() return res def combinaison_lineaire(self, A, B, u, v): assert isinstance(A, byA_Point) assert isinstance(B, byA_Point) return byA_Point(x=A._x * u + B._x * v, y=A._y * u + B._y * v) def point_bezier_3(self, points_control, t): x = (1 - t)**2 y = t * t A = self.combinaison_lineaire(points_control[0], points_control[1], (1 - t) * x, 3 * t * x) B = self.combinaison_lineaire(points_control[2], points_control[3], 3 * y * (1 - t), y * t) return byA_Point(x=A._x + B._x, y=A._y + B._y) def courbe_bezier_3(self, points_control, N): if len(points_control) != 4: raise SystemExit("4 points de controle") dt = 1.0 / N t = dt points_courbe = [points_control[0]] while t < 1.0: points_courbe.append(self.point_bezier_3(points_control, t)) t += dt points_courbe.append(points_control[3]) return points_courbe
def process_letter(font_char): try: font, char = font_char name = "{}_{}".format(char, os.path.splitext(font)[0]) face = Face('data/fonts/ttfs/{}'.format(font)) face.set_char_size(48 * 64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append( complex(*outline.points[contours[j - 1] + 1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment) - 1): A, C = segment[k:k + 2] B = (A + C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append( Line( segment[-1], complex(*outline.points[contours[j - 1] + 1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax - xmin, ymax - ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax - xmin)) / 2 ymargin = (1 - (ymax - ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) points = [] surface = cairo.ImageSurface(cairo.Format.RGB24, opt.img_size, opt.img_size) ctx = cairo.Context(surface) ctx.scale(opt.img_size, opt.img_size) ctx.set_source_rgba(1, 1, 1) ctx.rectangle(0, 0, 1, 1) ctx.fill() ctx.set_source_rgb(0, 0, 0) ctx.new_path() ctx.set_line_width(0.02) for path in paths: ctx.move_to(path[0].bpoints()[0].real, path[0].bpoints()[0].imag) for seg in path: bpoints = seg.bpoints() if len(bpoints) == 2: ctx.line_to(bpoints[1].real, bpoints[1].imag) elif len(bpoints) == 3: ctx.curve_to( bpoints[0].real * 1 / 3 + bpoints[1].real * 2 / 3, bpoints[0].imag * 1 / 3 + bpoints[1].imag * 2 / 3, bpoints[1].real * 2 / 3 + bpoints[2].real * 1 / 3, bpoints[1].imag * 2 / 3 + bpoints[2].imag * 1 / 3, bpoints[2].real, bpoints[2].imag) elif len(bpoints) == 4: ctx.curve_to(bpoints[1].real, bpoints[1].imag, bpoints[2].real, bpoints[2].imag, bpoints[3].real, bpoints[3].imag) for t in np.linspace(0, 1, num=opt.n_points_sampled // len(paths) + 1): points.append(path.point(t)) ctx.fill() n_points = len(points) points = np.array(points, dtype=np.complex64).view(np.float32).reshape([-1, 2]) np.random.shuffle(points) np.save('data/fonts/points/{}.npy'.format(name), points) grid = np.mgrid[-0.25:1.25:opt.img_size * 1.5j, -0.25:1.25:opt.img_size * 1.5j].T[:, :, None, :] distances = np.empty((grid.shape[0], grid.shape[1])) for i in range(grid.shape[0]): for j in range(grid.shape[0]): distances[i, j] = np.amin( np.linalg.norm(grid[i, j] - points, axis=1)) if not np.isnan(distances).any(): np.save('data/fonts/distances/{}.npy'.format(name), distances) surface.write_to_png('data/fonts/pngs/{}.png'.format(name)) else: return e except Exception as e: return e return None
def parse_path(pathdef, current_pos=0j, tree_element=None): # In the SVG specs, initial movetos are absolute, even if # specified as 'm'. This is the default behavior here as well. # But if you pass in a current_pos variable, the initial moveto # will be relative to that current_pos. This is useful. elements = list(_tokenize_path(pathdef)) # Reverse for easy use of .pop() elements.reverse() if tree_element is None: segments = Path() else: segments = Path(tree_element=tree_element) start_pos = None command = None while elements: if elements[-1] in COMMANDS: # New command. last_command = command # Used by S and T command = elements.pop() absolute = command in UPPERCASE command = command.upper() else: # If this element starts with numbers, it is an implicit command # and we don't change the command. Check that it's allowed: if command is None: raise ValueError( "Unallowed implicit command in %s, position %s" % (pathdef, len(pathdef.split()) - len(elements))) if command == 'M': # Moveto command. x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if absolute: current_pos = pos else: current_pos += pos # when M is called, reset start_pos # This behavior of Z is defined in svg spec: # http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand start_pos = current_pos # Implicit moveto commands are treated as lineto commands. # So we set command to lineto here, in case there are # further implicit commands after this moveto. command = 'L' elif command == 'Z': # Close path if not (current_pos == start_pos): segments.append(Line(current_pos, start_pos)) segments.closed = True current_pos = start_pos command = None elif command == 'L': x = elements.pop() y = elements.pop() pos = float(x) + float(y) * 1j if not absolute: pos += current_pos segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'H': x = elements.pop() pos = float(x) + current_pos.imag * 1j if not absolute: pos += current_pos.real segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'V': y = elements.pop() pos = current_pos.real + float(y) * 1j if not absolute: pos += current_pos.imag * 1j segments.append(Line(current_pos, pos)) current_pos = pos elif command == 'C': control1 = float(elements.pop()) + float(elements.pop()) * 1j control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control1 += current_pos control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'S': # Smooth curve. First control point is the "reflection" of # the second control point in the previous path. if last_command not in 'CS': # If there is no previous command or if the previous command # was not an C, c, S or s, assume the first control point is # coincident with the current point. control1 = current_pos else: # The first control point is assumed to be the reflection of # the second control point on the previous command relative # to the current point. control1 = current_pos + current_pos - segments[-1].control2 control2 = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control2 += current_pos end += current_pos segments.append(CubicBezier(current_pos, control1, control2, end)) current_pos = end elif command == 'Q': control = float(elements.pop()) + float(elements.pop()) * 1j end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: control += current_pos end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'T': # Smooth curve. Control point is the "reflection" of # the second control point in the previous path. if last_command not in 'QT': # If there is no previous command or if the previous command # was not an Q, q, T or t, assume the first control point is # coincident with the current point. control = current_pos else: # The control point is assumed to be the reflection of # the control point on the previous command relative # to the current point. control = current_pos + current_pos - segments[-1].control end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(QuadraticBezier(current_pos, control, end)) current_pos = end elif command == 'A': radius = float(elements.pop()) + float(elements.pop()) * 1j rotation = float(elements.pop()) arc = float(elements.pop()) sweep = float(elements.pop()) end = float(elements.pop()) + float(elements.pop()) * 1j if not absolute: end += current_pos segments.append(Arc(current_pos, radius, rotation, arc, sweep, end)) current_pos = end return segments
def testPathToPolyligne(): continuous_path = SVGPath( CubicBezier(start=(540.305 + 223.059j), control1=(541.074 + 230.176j), control2=(542.559 + 237.961j), end=(544.609 + 245.051j)), Line(start=(544.609 + 245.051j), end=(631.121 + 550.996j)), Line(start=(631.121 + 550.996j), end=(726.211 + 550.996j)), Line(start=(726.211 + 550.996j), end=(643.008 + 257.996j)), CubicBezier(start=(643.008 + 257.996j), control1=(640.977 + 250.93j), control2=(639.629 + 244.414j), end=(638.965 + 237.961j)), CubicBezier(start=(638.965 + 237.961j), control1=(636.172 + 212.078j), control2=(648.184 + 197.805j), end=(693.516 + 197.805j)), CubicBezier(start=(693.516 + 197.805j), control1=(745.254 + 197.805j), control2=(766.211 + 218.555j), end=(776.504 + 254.133j)), Line(start=(776.504 + 254.133j), end=(860.754 + 550.996j)), Line(start=(860.754 + 550.996j), end=(955.859 + 550.996j)), Line(start=(955.859 + 550.996j), end=(869.336 + 245.051j)), CubicBezier(start=(869.336 + 245.051j), control1=(848.594 + 172.637j), control2=(806.074 + 113.762j), end=(674.746 + 113.762j)), CubicBezier(start=(674.746 + 113.762j), control1=(570.637 + 113.762j), control2=(534.199 + 166.152j), end=(540.305 + 223.059j))) discontinuous_path = SVGPath( CubicBezier(start=(1648.6 + 430.684j), control1=(1651.47 + 457.266j), control2=(1635.71 + 473.398j), end=(1589.79 + 473.398j)), CubicBezier(start=(1589.79 + 473.398j), control1=(1540.63 + 473.398j), control2=(1512.46 + 452.051j), end=(1502.33 + 417.781j)), Line(start=(1502.33 + 417.781j), end=(1455.62 + 253.477j)), CubicBezier(start=(1455.62 + 253.477j), control1=(1454.41 + 248.309j), control2=(1453.24 + 243.105j), end=(1452.73 + 238.586j)), CubicBezier(start=(1452.73 + 238.586j), control1=(1449.93 + 212.703j), control2=(1465.2 + 197.805j), end=(1511.74 + 197.805j)), CubicBezier(start=(1511.74 + 197.805j), control1=(1560.88 + 197.805j), control2=(1588.76 + 216.57j), end=(1599.2 + 253.477j)), Line(start=(1599.2 + 253.477j), end=(1645.92 + 417.781j)), CubicBezier(start=(1645.92 + 417.781j), control1=(1647.08 + 422.285j), control2=(1648.21 + 426.863j), end=(1648.6 + 430.684j)), CubicBezier(start=(1355.23 + 228.242j), control1=(1355.92 + 234.695j), control2=(1357.37 + 241.836j), end=(1359.35 + 248.309j)), Line(start=(1359.35 + 248.309j), end=(1409.47 + 426.152j)), CubicBezier(start=(1409.47 + 426.152j), control1=(1432.25 + 506.406j), control2=(1486.96 + 557.488j), end=(1608.54 + 557.488j)), CubicBezier(start=(1608.54 + 557.488j), control1=(1710.74 + 557.488j), control2=(1752.37 + 499.27j), end=(1746.04 + 440.43j)), CubicBezier(start=(1746.04 + 440.43j), control1=(1745.44 + 434.578j), control2=(1743.96 + 427.441j), end=(1742.02 + 420.996j)), Line(start=(1742.02 + 420.996j), end=(1692.08 + 245.051j)), CubicBezier(start=(1692.08 + 245.051j), control1=(1669.06 + 162.938j), control2=(1615.23 + 113.762j), end=(1487.85 + 113.762j)), CubicBezier(start=(1487.85 + 113.762j), control1=(1386.29 + 113.762j), control2=(1348.96 + 170.012j), end=(1355.23 + 228.242j))) polc = continuousPathToPolyligne(continuous_path) polc.plot(plt) pols = pathToPolylignes(discontinuous_path) for pol in pols: pts = pol.cpoints name = pol.name plt.plot(pts[:, 0], pts[:, 1], '.-', label=name) plt.legend() plt.show()
def getSegment(path): return CubicBezier(complex(path['p0'][0], path['p0'][1]), complex(path['c0'][0], path['c0'][1]), complex(path['c1'][0], path['c1'][1]), complex(path['p1'][0], path['p1'][1]))
def sample_points_from_font(font, char): try: face = Face('data/ttfs/{}.ttf'.format(font)) except: face = Face('data/ttfs/{}.otf'.format(font)) face.set_char_size(48 * 64) face.load_char(char) outline = face.glyph.outline contours = [-1] + outline.contours segment = [] segments = [] paths = [] for i in range(len(outline.points)): segment.append(complex(*outline.points[i]).conjugate()) tag = int(bin(outline.tags[i])[2]) try: j = contours.index(i) if tag == 0: segment.append( complex(*outline.points[contours[j - 1] + 1]).conjugate()) tag = 2 else: tag = 3 except ValueError: pass if tag > 0: if len(segment) == 1: pass elif len(segment) == 2: segments.append(Line(*segment)) elif len(segment) == 3: segments.append(QuadraticBezier(*segment)) elif len(segment) == 4: segments.append(CubicBezier(*segment)) else: for k in range(len(segment) - 1): A, C = segment[k:k + 2] B = (A + C) / 2 segments.append(QuadraticBezier(A, B, C)) if tag == 1: segment = [complex(*outline.points[i]).conjugate()] elif tag == 2: paths.append(Path(*segments)) segments = [] segment = [] else: segments.append( Line( segment[-1], complex(*outline.points[contours[j - 1] + 1]).conjugate())) paths.append(Path(*segments)) segments = [] segment = [] xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) factor = 0.8 / max(xmax - xmin, ymax - ymin) for i, path in enumerate(paths): paths[i] = path.translated(complex(-xmin, -ymin)).scaled(factor) xmin, xmax, ymin, ymax = paths2svg.big_bounding_box(paths) xmargin = (1 - (xmax - xmin)) / 2 ymargin = (1 - (ymax - ymin)) / 2 for i, path in enumerate(paths): paths[i] = path.translated(complex(xmargin, ymargin)) points = [] for path in paths: for seg in path: length = seg.length() for a in np.linspace(0, 1, num=length * n_points_per_unit_length): points.append(seg.point(seg.ilength(a * length))) return [(p.real, p.imag) for p in points]