def __init__(self, glyphName, width=500, **kwargs): r = Rect(kwargs.get("rect", Rect(1000, 1000))) kwargs.pop("rect", None) self.width = width self.body = r.take(750, "mdy").take(self.width, "mdx") self.glyphName = glyphName super().__init__(rect=r, **kwargs)
class animation(renderable, Timeable): """ Base class for any frame-wise animation animatable by Coldtype """ def __init__(self, rect=(1080, 1080), duration=10, storyboard=[0], timeline: Timeline = None, audio=None, **kwargs): super().__init__(**kwargs) self.rect = Rect(rect) self.r = self.rect self.audio = audio if self.audio and sf: self.wavfile = Wavfile(audio) else: self.wavfile = None self.start = 0 self.end = duration self.storyboard = storyboard if timeline: self.timeline = timeline self.t = timeline self.start = timeline.start self.end = timeline.end if self.storyboard != [0] and timeline.storyboard == [0]: pass else: self.storyboard = timeline.storyboard.copy() else: self.timeline = Timeline(duration) def __call__(self, func): res = super().__call__(func) self.prefix = self.name + "_" return res def folder(self, filepath): return filepath.stem + "/" + self.name # TODO necessary? def all_frames(self): return list(range(0, self.duration)) def _active_frames(self, renderer_state): frames = [] for f in renderer_state.get_frame_offsets(self.name): frames.append(f % self.duration) return frames def active_frames(self, action, renderer_state, indices): frames = self._active_frames(renderer_state) if action == Action.RenderAll: frames = self.all_frames() elif action in [Action.PreviewIndices, Action.RenderIndices]: frames = indices elif action in [Action.RenderWorkarea]: if self.timeline: try: frames = self.workarea() except: frames = self.all_frames() #if hasattr(self.timeline, "find_workarea"): # frames = self.timeline.find_workarea() return frames def workarea(self): return list(self.timeline.workareas[0]) def jump(self, current, direction): c = current % self.duration js = self.timeline.jumps() if direction < 0: for j in reversed(js): if c > j: return j else: for j in js: if c < j: return j return current def pass_suffix(self, index): return "{:04d}".format(index) def passes(self, action, renderer_state, indices=[]): frames = self.active_frames(action, renderer_state, indices) return [ RenderPass(self, self.pass_suffix(i), [Frame(i, self)]) for i in frames ] def runpost(self, result, render_pass, renderer_state): res = super().runpost(result, render_pass, renderer_state) if Overlay.Info in renderer_state.overlays: t = self.rect.take(50, "mxy") frame: Frame = render_pass.args[0] wave = DATPen() if self.audio and sf: wave = (self.wavfile.frame_waveform( frame.i, self.rect.inset(0, 300), 20).translate(0, self.rect.h / 2).s(1).sw(5)) return DATPens([ wave, res, DATPen().rect(t).f(bw(0, 0.75)), DATText(f"{frame.i} / {self.duration}", Style("Times", 42, load_font=0, fill=bw(1)), t.inset(10)) ]) return res def package(self, filepath, output_folder): pass def contactsheet(self, gx, sl=slice(0, None, None)): try: sliced = True start, stop, step = sl.indices(self.duration) duration = (stop - start) // step except AttributeError: # indices storyboard duration = len(sl) sliced = False ar = self.rect gy = math.ceil(duration / gx) @renderable(rect=(ar.w * gx, ar.h * gy), bg=self.bg, name=self.name + "_contactsheet") def contactsheet(r: Rect): _pngs = list(sorted(self.output_folder.glob("*.png"))) if sliced: pngs = _pngs[sl] else: pngs = [p for i, p in enumerate(_pngs) if i in sl] dps = DATPens() dps += DATPen().rect(r).f(self.bg) for idx, g in enumerate(r.grid(columns=gx, rows=gy)): if idx < len(pngs): dps += DATPen().rect(g).f(None).img(pngs[idx], g, pattern=False) return dps return contactsheet