diff --git a/bemani/format/afp/render.py b/bemani/format/afp/render.py index 85a8c9c..b552ea4 100644 --- a/bemani/format/afp/render.py +++ b/bemani/format/afp/render.py @@ -253,10 +253,10 @@ class AFPRenderer(VerboseOutput): else: raise Exception(f"Failed to process tag: {tag}") - def __render_object(self, img: Image.Image, tag: AP2PlaceObjectTag, parent_transform: Matrix, parent_origin: Point) -> Image.Image: + def __render_object(self, img: Image.Image, tag: AP2PlaceObjectTag, parent_transform: Matrix, parent_origin: Point) -> None: if tag.source_tag_id is None: self.vprint(" Nothing to render!") - return img + return # Look up the affine transformation matrix and rotation/origin. transform = parent_transform.multiply(tag.transform or Matrix.identity()) @@ -272,13 +272,13 @@ class AFPRenderer(VerboseOutput): for obj in self.__placed_objects: if obj.parent_clip == tag.source_tag_id: self.vprint(f" Rendering placed object ID {obj.object_id} from sprite {obj.parent_clip} onto Depth {obj.depth}") - img = self.__render_object(img, obj.tag, transform, origin) + self.__render_object(img, obj.tag, transform, origin) found_one = True if not found_one: raise Exception(f"Couldn't find parent clip {obj.parent_clip} to render animation out of!") - return img + return # This is a shape draw reference. shape = self.__registered_shapes[tag.source_tag_id] @@ -291,7 +291,7 @@ class AFPRenderer(VerboseOutput): for params in shape.draw_params: if not (params.flags & 0x1): # Not instantiable, don't render. - return img + return if params.flags & 0x8: # TODO: Need to support blending and UV coordinate colors here. @@ -365,11 +365,8 @@ class AFPRenderer(VerboseOutput): texoff = texx + (texy * texture.width) imgmap[imgoff] = self.__blend(imgmap[imgoff], texmap[texoff], mult_color, add_color) - img = Image.new("RGBA", (img.width, img.height)) img.putdata(imgmap) - return img - def __blend( self, bg: Tuple[int, int, int, int], @@ -459,7 +456,7 @@ class AFPRenderer(VerboseOutput): continue self.vprint(f" Rendering placed object ID {obj.object_id} from sprite {obj.parent_clip} onto Depth {obj.depth}") - curimage = self.__render_object(curimage, obj.tag, Matrix.identity(), Point.identity()) + self.__render_object(curimage, obj.tag, Matrix.identity(), Point.identity()) else: # Nothing changed, make a copy of the previous render. self.vprint(" Using previous frame render") diff --git a/bemani/utils/afputils.py b/bemani/utils/afputils.py index 6f50ff4..d86dc5f 100644 --- a/bemani/utils/afputils.py +++ b/bemani/utils/afputils.py @@ -159,7 +159,7 @@ def main() -> int: metavar="IMAGE", type=str, default="out.gif", - help='The output file (ending either in .gif or .webp) where the render should be saved.', + help='The output file (ending either in .gif, .webp or .png) where the render should be saved.', ) render_parser.add_argument( "-v", @@ -558,11 +558,40 @@ def main() -> int: continue if args.action == "render": + # Render the gif/webp frames. duration, images = renderer.render_path(args.path, verbose=args.verbose) if len(images) == 0: raise Exception("Did not render any frames!") - images[0].save(args.output, save_all=True, append_images=images[1:], loop=0, duration=duration) - print(f"Wrote animation to {args.output}") + + # Write them out to a new file. + if args.output.lower().endswith(".gif"): + fmt = "GIF" + elif args.output.lower().endswith(".webp"): + fmt = "WEBP" + elif args.output.lower().endswith(".png"): + fmt = "PNG" + else: + raise Exception("Unrecognized file extension for output!") + + if fmt in ["GIF", "WEBP"]: + # Write all the frames out in one file. + with open(args.output, "wb") as bfp: + images[0].save(bfp, format=fmt, save_all=True, append_images=images[1:], duration=[duration] * len(images)) + + print(f"Wrote animation to {args.output}") + else: + # Write all the frames out in individual_files. + filename = args.output[:-4] + ext = args.output[-4:] + + for i, img in enumerate(images): + fullname = f"{filename}-{i}{ext}" + + with open(fullname, "wb") as bfp: + img.save(bfp, format=fmt) + + print(f"Wrote animation frame to {fullname}") + elif args.action == "list": paths = renderer.list_paths(verbose=args.verbose) for path in paths: