Coverage for lib/lottie/parsers/raster.py: 0%
122 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-20 16:17 +0100
1from PIL import Image
2import glaxnimate
3from . import glaxnimate_helpers
4import enum
5from .. import objects
6from ..nvector import NVector
7from .pixel import _vectorizing_func
10class QuantizationMode(enum.Enum):
11 Nearest = 1
12 Exact = 2
15class PaletteAlgorithm:
16 def get_colors(self, image, n_colors):
17 pass
20class KMeansPalette(PaletteAlgorithm):
21 def __init__(self, iterations=100, match=glaxnimate.utils.quantize.MatchType.MostFrequent):
22 self.iterations = iterations
23 self.match = match
25 def get_colors(self, image, n_colors):
26 return glaxnimate.utils.quantize.k_means(image, n_colors, self.iterations, self.match)
29class OctreePalette(PaletteAlgorithm):
30 def get_colors(self, image, n_colors):
31 return glaxnimate.utils.quantize.octree(image, n_colors)
34class KModesPalette(PaletteAlgorithm):
35 def get_colors(self, image, n_colors):
36 return glaxnimate.utils.quantize.k_modes(image, n_colors)
39class EdgeExclusionModesPalette(PaletteAlgorithm):
40 def __init__(self, min_frequency=0.0005):
41 self.min_frequency = min_frequency
43 def get_colors(self, image, n_colors):
44 return glaxnimate.utils.quantize.edge_exclusion_modes(image, n_colors, self.min_frequency)
47class TraceOptions:
48 def __init__(
49 self,
50 color_mode=QuantizationMode.Nearest,
51 palette_algorithm=OctreePalette(),
52 tolerance=100,
53 stroke_width=1,
54 smoothness=0.75,
55 min_area=16
56 ):
57 self.trace_options = glaxnimate.utils.trace.TraceOptions()
58 self.palette_algorithm = palette_algorithm
59 self.color_mode = color_mode
60 self.tolerance = tolerance
61 self.stroke_width = stroke_width
62 self.min_area = min_area
63 self.smoothness = smoothness
65 @property
66 def smoothness(self):
67 return self.trace_options.smoothness
69 @smoothness.setter
70 def smoothness(self, value):
71 self.trace_options.smoothness = value
73 @property
74 def min_area(self):
75 return self.trace_options.min_area
77 @min_area.setter
78 def min_area(self, value):
79 self.trace_options.min_area = value
81 def quantize(self, image, n_colors):
82 """!
83 Returns a list of RGB values
84 """
85 return self.palette_algorithm.get_colors(image, n_colors)
87 def trace(self, image, codebook):
88 """!
89 Returns a list of tuple [color, data] where for each color in codebook
90 data is a list of bezier
92 You can get codebook from quantize()
93 """
95 if codebook is None or len(codebook) == 0:
96 tracer = glaxnimate.utils.trace.Tracer(image, self.trace_options)
97 tracer.set_target_alpha(128, False)
98 return [glaxnimate.utils.Color(0, 0, 0), tracer.trace()]
100 if self.color_mode == QuantizationMode.Nearest:
101 return list(zip(codebook, glaxnimate.utils.trace.quantize_and_trace(image, self.trace_options, codebook)))
103 mono_data = []
104 tracer = glaxnimate.utils.trace.Tracer(image, self.trace_options)
105 for color in codebook:
106 tracer.set_target_color(color, self.tolerance)
107 mono_data.append((color, tracer.trace()))
109 return mono_data
112class Vectorizer:
113 def __init__(self, trace_options: TraceOptions):
114 self.palette = None
115 self.layers = {}
116 self.trace_options = trace_options
118 def _create_layer(self, animation, layer_name):
119 layer = animation.add_layer(objects.ShapeLayer())
120 if layer_name:
121 self.layers[layer_name] = layer
122 layer.name = layer_name
123 return layer
125 def prepare_layer(self, animation, layer_name=None):
126 layer = self._create_layer(animation, layer_name)
127 layer._max_verts = {}
128 for color in self.palette:
129 group = layer.add_shape(objects.Group())
130 group.name = "color_%s" % color.name
131 layer._max_verts[group.name] = 0
132 fcol = glaxnimate_helpers.color_from_glaxnimate(color)
133 group.add_shape(objects.Fill(NVector(*fcol)))
134 if self.trace_options.stroke_width > 0:
135 group.add_shape(objects.Stroke(NVector(*fcol), self.trace_options.stroke_width))
136 return layer
138 def raster_to_layer(self, animation, raster, layer_name=None):
139 layer = self.prepare_layer(animation, layer_name)
140 mono_data = self.trace_options.trace(raster, self.palette)
141 for (color, beziers), group in zip(mono_data, layer.shapes):
142 self.traced_to_shapes(group, beziers)
143 return layer
145 def traced_to_shapes(self, group, beziers):
146 shapes = []
147 for bezier in beziers:
148 shape = group.insert_shape(0, objects.Path())
149 shapes.append(shape)
150 shape.shape.value = self.traced_to_bezier(bezier)
151 return shapes
153 def traced_to_bezier(self, path):
154 bezier = objects.Bezier()
155 for point in path:
156 pos = glaxnimate_helpers.point_from_glaxnimate(point.pos)
157 tan_in = glaxnimate_helpers.point_from_glaxnimate(point.tan_in - point.pos)
158 tan_out = glaxnimate_helpers.point_from_glaxnimate(point.tan_out - point.pos)
159 bezier.add_point(pos, tan_in, tan_out)
160 if path.closed:
161 bezier.closed = True
162 return bezier
165def raster_to_animation(
166 filenames,
167 n_colors=1,
168 frame_delay=1,
169 framerate=60,
170 palette=[],
171 trace_options=TraceOptions()
172):
173 vc = Vectorizer(trace_options)
175 def callback(animation, raster, frame, time, duration):
176 if vc.palette is None:
177 if palette:
178 vc.palette = [glaxnimate_helpers.color_to_glaxnimate(c) for c in palette]
179 elif n_colors > 1:
180 vc.palette = trace_options.quantize(raster, n_colors)
181 else:
182 vc.palette = [glaxnimate.utils.Color(0, 0, 0, 255)]
183 layer = vc.raster_to_layer(animation, raster, "frame_%s" % frame)
184 layer.in_point = time
185 layer.out_point = layer.in_point + duration
187 animation = _vectorizing_func(filenames, frame_delay, framerate, callback)
189 return animation