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)