#!/usr/bin/python3 # # sspreview # Copyright (C) 2024 Juan J. Martinez # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from argparse import ArgumentParser from tkinter import Tk, N, W, E, S, StringVar from tkinter import ttk from PIL import ImageTk, Image __version__ = "0.2" DEF_W = 16 DEF_H = 16 DEF_F = 0 DEF_D = 600 DEF_S = 10 class Preview(object): def __init__(self, spritesheet, frame_w, frame_h, error_fn): self.spritesheet = spritesheet self.error = error_fn self.frames = [] self.current = 0 self.next_step = None self.delay_v = DEF_D self.root = Tk() self.root.title("SpriteSheet Animation Preview") self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) mainframe = ttk.Frame(self.root, padding="10 10 10 10") mainframe.grid(column=0, row=0, sticky=(N, W, E, S)) self.img = ttk.Label(mainframe, text="Preview", padding="0 10 10 10") self.img.grid(column=0, row=0, columnspan=2, sticky=N, padx=10) self.frame_w = StringVar() self.frame_w.set(frame_w) self.frame_h = StringVar() self.frame_h.set(frame_h) self.frame_list = StringVar() self.frame_list.set(DEF_F) self.delay = StringVar() self.delay.set(DEF_D) self.scale = StringVar() self.scale.set(DEF_S) ttk.Label(mainframe, text="Width (px)").grid(column=0, row=3, sticky=E, padx=10) entry_w = ttk.Entry(mainframe, width=8, textvariable=self.frame_w) entry_w.grid(column=1, row=3, sticky=(W, E)) ttk.Label(mainframe, text="Height (px)").grid( column=0, row=4, sticky=E, padx=10 ) entry_h = ttk.Entry(mainframe, width=8, textvariable=self.frame_h) entry_h.grid(column=1, row=4, sticky=(W, E)) ttk.Label(mainframe, text="Frames").grid(column=0, row=5, sticky=E, padx=10) entry_f = ttk.Entry(mainframe, width=16, textvariable=self.frame_list) entry_f.grid(column=1, row=5, sticky=(W, E)) ttk.Label(mainframe, text="Delay (ms)").grid(column=0, row=6, sticky=E, padx=10) entry_d = ttk.Entry(mainframe, width=16, textvariable=self.delay) entry_d.grid(column=1, row=6, sticky=(W, E)) ttk.Label(mainframe, text="Scale").grid(column=0, row=7, sticky=E, padx=10) entry_s = ttk.Entry(mainframe, width=16, textvariable=self.scale) entry_s.grid(column=1, row=7, sticky=(W, E)) bf = ttk.Frame(mainframe, padding="0 10 0 10") bf.grid(column=1, row=8, sticky=(N, W, E, S)) ttk.Button(bf, text="Update", command=self.update).grid( column=0, row=0, sticky=W ) ttk.Button(bf, text="Quit", command=lambda *x: self.root.quit()).grid( column=1, row=0, sticky=W ) self.update() def step(self, next): if not self.frames: self.next_step = None return self.current = next if self.current == len(self.frames): self.current = 0 self.img.image = ImageTk.PhotoImage(self.frames[self.current]) self.img["image"] = self.img.image self.next_step = self.root.after( self.delay_v, lambda: self.step(self.current + 1) ) def update(self, *args): self.frames = [] self.current = 0 if self.next_step is not None: self.root.after_cancel(self.next_step) with Image.open(self.spritesheet) as image: try: width = int(self.frame_w.get()) except Exception: self.error("Invalid width") try: height = int(self.frame_h.get()) except Exception: self.error("Invalid height") try: scale = self.scale.get() if "," in scale: scale = scale.split(",") if len(scale) != 2: self.error("Invalid x,y scale factor") else: scale = (scale, scale) scale = [int(x) for x in scale] except Exception: self.error("Invalid scale") try: frame_list = [int(f) for f in self.frame_list.get().split()] except Exception: self.error("Invalid frame") try: self.delay_v = int(self.delay.get()) except Exception: self.error("Invalid delay") (w, h) = image.size if w % width or h % height: self.error( f"{self.spritesheet} size is not multiple of frame size ({width}, {height})" ) frames = [] for y in range(0, h, height): for x in range(0, w, width): frames.append( image.crop((x, y, x + width, y + height)).resize( (width * scale[0], height * scale[1]), None ) ) for f in frame_list: self.frames.append(frames[f]) self.step(0) def run(self): self.root.mainloop() if __name__ == "__main__": parser = ArgumentParser(description="SpriteSheet Animation Preview") parser.add_argument( "--version", action="version", version="%(prog)s " + __version__ ) parser.add_argument("--width", dest="w", default=DEF_W, type=int, help="frame width (default: %s)" % DEF_W) parser.add_argument("--height", dest="h", default=DEF_H, type=int, help="frame height (default: %s)" % DEF_H) parser.add_argument("image", help="spritesheet to ipreview") args = parser.parse_args() Preview(args.image, args.w, args.h, parser.error).run()