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

0001 from PIL import Image
0002 from .. import objects
0003 from .. import NVector, Color
0004 from ..utils import color
0005 
0006 
0007 class Polygen:
0008     def __init__(self, x, y):
0009         self.vertices = [
0010             NVector(x, y),
0011             NVector(x+1, y),
0012             NVector(x+1, y+1),
0013             NVector(x, y+1),
0014         ]
0015         self._has_x = False
0016         self._has_y = False
0017 
0018     def add_pixel_x(self, x, y):
0019         i = self.vertices.index(NVector(x, y))
0020         if len(self.vertices) > i and self.vertices[i+1] == NVector(x, y+1):
0021             self._has_x = True
0022             self.vertices.insert(i+1, NVector(x+1, y))
0023             self.vertices.insert(i+2, NVector(x+1, y+1))
0024         else:
0025             raise ValueError()
0026 
0027     def add_pixel_x_neg(self, x, y):
0028         i = self.vertices.index(NVector(x+1, y))
0029         if i > 0 and self.vertices[i-1] == NVector(x+1, y+1):
0030             self._has_x = True
0031             self.vertices.insert(i, NVector(x, y))
0032             self.vertices.insert(i, NVector(x, y+1))
0033         else:
0034             raise ValueError()
0035 
0036     def add_pixel_y(self, x, y):
0037         i = self.vertices.index(NVector(x, y))
0038         if i > 0 and self.vertices[i-1] == NVector(x+1, y):
0039             self._has_y = True
0040             if i > 1 and self.vertices[i-2] == NVector(x+1, y+1):
0041                 self.vertices[i-1] = NVector(x, y+1)
0042             else:
0043                 self.vertices.insert(i, NVector(x, y+1))
0044                 self.vertices.insert(i, NVector(x+1, y+1))
0045         else:
0046             raise ValueError()
0047 
0048     def _to_rect(self, id1, id2):
0049         p1 = self.vertices[id1]
0050         p2 = self.vertices[id2]
0051         return objects.Rect((p1+p2)/2, p2-p1)
0052 
0053     def to_shape(self):
0054         if not self._has_x or not self._has_y:
0055             return self._to_rect(0, int(len(self.vertices)/2))
0056         bez = objects.Bezier()
0057         bez.closed = True
0058         for point in self.vertices:
0059             if len(bez.vertices) > 1 and (
0060                 bez.vertices[-1].x == bez.vertices[-2].x == point.x or
0061                 bez.vertices[-1].y == bez.vertices[-2].y == point.y
0062             ):
0063                 bez.vertices[-1] = point
0064             else:
0065                 bez.add_point(point)
0066 
0067         if len(bez.vertices) > 2 and bez.vertices[0].x == bez.vertices[-1].x == bez.vertices[-2].x:
0068             bez.vertices.pop()
0069             bez.out_tangents.pop()
0070             bez.in_tangents.pop()
0071         return objects.Path(bez)
0072 
0073 
0074 def pixel_add_layer_paths(animation, raster):
0075     layer = animation.add_layer(objects.ShapeLayer())
0076     groups = {}
0077     processed = set()
0078     xneg_candidates = set()
0079 
0080     def avail(x, y):
0081         rid = (x, y)
0082         return not (
0083             x < 0 or x >= raster.width or y >= raster.height or
0084             rid in processed or raster.getpixel(rid) != colort
0085         )
0086 
0087     def recurse(gen, x, y, xneg):
0088         processed.add((x, y))
0089         if avail(x+1, y):
0090             gen.add_pixel_x(x+1, y)
0091             recurse(gen, x+1, y, False)
0092         if avail(x, y+1):
0093             gen.add_pixel_y(x, y+1)
0094             recurse(gen, x, y+1, True)
0095         if xneg and avail(x-1, y):
0096             xneg_candidates.add((x-1, y))
0097 
0098     for y in range(raster.height):
0099         for x in range(raster.width):
0100             pid = (x, y)
0101             colort = raster.getpixel(pid)
0102             if colort[-1] == 0 or pid in processed:
0103                 continue
0104 
0105             gen = Polygen(x, y)
0106             xneg_candidates = set()
0107             recurse(gen, x, y, False)
0108             xneg_candidates -= processed
0109             while xneg_candidates:
0110                 p = next(iter(sorted(xneg_candidates, key=lambda t: (t[1], t[0]))))
0111                 gen.add_pixel_x_neg(*p)
0112                 recurse(gen, p[0], p[1], True)
0113                 processed.add(p)
0114                 xneg_candidates -= processed
0115 
0116             g = groups.setdefault(colort, set())
0117             g.add(gen.to_shape())
0118 
0119     for colort, rects in groups.items():
0120         g = layer.add_shape(objects.Group())
0121         g.shapes = list(rects) + g.shapes
0122         g.name = "".join("%02x" % c for c in colort)
0123         fill = g.add_shape(objects.Fill())
0124         fill.color.value = color.from_uint8(*colort[:3])
0125         fill.opacity.value = colort[-1] / 255 * 100
0126         stroke = g.add_shape(objects.Stroke(fill.color.value, 0.1))
0127         stroke.opacity.value = fill.opacity.value
0128     return layer
0129 
0130 
0131 def pixel_add_layer_rects(animation, raster):
0132     layer = animation.add_layer(objects.ShapeLayer())
0133     last_rects = {}
0134     groups = {}
0135 
0136     def merge_up():
0137         if last_rect and last_rect._start in last_rects:
0138             yrect = last_rects[last_rect._start]
0139             if yrect.size.value.x == last_rect.size.value.x and yrect._color == last_rect._color:
0140                 groups[last_rect._color].remove(last_rect)
0141                 yrect.position.value.y += 0.5
0142                 yrect.size.value.y += 1
0143                 rects[last_rect._start] = yrect
0144 
0145     def group(colort):
0146         return groups.setdefault(colort, set())
0147 
0148     for y in range(raster.height):
0149         rects = {}
0150         last_color = None
0151         last_rect = None
0152         for x in range(raster.width):
0153             colort = raster.getpixel((x, y))
0154             if colort[-1] == 0:
0155                 continue
0156             yrect = last_rects.get(x, None)
0157             if colort == last_color:
0158                 last_rect.position.value.x += 0.5
0159                 last_rect.size.value.x += 1
0160             elif yrect and colort == yrect._color and yrect.size.value.x == 1:
0161                 yrect.position.value.y += 0.5
0162                 yrect.size.value.y += 1
0163                 rects[x] = yrect
0164                 last_color = last_rect = colort = None
0165             else:
0166                 merge_up()
0167                 g = group(colort)
0168                 last_rect = objects.Rect()
0169                 g.add(last_rect)
0170                 last_rect.size.value = NVector(1, 1)
0171                 last_rect.position.value = NVector(x + 0.5, y + 0.5)
0172                 rects[x] = last_rect
0173                 last_rect._start = x
0174                 last_rect._color = colort
0175             last_color = colort
0176         merge_up()
0177         last_rects = rects
0178 
0179     for colort, rects in groups.items():
0180         g = layer.add_shape(objects.Group())
0181         g.shapes = list(rects) + g.shapes
0182         g.name = "".join("%02x" % c for c in colort)
0183         fill = g.add_shape(objects.Fill())
0184         fill.color.value = color.from_uint8(*colort[:3])
0185         fill.opacity.value = colort[-1] / 255 * 100
0186         stroke = g.add_shape(objects.Stroke(fill.color.value, 0.1))
0187         stroke.opacity.value = fill.opacity.value
0188     return layer
0189 
0190 
0191 def _vectorizing_func(filenames, frame_delay, framerate, callback):
0192     if not isinstance(filenames, list):
0193         filenames = [filenames]
0194 
0195     animation = objects.Animation(0, framerate)
0196     nframes = 0
0197 
0198     for filename in filenames:
0199         raster = Image.open(filename)
0200         if nframes == 0:
0201             animation.width = raster.width
0202             animation.height = raster.height
0203         if not hasattr(raster, "is_animated"):
0204             raster.n_frames = 1
0205             raster.seek = lambda x: None
0206         for frame in range(raster.n_frames):
0207             raster.seek(frame)
0208             new_im = Image.new("RGBA", raster.size)
0209             new_im.paste(raster)
0210             callback(animation, new_im, nframes + frame)
0211             new_im.close()
0212         nframes += raster.n_frames
0213 
0214     animation.out_point = frame_delay * nframes
0215     #animation._nframes = nframes
0216 
0217     return animation
0218 
0219 
0220 def raster_to_embedded_assets(filenames, frame_delay=1, framerate=60, embed_format=None):
0221     """!
0222     @brief Loads external assets
0223     """
0224     def callback(animation, raster, frame):
0225         asset = objects.assets.Image.embedded(raster, embed_format)
0226         animation.assets.append(asset)
0227         layer = animation.add_layer(objects.ImageLayer(asset.id))
0228         layer.in_point = frame * frame_delay
0229         layer.out_point = layer.in_point + frame_delay
0230 
0231     return _vectorizing_func(filenames, frame_delay, framerate, callback)
0232 
0233 
0234 def raster_to_linked_assets(filenames, frame_delay=1, framerate=60):
0235     """!
0236     @brief Loads external assets
0237     """
0238     animation = objects.Animation(frame_delay * len(filenames), framerate)
0239 
0240     for frame, filename in enumerate(filenames):
0241         asset = objects.assets.Image.linked(filename)
0242         animation.assets.append(asset)
0243         layer = animation.add_layer(objects.ImageLayer(asset.id))
0244         layer.in_point = frame * frame_delay
0245         layer.out_point = layer.in_point + frame_delay
0246 
0247     return animation
0248 
0249 
0250 def pixel_to_animation(filenames, frame_delay=1, framerate=60):
0251     """!
0252     @brief Converts pixel art to vector
0253     """
0254     def callback(animation, raster, frame):
0255         layer = pixel_add_layer_rects(animation, raster.convert("RGBA"))
0256         layer.in_point = frame * frame_delay
0257         layer.out_point = layer.in_point + frame_delay
0258 
0259     return _vectorizing_func(filenames, frame_delay, framerate, callback)
0260 
0261 
0262 def pixel_to_animation_paths(filenames, frame_delay=1, framerate=60):
0263     """!
0264     @brief Converts pixel art to vector paths
0265 
0266     Slower and yields larger files compared to pixel_to_animation,
0267     but it produces a single shape for each area with the same color.
0268     Mostly useful when you want to add your own animations to the loaded image
0269     """
0270     def callback(animation, raster, frame):
0271         layer = pixel_add_layer_paths(animation, raster.convert("RGBA"))
0272         layer.in_point = frame * frame_delay
0273         layer.out_point = layer.in_point + frame_delay
0274 
0275     return _vectorizing_func(filenames, frame_delay, framerate, callback)