def path_to_polygon(path): polygon = [] global max_x, max_y, min_x, min_y new_path = Path() for segment in parse_path(path): new_path.append(Line(segment.start, segment.end)) new_path.closed = True raw_path = new_path.d() # search for compound path bits and remove them path_bits = re.findall('M.+?[ZM]', raw_path) if len(path_bits) > 0: raw_path = path_bits[0] raw_path_size = len(re.findall(',', raw_path)) for bit in path_bits: bit_size = len(re.findall(',', bit)) if bit_size > raw_path_size: raw_path = bit raw_path_size = bit_size # convert to simple list of nodes nodes = re.findall('[ML]\s*(\d+\.*\d*,\d+\.*\d*)\s*', raw_path) for n in nodes: coords = n.split(',') if max_x < int(coords[0]): max_x = int(coords[0]) if max_y < int(coords[1]): max_y = int(coords[1]) if min_x > int(coords[0]): min_x = int(coords[0]) if min_y > int(coords[1]): min_y = int(coords[1]) polygon.append([int(coords[0]), int(coords[1])]) polygon.pop() return polygon
def createSVG(filename, tool, points): # Generate the paths allPoints = list() paths = list() t = Template(PATH_TEMPLATE) for point in points: allPoints.extend(point) path = Path() lx, ly = point[0] pc = 0 for p in point[1:]: l = mkLine(lx, ly, p[0], p[1]) if l is not None: path.append(l) pc = pc + 1 lx = p[0] ly = p[1] path.closed = True paths.append(t.safe_substitute({ 'strokeWidth': tool, 'path': path.d() })) print "Generated path with %d points." % pc # Build the final output v = { 'xmin': min([ x for x, y in allPoints ]) - (tool / 2), 'ymin': min([ y for x, y in allPoints ]) - (tool / 2), 'xmax': max([ x for x, y in allPoints ]) + (tool / 2), 'ymax': max([ y for x, y in allPoints ]) + (tool / 2) } v['width'] = abs(v['xmax'] - v['xmin']) v['height'] = abs(v['ymax'] - v['ymin']) v['path'] = "\n".join(paths) out = Template(SVG_TEMPLATE).substitute(v).strip() # And write the file with open(filename, "w") as svg: svg.write(out)
def createSVG(filename, tool, points): # Generate the paths allPoints = list() paths = list() t = Template(PATH_TEMPLATE) for point in points: allPoints.extend(point) path = Path() lx, ly = point[0] pc = 0 for p in point[1:]: l = mkLine(lx, ly, p[0], p[1]) if l is not None: path.append(l) pc = pc + 1 lx = p[0] ly = p[1] path.closed = True paths.append(t.safe_substitute({ 'strokeWidth': tool, 'path': path.d() })) print "Generated path with %d points." % pc # Build the final output v = { 'xmin': min([x for x, y in allPoints]) - (tool / 2), 'ymin': min([y for x, y in allPoints]) - (tool / 2), 'xmax': max([x for x, y in allPoints]) + (tool / 2), 'ymax': max([y for x, y in allPoints]) + (tool / 2) } v['width'] = abs(v['xmax'] - v['xmin']) v['height'] = abs(v['ymax'] - v['ymin']) v['path'] = "\n".join(paths) out = Template(SVG_TEMPLATE).substitute(v).strip() # And write the file with open(filename, "w") as svg: svg.write(out)
def remove_redundant_lines(self): eps = 0.001 def get_slope_intersect(p1, p2): if abs(p1.real - p2.real) < eps: # Vertical; no slope and return x-intercept return None, p1.real m = (p2.imag - p1.imag) / (p2.real - p1.real) b1 = p1.imag - m * p1.real b2 = p2.imag - m * p2.real assert abs(b1 - b2) < eps return m, b1 lines_bucketed_by_slope_intersect = defaultdict(list) paths = self.svg_node.getElementsByTagName('path') overall_index = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) for line_index, line in enumerate(path_obj): slope, intersect = get_slope_intersect(line.start, line.end) if slope is not None: slope = round(slope, ndigits=3) intersect = round(intersect, ndigits=3) lines_bucketed_by_slope_intersect[(slope, intersect)].append({ 'overall_index': overall_index, 'path_index': path_index, 'line_index': line_index, 'line': line, }) overall_index += 1 to_remove = {} for slope_intersect, lines in lines_bucketed_by_slope_intersect.items(): # Naive N^2 search for overlapping lines within a slope-intersect bucket for i in range(len(lines)): line1 = lines[i]['line'] eq1 = get_slope_intersect(line1.start, line1.end) for j in range(i + 1, len(lines)): line2 = lines[j]['line'] eq2 = get_slope_intersect(line2.start, line2.end) # Compare slopes if (eq1[0] is None and eq2[0] is None) \ or (eq1[0] is not None and eq2[0] is not None and abs(eq1[0] - eq2[0]) < eps): # Compare intersects if abs(eq1[1] - eq2[1]) < eps: # Must be collinear, check for overlap l1x1 = min(line1.start.real, line1.end.real) l1x2 = max(line1.start.real, line1.end.real) l1y1 = min(line1.start.imag, line1.end.imag) l1y2 = max(line1.start.imag, line1.end.imag) l2x1 = min(line2.start.real, line2.end.real) l2x2 = max(line2.start.real, line2.end.real) l2y1 = min(line2.start.imag, line2.end.imag) l2y2 = max(line2.start.imag, line2.end.imag) if l1x1 <= l2x1 + eps and l1x2 + eps >= l2x2 and l1y1 <= l2y1 + eps and l1y2 + eps >= l2y2: # Overlapping, line 1 is bigger assert line1.length() + eps >= line2.length() to_remove[lines[j]['overall_index']] = (lines[j]['path_index'], lines[j]['line_index'], line2) elif l1x1 + eps >= l2x1 and l1x2 <= l2x2 + eps and l1y1 + eps >= l2y1 and l1y2 <= l2y2 + eps: # Overlapping, line 2 is bigger assert line2.length() + eps >= line1.length() to_remove[lines[i]['overall_index']] = (lines[i]['path_index'], lines[i]['line_index'], line1) # Reconstruct the paths, but excluding the redundant lines we just identified i = 0 removed = 0 removed_length = 0 kept = 0 kept_length = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) filtered_path = Path() for line_index, line in enumerate(path_obj): if i in to_remove: assert path_index == to_remove[i][0] assert line_index == to_remove[i][1] assert line == to_remove[i][2] removed += 1 removed_length += line.length() else: filtered_path.append(line) kept += 1 kept_length += line.length() i += 1 # Update the path data with the filtered path data path.attributes['d'] = filtered_path.d() print 'Removed {} lines ({} length) and kept {} lines ({} length)'.format( removed, removed_length, kept, kept_length, ) return [to_remove[k][2] for k in to_remove]
SVG2 = ''' </g> </svg> ''' svgpath1 = '''<path style="fill:none;fill-rule:evenodd;stroke:#{0:02x}{1:02x}{2:02x};stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: path = parse_path(p.attrib["d"]) start = None myrange = list(frange(0, 1, decimal.Decimal('0.01'))) mycolors = list(red.range_to(blue, len(myrange))) for i, px in enumerate(myrange): new_path = Path() if start is None: start = path.point(float(px)) else: end = path.point(float(px)) new_path.append(Line(start, end)) start = end color = mycolors[i] r = int(color.red * 255) g = int(color.green * 255) b = int(color.blue * 255) print((svgpath1 + new_path.d() + svgpath2).format(clamp(r), clamp(g), clamp(b))) print(SVG2)
def remove_redundant_lines(self): lines_bucketed_by_slope_intersect = defaultdict(list) paths = self.svg_node.getElementsByTagName('path') overall_index = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) for line_index, line in enumerate(path_obj): slope, intersect = _get_slope_intersect(line.start, line.end) # TODO: float inaccuracy and rounding may cause collinear lines to end up in separate buckets in rare # cases, so this is not quite correct. Would be better to put lines into *2* nearest buckets in each # dimension to avoid edge cases. if slope is not None: slope = round(slope, ndigits=3) intersect = round(intersect, ndigits=3) lines_bucketed_by_slope_intersect[(slope, intersect)].append({ 'overall_index': overall_index, 'path_index': path_index, 'line_index': line_index, 'line': line, }) overall_index += 1 to_remove = {} to_update = {} for slope_intersect, lines in lines_bucketed_by_slope_intersect.items(): for i in range(20): if SvgProcessor._pairwise_overlap_check(lines, to_update, to_remove): print 'Re-running pairwise overlap check because of updated/merged line' continue break else: raise Exception( 'Exceeded the max number of pairwise overlap check passes. Something is probably wrong.' ) # Reconstruct the paths, but excluding/updating the lines we just identified i = 0 removed = 0 removed_length = 0 kept = 0 kept_length = 0 for path_index, path in enumerate(paths): path_text = path.attributes['d'].value path_obj = parse_path(path_text) filtered_path = Path() for line_index, line in enumerate(path_obj): if i in to_remove: assert path_index == to_remove[i][0] assert line_index == to_remove[i][1] removed += 1 removed_length += line.length() elif i in to_update: assert path_index == to_update[i][0] assert line_index == to_update[i][1] replacement_line = to_update[i][2] filtered_path.append(replacement_line) kept += 1 kept_length += replacement_line.length() else: filtered_path.append(line) kept += 1 kept_length += line.length() i += 1 # Update the path data with the filtered path data path.attributes['d'] = filtered_path.d() print 'Removed {} lines ({} length) and kept {} lines ({} length)'.format( removed, removed_length, kept, kept_length, ) return [to_remove[k][2] for k in to_remove], [to_update[k][2] for k in to_update]
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: new_path = Path() path = parse_path(p.attrib["d"]) start = None for command in path: if type(command) == Line: new_path.append(command) start = command.end ##print(command) else: not_line_path = Path(command) for px in list(frange(0, 1, decimal.Decimal(factor))): if start is None: start = not_line_path.point(float(px)) else: end = not_line_path.point(float(px)) new_command = Line(start, end) new_path.append(new_command) ##print(new_command) start = end new_path_str = new_path.d() start = None print(svgpath1 + new_path_str + svgpath2) print(SVG2)
SVG1 = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="500" height="500"> <g> ''' SVG2 = ''' </g> </svg> ''' svgpath1 = '''<path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="''' svgpath2 = ''' Z"/> ''' print(SVG1) for p in paths: new_path = Path() path = parse_path(p.attrib["d"]) start = None #for px in list(frange(0, 1, decimal.Decimal('0.05'))): for px in list(frange(0, 1, decimal.Decimal('0.005'))): if start is None: start = path.point(float(px)) print(start) else: end = path.point(float(px)) new_path.append(Line(start, end)) start = end print(svgpath1 + new_path.d() + svgpath2) print(SVG2)