File indexing completed on 2025-01-19 03:59:54

0001 # NOTE: requires pillow, pypotrace>=0.2, numpy, scipy to be installed
0002 from PIL import Image
0003 import potrace
0004 import numpy
0005 import enum
0006 from scipy.cluster.vq import kmeans
0007 from .. import objects
0008 from ..nvector import NVector
0009 from .pixel import _vectorizing_func
0010 
0011 
0012 class QuanzationMode(enum.Enum):
0013     Nearest = 1
0014     Exact = 2
0015 
0016 
0017 class RasterImage:
0018     def __init__(self, data):
0019         self.data = data
0020 
0021     @classmethod
0022     def from_pil(cls, image):
0023         return cls(numpy.array(image))
0024 
0025     #@classmethod
0026     #def open(cls, filename):
0027         #return cls.from_pil(Image.open(filename))
0028 
0029     def k_means(self, n_colors):
0030         """!
0031         Returns a list of centroids
0032         """
0033         colors = []
0034         for row in range(self.data.shape[0]):
0035             for column in range(self.data.shape[1]):
0036                 if self.get_alpha(row, column) == 255:
0037                     colors.append(self.data[row][column])
0038 
0039         colors = numpy.array(colors, numpy.float)
0040         return kmeans(colors, n_colors+1)[0]
0041 
0042     def get_alpha(self, row, column):
0043         if self.data.shape[2] >= 4:
0044             return self.data[row][column][3]
0045         return 255
0046 
0047     def quantize(self, codebook, quantization_mode=QuanzationMode.Nearest):
0048         """!
0049         Returns a list of tuple [color, data] where for each color in codebook
0050         data is a bit mask for the image
0051 
0052         You can get codebook from k_means
0053         """
0054         if codebook is None or len(codebook) == 0:
0055             return [(numpy.array([0., 0., 0., 255.]), self.mono())]
0056 
0057         mono_data = []
0058         for c in codebook:
0059             mono_data.append((c, numpy.zeros(self.data.shape[:2])))
0060 
0061         for row in range(self.data.shape[0]):
0062             for column in range(self.data.shape[1]):
0063                 if self.get_alpha(row, column) == 255:
0064                     if quantization_mode == QuanzationMode.Nearest:
0065                         min_norm = 511 # (norm of [255, 255, 255, 255]) + 1
0066                         best = None
0067                         for color, bitmap in mono_data:
0068                             norm = numpy.linalg.norm(self.data[row][column] - color)
0069                             if norm < min_norm:
0070                                 min_norm = norm
0071                                 best = bitmap
0072                                 if norm == 0:
0073                                     break
0074                         best[row][column] = 1
0075                     else:
0076                         for color, bitmap in mono_data:
0077                             if numpy.array_equal(color, self.data[row][column]):
0078                                 bitmap[row][column] = 1
0079                                 break
0080 
0081         return mono_data
0082 
0083     def mono(self):
0084         """!
0085         Returns a bit mask of opaque pixels
0086         """
0087         mono_data = numpy.zeros(self.data.shape[:2])
0088         for row in range(self.data.shape[0]):
0089             for column in range(self.data.shape[1]):
0090                 mono_data[row][column] = int(self.data[row][column][3] == 255)
0091         return mono_data
0092 
0093 
0094 class Vectorizer:
0095     def __init__(self):
0096         self.palette = None
0097         self.layers = {}
0098 
0099     def _create_layer(self, animation, layer_name):
0100         layer = animation.add_layer(objects.ShapeLayer())
0101         if layer_name:
0102             self.layers[layer_name] = layer
0103             layer.name = layer_name
0104         return layer
0105 
0106     def prepare_layer(self, animation, layer_name=None):
0107         layer = self._create_layer(animation, layer_name)
0108         layer._max_verts = {}
0109         if self.palette is None:
0110             group = layer.add_shape(objects.Group())
0111             group.name = "bitmap"
0112             layer._max_verts[group.name] = 0
0113             group.add_shape(objects.Path())
0114             group.add_shape(objects.Fill(NVector(0, 0, 0)))
0115         else:
0116             for color in self.palette:
0117                 group = layer.add_shape(objects.Group())
0118                 group.name = "color_%s" % "".join("%02x" % int(c) for c in color)
0119                 layer._max_verts[group.name] = 0
0120                 fcol = color/255
0121                 fill = group.add_shape(objects.Fill(NVector(*fcol)))
0122                 if len(fcol) > 3 and fcol[3] < 1:
0123                     fill.opacity.value = fcol[3] * 100
0124         return layer
0125 
0126     def raster_to_layer(self, animation, raster, layer_name=None, mode=QuanzationMode.Nearest):
0127         layer = self.prepare_layer(animation, layer_name)
0128         mono_data = raster.quantize(self.palette, mode)
0129         for (color, bitmap), group in zip(mono_data, layer.shapes):
0130             self.raster_to_shapes(group, bitmap)
0131         return layer
0132 
0133     def raster_to_shapes(self, group, mono_data):
0134         shapes = []
0135         for bezier in self.raster_to_bezier(mono_data):
0136             shape = group.insert_shape(0, objects.Path())
0137             shapes.append(shape)
0138             shape.shape.value = bezier
0139         return shapes
0140 
0141     def raster_to_bezier(self, mono_data):
0142         bmp = potrace.Bitmap(mono_data)
0143         path = bmp.trace()
0144         shapes = []
0145         for curve in path:
0146             bezier = objects.Bezier()
0147             shapes.append(bezier)
0148             bezier.add_point(NVector(*curve.start_point))
0149             for segment in curve:
0150                 if segment.is_corner:
0151                     bezier.add_point(NVector(*segment.c))
0152                     bezier.add_point(NVector(*segment.end_point))
0153                 else:
0154                     sp = NVector(*bezier.vertices[-1])
0155                     ep = NVector(*segment.end_point)
0156                     c1 = NVector(*segment.c1) - sp
0157                     c2 = NVector(*segment.c2) - ep
0158                     bezier.out_tangents[-1] = c1
0159                     bezier.add_point(ep, c2)
0160         return shapes
0161 
0162     def _frame_keyframe(self, layer, group, time, shapes, beziers):
0163         if shapes:
0164             # TODO handle multiple shapes
0165             nverts = len(beziers[0].vertices)
0166             if nverts > layer._max_verts[group.name]:
0167                 layer._max_verts[group.name] = nverts
0168             for shape, bezier in zip(shapes, beziers):
0169                 shape.shape.add_keyframe(time, bezier)
0170 
0171     def raster_to_frame(self, animation, raster, layer_name, time, mode=QuanzationMode.Nearest):
0172         mono_data = raster.quantize(self.palette, mode)
0173 
0174         if layer_name not in self.layers:
0175             layer = self.prepare_layer(animation, layer_name)
0176             for (color, bitmap), group in zip(mono_data, layer.shapes):
0177                 shapes = self.raster_to_shapes(group, bitmap)
0178                 beziers = [s.shape.value for s in shapes]
0179                 self._frame_keyframe(layer, group, time, shapes, beziers)
0180         else:
0181             layer = self.layers[layer_name]
0182 
0183             for (color, bitmap), group in zip(mono_data, layer.shapes):
0184                 shapes = [s for s in group.shapes if isinstance(s, objects.Path)]
0185                 beziers = self.raster_to_bezier(bitmap)
0186                 self._frame_keyframe(layer, group, time, shapes, beziers)
0187 
0188     def adjust_missing_vertices(self, layer_name):
0189         layer = self.layers[layer_name]
0190         for group in layer.shapes:
0191             # TODO handle multiple shapes
0192             shape = group.shapes[0]
0193             nverts = layer._max_verts[group.name]
0194             if shape.shape.animated:
0195                 for kf in shape.shape.keyframes:
0196                     bezier = kf.start
0197                     count = nverts - len(bezier.vertices)
0198                     bezier.vertices += [bezier.vertices[-1]] * count
0199                     bezier.in_tangents += [NVector(0, 0)] * count
0200                     bezier.out_tangents += [NVector(0, 0)] * count
0201 
0202     def duplicate_start_frame(self, layer_name, time):
0203         layer = self.layers[layer_name]
0204         for group in layer.shapes:
0205             shape = group.shapes[0]
0206             bezier = shape.shape.keyframes[0].start
0207             group.shapes[0].shape.add_keyframe(time, bezier)
0208 
0209 
0210 def color2numpy(vcolor):
0211     l = (vcolor * 255).components
0212     if len(l) == 3:
0213         l.append(255)
0214     return numpy.array(l, numpy.uint8)
0215 
0216 
0217 def raster_to_animation(filenames, n_colors=1, frame_delay=1,
0218                         looping=True, framerate=60, palette=[],
0219                         mode=QuanzationMode.Nearest):
0220 
0221     vc = Vectorizer()
0222 
0223     def callback(animation, raster, frame):
0224         raster = RasterImage.from_pil(raster)
0225         if vc.palette is None:
0226             if palette:
0227                 vc.palette = [color2numpy(c) for c in palette]
0228             elif n_colors > 1:
0229                 vc.palette = raster.k_means(n_colors)
0230         #vc.raster_to_frame(animation, raster, "anim", frame * frame_delay, mode)
0231         layer = vc.raster_to_layer(animation, raster, "frame_%s" % frame, mode)
0232         layer.in_point = frame * frame_delay
0233         layer.out_point = (frame + 1) * frame_delay
0234 
0235     animation = _vectorizing_func(filenames, frame_delay, framerate, callback)
0236 
0237     #vc.adjust_missing_vertices("anim")
0238     #if looping and animation._nframes > 1:
0239         #animation.out_point += frame_delay
0240         #vc.duplicate_start_frame("anim", animation.out_point)
0241     #elif animation._nframes == 1:
0242         #for g in animation.find("anim").shapes:
0243             #for shape in g.find_all(objects.Path):
0244                 #shape.shape.clear_animation(shape.shape.get_value(0))
0245 
0246     #animation.find("anim").out_point = animation.out_point
0247 
0248     return animation