initial commit
This commit is contained in:
172
pydiffvg/shape.py
Normal file
172
pydiffvg/shape.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import torch
|
||||
import svgpathtools
|
||||
import math
|
||||
|
||||
class Circle:
|
||||
def __init__(self, radius, center, stroke_width = torch.tensor(1.0), id = ''):
|
||||
self.radius = radius
|
||||
self.center = center
|
||||
self.stroke_width = stroke_width
|
||||
self.id = id
|
||||
|
||||
class Ellipse:
|
||||
def __init__(self, radius, center, stroke_width = torch.tensor(1.0), id = ''):
|
||||
self.radius = radius
|
||||
self.center = center
|
||||
self.stroke_width = stroke_width
|
||||
self.id = id
|
||||
|
||||
class Path:
|
||||
def __init__(self,
|
||||
num_control_points,
|
||||
points,
|
||||
is_closed,
|
||||
stroke_width = torch.tensor(1.0),
|
||||
id = '',
|
||||
use_distance_approx = False):
|
||||
self.num_control_points = num_control_points
|
||||
self.points = points
|
||||
self.is_closed = is_closed
|
||||
self.stroke_width = stroke_width
|
||||
self.id = id
|
||||
self.use_distance_approx = use_distance_approx
|
||||
|
||||
class Polygon:
|
||||
def __init__(self, points, is_closed, stroke_width = torch.tensor(1.0), id = ''):
|
||||
self.points = points
|
||||
self.is_closed = is_closed
|
||||
self.stroke_width = stroke_width
|
||||
self.id = id
|
||||
|
||||
class Rect:
|
||||
def __init__(self, p_min, p_max, stroke_width = torch.tensor(1.0), id = ''):
|
||||
self.p_min = p_min
|
||||
self.p_max = p_max
|
||||
self.stroke_width = stroke_width
|
||||
self.id = id
|
||||
|
||||
class ShapeGroup:
|
||||
def __init__(self,
|
||||
shape_ids,
|
||||
fill_color,
|
||||
use_even_odd_rule = True,
|
||||
stroke_color = None,
|
||||
shape_to_canvas = torch.eye(3),
|
||||
id = ''):
|
||||
self.shape_ids = shape_ids
|
||||
self.fill_color = fill_color
|
||||
self.use_even_odd_rule = use_even_odd_rule
|
||||
self.stroke_color = stroke_color
|
||||
self.shape_to_canvas = shape_to_canvas
|
||||
self.id = id
|
||||
|
||||
def from_svg_path(path_str, shape_to_canvas = torch.eye(3), force_close = False):
|
||||
path = svgpathtools.parse_path(path_str)
|
||||
if len(path) == 0:
|
||||
return []
|
||||
ret_paths = []
|
||||
subpaths = path.continuous_subpaths()
|
||||
for subpath in subpaths:
|
||||
if subpath.isclosed():
|
||||
if len(subpath) > 1 and isinstance(subpath[-1], svgpathtools.Line) and subpath[-1].length() < 1e-5:
|
||||
subpath.remove(subpath[-1])
|
||||
subpath[-1].end = subpath[0].start # Force closing the path
|
||||
subpath.end = subpath[-1].end
|
||||
assert(subpath.isclosed())
|
||||
else:
|
||||
beg = subpath[0].start
|
||||
end = subpath[-1].end
|
||||
if abs(end - beg) < 1e-5:
|
||||
subpath[-1].end = beg # Force closing the path
|
||||
subpath.end = subpath[-1].end
|
||||
assert(subpath.isclosed())
|
||||
elif force_close:
|
||||
subpath.append(svgpathtools.Line(end, beg))
|
||||
subpath.end = subpath[-1].end
|
||||
assert(subpath.isclosed())
|
||||
|
||||
num_control_points = []
|
||||
points = []
|
||||
|
||||
for i, e in enumerate(subpath):
|
||||
if i == 0:
|
||||
points.append((e.start.real, e.start.imag))
|
||||
else:
|
||||
# Must begin from the end of previous segment
|
||||
assert(e.start.real == points[-1][0])
|
||||
assert(e.start.imag == points[-1][1])
|
||||
if isinstance(e, svgpathtools.Line):
|
||||
num_control_points.append(0)
|
||||
elif isinstance(e, svgpathtools.QuadraticBezier):
|
||||
num_control_points.append(1)
|
||||
points.append((e.control.real, e.control.imag))
|
||||
elif isinstance(e, svgpathtools.CubicBezier):
|
||||
num_control_points.append(2)
|
||||
points.append((e.control1.real, e.control1.imag))
|
||||
points.append((e.control2.real, e.control2.imag))
|
||||
elif isinstance(e, svgpathtools.Arc):
|
||||
# Convert to Cubic curves
|
||||
# https://www.joecridge.me/content/pdf/bezier-arcs.pdf
|
||||
start = e.theta * math.pi / 180.0
|
||||
stop = (e.theta + e.delta) * math.pi / 180.0
|
||||
|
||||
sign = 1.0
|
||||
if stop < start:
|
||||
sign = -1.0
|
||||
|
||||
epsilon = 0.00001
|
||||
debug = abs(e.delta) >= 90.0
|
||||
while (sign * (stop - start) > epsilon):
|
||||
arc_to_draw = stop - start
|
||||
if arc_to_draw > 0.0:
|
||||
arc_to_draw = min(arc_to_draw, 0.5 * math.pi)
|
||||
else:
|
||||
arc_to_draw = max(arc_to_draw, -0.5 * math.pi)
|
||||
alpha = arc_to_draw / 2.0
|
||||
cos_alpha = math.cos(alpha)
|
||||
sin_alpha = math.sin(alpha)
|
||||
cot_alpha = 1.0 / math.tan(alpha)
|
||||
phi = start + alpha
|
||||
cos_phi = math.cos(phi)
|
||||
sin_phi = math.sin(phi)
|
||||
lambda_ = (4.0 - cos_alpha) / 3.0
|
||||
mu = sin_alpha + (cos_alpha - lambda_) * cot_alpha
|
||||
last = sign * (stop - (start + arc_to_draw)) <= epsilon
|
||||
num_control_points.append(2)
|
||||
rx = e.radius.real
|
||||
ry = e.radius.imag
|
||||
cx = e.center.real
|
||||
cy = e.center.imag
|
||||
rot = e.phi * math.pi / 180.0
|
||||
cos_rot = math.cos(rot)
|
||||
sin_rot = math.sin(rot)
|
||||
x = lambda_ * cos_phi + mu * sin_phi
|
||||
y = lambda_ * sin_phi - mu * cos_phi
|
||||
xx = x * cos_rot - y * sin_rot
|
||||
yy = x * sin_rot + y * cos_rot
|
||||
points.append((cx + rx * xx, cy + ry * yy))
|
||||
x = lambda_ * cos_phi - mu * sin_phi
|
||||
y = lambda_ * sin_phi + mu * cos_phi
|
||||
xx = x * cos_rot - y * sin_rot
|
||||
yy = x * sin_rot + y * cos_rot
|
||||
points.append((cx + rx * xx, cy + ry * yy))
|
||||
if not last:
|
||||
points.append((cx + rx * math.cos(rot + start + arc_to_draw),
|
||||
cy + ry * math.sin(rot + start + arc_to_draw)))
|
||||
start += arc_to_draw
|
||||
first = False
|
||||
if i != len(subpath) - 1:
|
||||
points.append((e.end.real, e.end.imag))
|
||||
else:
|
||||
if subpath.isclosed():
|
||||
# Must end at the beginning of first segment
|
||||
assert(e.end.real == points[0][0])
|
||||
assert(e.end.imag == points[0][1])
|
||||
else:
|
||||
points.append((e.end.real, e.end.imag))
|
||||
points = torch.tensor(points)
|
||||
points = torch.cat((points, torch.ones([points.shape[0], 1])), dim = 1) @ torch.transpose(shape_to_canvas, 0, 1)
|
||||
points = points / points[:, 2:3]
|
||||
points = points[:, :2].contiguous()
|
||||
ret_paths.append(Path(torch.tensor(num_control_points), points, subpath.isclosed()))
|
||||
return ret_paths
|
Reference in New Issue
Block a user