mirror of
https://github.com/OpenVGLab/OmniLottie.git
synced 2026-09-17 15:46:26 +00:00
529 lines
21 KiB
Python
529 lines
21 KiB
Python
import math
|
|||
|
|
from ... import objects
|
||
|
|
from ...objects import easing
|
||
|
|
from . import api, ast
|
||
|
|
from ... import NVector, PolarVector
|
||
|
|
|
||
|
|
try:
|
||
|
|
from ...utils import font
|
||
|
|
has_font = True
|
||
|
|
except ImportError:
|
||
|
|
has_font = False
|
||
|
|
|
||
|
|
|
||
|
|
def convert(canvas: api.Canvas):
|
||
|
|
return Converter().convert(canvas)
|
||
|
|
|
||
|
|
|
||
|
|
class Converter:
|
||
|
|
def __init__(self):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _animated(self, sifval):
|
||
|
|
return isinstance(sifval, ast.SifAnimated)
|
||
|
|
|
||
|
|
def convert(self, canvas: api.Canvas):
|
||
|
|
self.canvas = canvas
|
||
|
|
self.animation = objects.Animation(
|
||
|
|
self._time(canvas.end_time),
|
||
|
|
canvas.fps
|
||
|
|
)
|
||
|
|
self.animation.in_point = self._time(canvas.begin_time)
|
||
|
|
self.animation.width = canvas.width
|
||
|
|
self.animation.height = canvas.height
|
||
|
|
self.view_p1 = NVector(canvas.view_box[0], canvas.view_box[1])
|
||
|
|
self.view_p2 = NVector(canvas.view_box[2], canvas.view_box[3])
|
||
|
|
self.target_size = NVector(canvas.width, canvas.height)
|
||
|
|
self.shape_layer = self.animation.add_layer(objects.ShapeLayer())
|
||
|
|
self.gamma = NVector(canvas.gamma_r, canvas.gamma_g, canvas.gamma_b)
|
||
|
|
self._process_layers(canvas.layers, self.shape_layer)
|
||
|
|
return self.animation
|
||
|
|
|
||
|
|
def _time(self, t: api.FrameTime):
|
||
|
|
return self.canvas.time_to_frames(t)
|
||
|
|
|
||
|
|
def _process_layers(self, layers, parent):
|
||
|
|
old_gamma = self.gamma
|
||
|
|
|
||
|
|
for layer in reversed(layers):
|
||
|
|
if not layer.active:
|
||
|
|
continue
|
||
|
|
elif isinstance(layer, api.GroupLayerBase):
|
||
|
|
parent.add_shape(self._convert_group(layer))
|
||
|
|
elif isinstance(layer, api.RectangleLayer):
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_rect))
|
||
|
|
elif isinstance(layer, api.CircleLayer):
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_circle))
|
||
|
|
elif isinstance(layer, api.StarLayer):
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_star))
|
||
|
|
elif isinstance(layer, api.PolygonLayer):
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_polygon))
|
||
|
|
elif isinstance(layer, api.RegionLayer):
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_bline))
|
||
|
|
elif isinstance(layer, api.AbstractOutline):
|
||
|
|
parent.add_shape(self._convert_outline(layer, self._convert_bline))
|
||
|
|
elif isinstance(layer, api.GradientLayer):
|
||
|
|
parent.add_shape(self._convert_gradient(layer, parent))
|
||
|
|
elif isinstance(layer, api.TransformDown):
|
||
|
|
shape = self._convert_transform_down(layer)
|
||
|
|
parent.add_shape(shape)
|
||
|
|
parent = shape
|
||
|
|
elif isinstance(layer, api.TextLayer):
|
||
|
|
if has_font:
|
||
|
|
parent.add_shape(self._convert_fill(layer, self._convert_text))
|
||
|
|
elif isinstance(layer, api.ColorCorrectLayer):
|
||
|
|
self.gamma = self.gamma * NVector(layer.gamma.value, layer.gamma.value, layer.gamma.value)
|
||
|
|
|
||
|
|
self.gamma = old_gamma
|
||
|
|
|
||
|
|
def _convert_group(self, layer: api.GroupLayer):
|
||
|
|
shape = objects.Group()
|
||
|
|
self._set_name(shape, layer)
|
||
|
|
shape.transform.anchor_point = self._adjust_coords(self._convert_vector(layer.origin))
|
||
|
|
self._convert_transform(layer.transformation, shape.transform)
|
||
|
|
self._process_layers(layer.layers, shape)
|
||
|
|
shape.transform.opacity = self._adjust_animated(
|
||
|
|
self._convert_scalar(layer.amount),
|
||
|
|
lambda x: x*100
|
||
|
|
)
|
||
|
|
return shape
|
||
|
|
|
||
|
|
def _convert_transform(self, sif_transform: api.AbstractTransform, lottie_transform: objects.Transform):
|
||
|
|
if isinstance(sif_transform, api.BoneLinkTransform):
|
||
|
|
base_transform = sif_transform.base_value
|
||
|
|
else:
|
||
|
|
base_transform = sif_transform
|
||
|
|
|
||
|
|
position = self._adjust_coords(self._convert_vector(base_transform.offset))
|
||
|
|
rotation = self._adjust_angle(self._convert_scalar(base_transform.angle))
|
||
|
|
scale = self._adjust_animated(
|
||
|
|
self._convert_vector(base_transform.scale),
|
||
|
|
lambda x: x * 100
|
||
|
|
)
|
||
|
|
|
||
|
|
lottie_transform.skew_axis = self._adjust_angle(self._convert_scalar(base_transform.skew_angle))
|
||
|
|
|
||
|
|
if isinstance(sif_transform, api.BoneLinkTransform):
|
||
|
|
lottie_transform.position = position
|
||
|
|
lottie_transform.rotation = rotation
|
||
|
|
lottie_transform.scale = scale
|
||
|
|
#bone = sif_transform.bone
|
||
|
|
#b_pos = self._adjust_coords(self._convert_vector(bone.origin))
|
||
|
|
#old_anchor = lottie_transform.anchor_point
|
||
|
|
|
||
|
|
#if sif_transform.translate:
|
||
|
|
#self._mix_animations_into(
|
||
|
|
#[position, b_pos, old_anchor],
|
||
|
|
#lottie_transform.position,
|
||
|
|
#lambda base_p, bone_p, anchor: (anchor-self.target_size/2)/2+self.target_size/2
|
||
|
|
#)
|
||
|
|
#else:
|
||
|
|
#lottie_transform.position = position
|
||
|
|
|
||
|
|
#lottie_transform.anchor_point = b_pos
|
||
|
|
#lottie_transform.anchor_point.value += NVector(100,0)
|
||
|
|
|
||
|
|
#if sif_transform.rotate:
|
||
|
|
#b_rot = self._convert_scalar(bone.angle)
|
||
|
|
#self._mix_animations_into([rotation, b_rot], lottie_transform.rotation, lambda a, b: a-b)
|
||
|
|
#else:
|
||
|
|
#lottie_transform.rotation = rotation
|
||
|
|
|
||
|
|
#if sif_transform.scale_y:
|
||
|
|
#b_scale = self._convert_scalar(bone.scalelx)
|
||
|
|
#self._mix_animations_into(
|
||
|
|
#scale, b_scale, lottie_transform.scale,
|
||
|
|
#lambda a, b: NVector(a.x, a.y * b)
|
||
|
|
#)
|
||
|
|
#else:
|
||
|
|
#lottie_transform.scale = scale
|
||
|
|
else:
|
||
|
|
lottie_transform.position = position
|
||
|
|
lottie_transform.rotation = rotation
|
||
|
|
lottie_transform.scale = scale
|
||
|
|
|
||
|
|
def _mix_animations_into(self, animations, output, mix):
|
||
|
|
if not any(x.animated for x in animations):
|
||
|
|
output.value = mix(*(x.value for x in animations))
|
||
|
|
else:
|
||
|
|
for vals in self._mix_animations(*animations):
|
||
|
|
time = vals.pop(0)
|
||
|
|
output.add_keyframe(time, mix(*vals))
|
||
|
|
|
||
|
|
def _convert_fill(self, layer, converter):
|
||
|
|
shape = objects.Group()
|
||
|
|
self._set_name(shape, layer)
|
||
|
|
shape.add_shape(converter(layer))
|
||
|
|
if layer.invert.value:
|
||
|
|
shape.add_shape(objects.Rect(self.target_size/2, self.target_size))
|
||
|
|
|
||
|
|
fill = objects.Fill()
|
||
|
|
fill.color = self._convert_color(layer.color)
|
||
|
|
fill.opacity = self._adjust_animated(
|
||
|
|
self._convert_scalar(layer.amount),
|
||
|
|
lambda x: x * 100
|
||
|
|
)
|
||
|
|
shape.add_shape(fill)
|
||
|
|
return shape
|
||
|
|
|
||
|
|
def _convert_linecap(self, lc: api.LineCap):
|
||
|
|
if lc == api.LineCap.Rounded:
|
||
|
|
return objects.LineCap.Round
|
||
|
|
if lc == api.LineCap.Squared:
|
||
|
|
return objects.LineCap.Square
|
||
|
|
return objects.LineCap.Butt
|
||
|
|
|
||
|
|
def _convert_cusp(self, lc: api.CuspStyle):
|
||
|
|
if lc == api.CuspStyle.Miter:
|
||
|
|
return objects.LineJoin.Miter
|
||
|
|
if lc == api.CuspStyle.Bevel:
|
||
|
|
return objects.LineJoin.Bevel
|
||
|
|
return objects.LineJoin.Round
|
||
|
|
|
||
|
|
def _convert_outline(self, layer: api.AbstractOutline, converter):
|
||
|
|
shape = objects.Group()
|
||
|
|
self._set_name(shape, layer)
|
||
|
|
shape.add_shape(converter(layer))
|
||
|
|
stroke = objects.Stroke()
|
||
|
|
stroke.color = self._convert_color(layer.color)
|
||
|
|
stroke.line_cap = self._convert_linecap(layer.start_tip)
|
||
|
|
stroke.line_join = self._convert_cusp(layer.cusp_type)
|
||
|
|
stroke.width = self._adjust_scalar(self._convert_scalar(layer.width))
|
||
|
|
shape.add_shape(stroke)
|
||
|
|
return shape
|
||
|
|
|
||
|
|
def _convert_rect(self, layer: api.RectangleLayer):
|
||
|
|
rect = objects.Rect()
|
||
|
|
p1 = self._adjust_coords(self._convert_vector(layer.point1))
|
||
|
|
p2 = self._adjust_coords(self._convert_vector(layer.point2))
|
||
|
|
if p1.animated or p2.animated:
|
||
|
|
for time, p1v, p2v in self._mix_animations(p1, p2):
|
||
|
|
rect.position.add_keyframe(time, (p1v + p2v) / 2)
|
||
|
|
rect.size.add_keyframe(time, abs(p2v - p1v))
|
||
|
|
pass
|
||
|
|
else:
|
||
|
|
rect.position.value = (p1.value + p2.value) / 2
|
||
|
|
rect.size.value = abs(p2.value - p1.value)
|
||
|
|
rect.rounded = self._adjust_scalar(self._convert_scalar(layer.bevel))
|
||
|
|
return rect
|
||
|
|
|
||
|
|
def _convert_circle(self, layer: api.CircleLayer):
|
||
|
|
shape = objects.Ellipse()
|
||
|
|
shape.position = self._adjust_coords(self._convert_vector(layer.origin))
|
||
|
|
radius = self._adjust_scalar(self._convert_scalar(layer.radius))
|
||
|
|
shape.size = self._adjust_add_dimension(radius, lambda x: NVector(x, x) * 2)
|
||
|
|
return shape
|
||
|
|
|
||
|
|
def _convert_star(self, layer: api.StarLayer):
|
||
|
|
shape = objects.Star()
|
||
|
|
shape.position = self._adjust_coords(self._convert_vector(layer.origin))
|
||
|
|
shape.inner_radius = self._adjust_scalar(self._convert_scalar(layer.radius2))
|
||
|
|
shape.outer_radius = self._adjust_scalar(self._convert_scalar(layer.radius1))
|
||
|
|
shape.rotation = self._adjust_animated(
|
||
|
|
self._convert_scalar(layer.angle),
|
||
|
|
lambda x: 90-x
|
||
|
|
)
|
||
|
|
shape.points = self._convert_scalar(layer.points)
|
||
|
|
if layer.regular_polygon.value:
|
||
|
|
shape.star_type = objects.StarType.Polygon
|
||
|
|
return shape
|
||
|
|
|
||
|
|
def _mix_animations(self, *animatable):
|
||
|
|
times = set()
|
||
|
|
for v in animatable:
|
||
|
|
self._force_animated(v)
|
||
|
|
for kf in v.keyframes:
|
||
|
|
times.add(kf.time)
|
||
|
|
|
||
|
|
for time in sorted(times):
|
||
|
|
yield [time] + [v.get_value(time) for v in animatable]
|
||
|
|
|
||
|
|
def _force_animated(self, lottieval):
|
||
|
|
if not lottieval.animated:
|
||
|
|
v = lottieval.value
|
||
|
|
lottieval.add_keyframe(0, v)
|
||
|
|
lottieval.add_keyframe(self.animation.out_point, v)
|
||
|
|
|
||
|
|
def _convert_easing_part(self, interp: api.Interpolation):
|
||
|
|
if interp == api.Interpolation.Linear:
|
||
|
|
return easing.Linear()
|
||
|
|
return easing.Sigmoid()
|
||
|
|
|
||
|
|
def _convert_easing(self, start: api.Interpolation, end: api.Interpolation):
|
||
|
|
if api.Interpolation.Constant in (start, end):
|
||
|
|
return easing.Jump()
|
||
|
|
if start == end:
|
||
|
|
return self._convert_easing_part(start)
|
||
|
|
return easing.Split(self._convert_easing_part(start), self._convert_easing_part(end))
|
||
|
|
|
||
|
|
def _convert_animatable(self, v: ast.SifAstNode, lot: objects.properties.AnimatableMixin):
|
||
|
|
if self._animated(v):
|
||
|
|
if len(v.keyframes) == 1:
|
||
|
|
lot.value = self._convert_ast_value(v.keyframes[0].value)
|
||
|
|
else:
|
||
|
|
for i, kf in enumerate(v.keyframes):
|
||
|
|
if i+1 < len(v.keyframes):
|
||
|
|
start = kf.after
|
||
|
|
end = v.keyframes[i+1].before
|
||
|
|
ease = self._convert_easing(start, end)
|
||
|
|
else:
|
||
|
|
ease = easing.Linear()
|
||
|
|
|
||
|
|
lot.add_keyframe(self._time(kf.time), self._convert_ast_value(kf.value), ease)
|
||
|
|
else:
|
||
|
|
lot.value = self._convert_ast_value(v)
|
||
|
|
return lot
|
||
|
|
|
||
|
|
def _convert_ast_value(self, v):
|
||
|
|
if isinstance(v, ast.SifRadialComposite):
|
||
|
|
return self._polar(v.radius.value, v.theta.value, 1)
|
||
|
|
elif isinstance(v, ast.SifValue):
|
||
|
|
return v.value
|
||
|
|
elif isinstance(v, ast.SifVectorComposite):
|
||
|
|
return NVector(v.x.value, v.y.value)
|
||
|
|
else:
|
||
|
|
return v
|
||
|
|
|
||
|
|
def _converted_vector_values(self, v):
|
||
|
|
if isinstance(v, ast.SifRadialComposite):
|
||
|
|
return [self._convert_scalar(v.radius), self._convert_scalar(v.theta)]
|
||
|
|
return self._convert_vector(v)
|
||
|
|
|
||
|
|
def _convert_color(self, v: ast.SifAstNode):
|
||
|
|
return self._adjust_animated(
|
||
|
|
self._convert_animatable(v, objects.ColorValue()),
|
||
|
|
self._color_gamma
|
||
|
|
)
|
||
|
|
|
||
|
|
def _convert_vector(self, v: ast.SifAstNode):
|
||
|
|
return self._convert_animatable(v, objects.MultiDimensional())
|
||
|
|
|
||
|
|
def _convert_scalar(self, v: ast.SifAstNode):
|
||
|
|
return self._convert_animatable(v, objects.Value())
|
||
|
|
|
||
|
|
def _color_gamma(self, color):
|
||
|
|
color = color.clone()
|
||
|
|
for i in range(3):
|
||
|
|
color[i] = color[i] ** (1/self.gamma[i])
|
||
|
|
return color
|
||
|
|
|
||
|
|
def _adjust_animated(self, lottieval, transform):
|
||
|
|
if lottieval.animated:
|
||
|
|
for kf in lottieval.keyframes:
|
||
|
|
if kf.start is not None:
|
||
|
|
kf.start = transform(kf.start)
|
||
|
|
if kf.end is not None:
|
||
|
|
kf.end = transform(kf.end)
|
||
|
|
else:
|
||
|
|
lottieval.value = transform(lottieval.value)
|
||
|
|
return lottieval
|
||
|
|
|
||
|
|
def _adjust_scalar(self, lottieval: objects.Value):
|
||
|
|
return self._adjust_animated(lottieval, self._scalar_mult)
|
||
|
|
|
||
|
|
def _adjust_angle(self, lottieval: objects.Value):
|
||
|
|
return self._adjust_animated(lottieval, lambda x: -x)
|
||
|
|
|
||
|
|
def _adjust_add_dimension(self, lottieval, transform):
|
||
|
|
to_val = objects.MultiDimensional()
|
||
|
|
to_val.animated = lottieval.animated
|
||
|
|
if lottieval.animated:
|
||
|
|
to_val.keyframes = []
|
||
|
|
for kf in lottieval.keyframes:
|
||
|
|
if kf.start is not None:
|
||
|
|
kf.start = transform(kf.start[0])
|
||
|
|
if kf.end is not None:
|
||
|
|
kf.end = transform(kf.end[0])
|
||
|
|
to_val.keyframes.append(kf)
|
||
|
|
else:
|
||
|
|
to_val.value = transform(lottieval.value)
|
||
|
|
return to_val
|
||
|
|
|
||
|
|
def _scalar_mult(self, x):
|
||
|
|
return x * 60
|
||
|
|
|
||
|
|
def _adjust_coords(self, lottieval: objects.MultiDimensional):
|
||
|
|
return self._adjust_animated(lottieval, self._coord)
|
||
|
|
|
||
|
|
def _coord(self, val: NVector):
|
||
|
|
return NVector(
|
||
|
|
self.target_size.x * (val.x / (self.view_p2.x - self.view_p1.x) + 0.5),
|
||
|
|
self.target_size.y * (val.y / (self.view_p2.y - self.view_p1.y) + 0.5),
|
||
|
|
)
|
||
|
|
|
||
|
|
def _convert_polygon(self, layer: api.PolygonLayer):
|
||
|
|
lot = objects.Path()
|
||
|
|
animatables = [self._convert_vector(layer.origin)] + [
|
||
|
|
self._convert_vector(p)
|
||
|
|
for p in layer.points
|
||
|
|
]
|
||
|
|
animated = any(x.animated for x in animatables)
|
||
|
|
if not animated:
|
||
|
|
lot.shape.value = self._polygon([x.value for x in animatables[1:]], animatables[0].value)
|
||
|
|
else:
|
||
|
|
for values in self._mix_animations(*animatables):
|
||
|
|
time = values[0]
|
||
|
|
origin = values[1]
|
||
|
|
points = values[2:]
|
||
|
|
lot.shape.add_keyframe(time, self._polygon(points, origin))
|
||
|
|
return lot
|
||
|
|
|
||
|
|
def _polygon(self, points, origin):
|
||
|
|
bezier = objects.Bezier()
|
||
|
|
bezier.closed = True
|
||
|
|
for point in points:
|
||
|
|
bezier.add_point(self._coord(point+origin))
|
||
|
|
return bezier
|
||
|
|
|
||
|
|
def _convert_bline(self, layer: api.AbstractOutline):
|
||
|
|
lot = objects.Path()
|
||
|
|
closed = layer.bline.loop
|
||
|
|
animatables = [
|
||
|
|
self._convert_vector(layer.origin)
|
||
|
|
]
|
||
|
|
for p in layer.bline.points:
|
||
|
|
animatables += [
|
||
|
|
self._convert_vector(p.point),
|
||
|
|
self._convert_scalar(p.t1.radius) if hasattr(p.t1, "radius") else objects.Value(0),
|
||
|
|
self._convert_scalar(p.t1.theta) if hasattr(p.t1, "radius") else objects.Value(0),
|
||
|
|
self._convert_scalar(p.t2.radius) if hasattr(p.t2, "radius") else objects.Value(0),
|
||
|
|
self._convert_scalar(p.t2.theta) if hasattr(p.t2, "radius") else objects.Value(0)
|
||
|
|
]
|
||
|
|
animated = any(x.animated for x in animatables)
|
||
|
|
if not animated:
|
||
|
|
lot.shape.value = self._bezier(
|
||
|
|
closed, [x.value for x in animatables[1:]], animatables[0].value, layer.bline.points
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
for values in self._mix_animations(*animatables):
|
||
|
|
time = values[0]
|
||
|
|
origin = values[1]
|
||
|
|
values = values[2:]
|
||
|
|
lot.shape.add_keyframe(time, self._bezier(closed, values, origin, layer.bline.points))
|
||
|
|
return lot
|
||
|
|
|
||
|
|
def _bezier(self, closed, values, origin, points):
|
||
|
|
chunk_size = 5
|
||
|
|
bezier = objects.Bezier()
|
||
|
|
bezier.closed = closed
|
||
|
|
for i in range(0, len(values), chunk_size):
|
||
|
|
point, r1, a1, r2, a2 = values[i:i+chunk_size]
|
||
|
|
sifvert = point+origin
|
||
|
|
vert = self._coord(sifvert)
|
||
|
|
if not points[i//chunk_size].split_radius.value:
|
||
|
|
r2 = r1
|
||
|
|
if not points[i//chunk_size].split_angle.value:
|
||
|
|
a2 = a1
|
||
|
|
t1 = self._coord(sifvert + self._polar(r1, a1, 1)) - vert
|
||
|
|
t2 = self._coord(sifvert + self._polar(r2, a2, 2)) - vert
|
||
|
|
bezier.add_point(vert, t1, t2)
|
||
|
|
return bezier
|
||
|
|
|
||
|
|
def _polar(self, radius, angle, dir):
|
||
|
|
offset_angle = 0
|
||
|
|
if dir == 1:
|
||
|
|
offset_angle += 180
|
||
|
|
return PolarVector(radius/3, (angle+offset_angle) * math.pi / 180)
|
||
|
|
|
||
|
|
def _convert_transform_down(self, tl: api.TransformDown):
|
||
|
|
group = objects.Group()
|
||
|
|
self._set_name(group, tl)
|
||
|
|
|
||
|
|
if isinstance(tl, api.TranslateLayer):
|
||
|
|
group.transform.anchor_point.value = self.target_size / 2
|
||
|
|
group.transform.position = self._adjust_coords(self._convert_vector(tl.origin))
|
||
|
|
elif isinstance(tl, api.RotateLayer):
|
||
|
|
group.transform.anchor_point = self._adjust_coords(self._convert_vector(tl.origin))
|
||
|
|
group.transform.position = group.transform.anchor_point.clone()
|
||
|
|
group.transform.rotation = self._adjust_angle(self._convert_scalar(tl.amount))
|
||
|
|
elif isinstance(tl, api.ScaleLayer):
|
||
|
|
group.transform.anchor_point = self._adjust_coords(self._convert_vector(tl.center))
|
||
|
|
group.transform.position = group.transform.anchor_point.clone()
|
||
|
|
group.transform.scale = self._adjust_add_dimension(
|
||
|
|
self._convert_scalar(tl.amount),
|
||
|
|
self._zoom_to_scale
|
||
|
|
)
|
||
|
|
|
||
|
|
return group
|
||
|
|
|
||
|
|
def _zoom_to_scale(self, value):
|
||
|
|
zoom = math.e ** value * 100
|
||
|
|
return NVector(zoom, zoom)
|
||
|
|
|
||
|
|
def _set_name(self, lottie, sif):
|
||
|
|
lottie.name = sif.desc if sif.desc is not None else sif.__class__.__name__
|
||
|
|
|
||
|
|
def _convert_gradient(self, layer: api.GradientLayer, parent):
|
||
|
|
group = objects.Group()
|
||
|
|
|
||
|
|
parent_shapes = parent.shapes
|
||
|
|
parent.shapes = []
|
||
|
|
if isinstance(parent, objects.Group):
|
||
|
|
parent.shapes.append(parent_shapes[-1])
|
||
|
|
|
||
|
|
self._gradient_gather_shapes(parent_shapes, group)
|
||
|
|
|
||
|
|
gradient = objects.GradientFill()
|
||
|
|
self._set_name(gradient, layer)
|
||
|
|
group.add_shape(gradient)
|
||
|
|
gradient.colors = self._convert_gradient_stops(layer.gradient)
|
||
|
|
gradient.opacity = self._adjust_animated(
|
||
|
|
self._convert_scalar(layer.amount),
|
||
|
|
lambda x: x * 100
|
||
|
|
)
|
||
|
|
|
||
|
|
if isinstance(layer, api.LinearGradient):
|
||
|
|
gradient.start_point = self._adjust_coords(self._convert_vector(layer.p1))
|
||
|
|
gradient.end_point = self._adjust_coords(self._convert_vector(layer.p2))
|
||
|
|
gradient.gradient_type = objects.GradientType.Linear
|
||
|
|
elif isinstance(layer, api.RadialGradient):
|
||
|
|
gradient.gradient_type = objects.GradientType.Radial
|
||
|
|
gradient.start_point = self._adjust_coords(self._convert_vector(layer.center))
|
||
|
|
radius = self._adjust_animated(self._convert_scalar(layer.radius), lambda x: x*45)
|
||
|
|
if not radius.animated and not gradient.start_point.animated:
|
||
|
|
gradient.end_point.value = gradient.start_point.value + NVector(radius.value, radius.value)
|
||
|
|
else:
|
||
|
|
for time, c, r in self._mix_animations(gradient.start_point.clone(), radius):
|
||
|
|
gradient.end_point.add_keyframe(time, c + NVector(r + r))
|
||
|
|
|
||
|
|
return group
|
||
|
|
|
||
|
|
def _gradient_gather_shapes(self, shapes, output: objects.Group):
|
||
|
|
for shape in shapes:
|
||
|
|
if isinstance(shape, objects.Shape):
|
||
|
|
output.add_shape(shape)
|
||
|
|
elif isinstance(shape, objects.Group):
|
||
|
|
self._gradient_gather_shapes(shape.shapes, output)
|
||
|
|
|
||
|
|
def _convert_gradient_stops(self, sif_gradient):
|
||
|
|
stops = objects.GradientColors()
|
||
|
|
if not self._animated(sif_gradient):
|
||
|
|
stops.set_stops(self._flatten_gradient_colors(sif_gradient.value))
|
||
|
|
stops.count = len(sif_gradient.value)
|
||
|
|
else:
|
||
|
|
# TODO easing
|
||
|
|
for kf in sif_gradient.keyframes:
|
||
|
|
stops.add_keyframe(self._time(kf.time), self._flatten_gradient_colors(kf.value))
|
||
|
|
stops.count = len(kf.value)
|
||
|
|
|
||
|
|
return stops
|
||
|
|
|
||
|
|
def _flatten_gradient_colors(self, stops):
|
||
|
|
return [
|
||
|
|
(stop.pos, self._color_gamma(stop.color))
|
||
|
|
for stop in stops
|
||
|
|
]
|
||
|
|
|
||
|
|
def _convert_text(self, layer: api.TextLayer):
|
||
|
|
shape = font.FontShape(layer.text.value, font.FontStyle(layer.family.value, 110, font.TextJustify.Center))
|
||
|
|
shape.refresh()
|
||
|
|
trans = shape.wrapped.transform
|
||
|
|
trans.anchor_point.value = shape.wrapped.bounding_box().center()
|
||
|
|
trans.anchor_point.value.x /= 2
|
||
|
|
trans.position = self._adjust_coords(self._convert_vector(layer.origin))
|
||
|
|
trans.scale = self._adjust_animated(
|
||
|
|
self._convert_vector(layer.size),
|
||
|
|
lambda v: v * 100
|
||
|
|
)
|
||
|
|
return shape
|