def get_palette_loop_timer(self, animation, direction, palettes): pose_list = self.get_pose_list(animation, direction) returnvalue = 1 # default for pose_number in range(len(pose_list)): tile_list = pose_list[pose_number]["tiles"][::-1] tile_list += self.get_supplemental_tiles(animation, direction, pose_number, palettes, 0) for tile_info in tile_list: new_palette = None for possible_palette_info_location in [ pose_list[pose_number], tile_info ]: if "palette" in possible_palette_info_location: palette_info_location = possible_palette_info_location new_palette = palette_info_location["palette"] if new_palette: palettes.append(new_palette) this_palette_duration = max( 1, self.get_palette_duration(palettes)) returnvalue = common.lcm(returnvalue, this_palette_duration) return returnvalue
def export_animation_as_gif(self, filename, zoom=1, speed=1): #TODO: factor out common code with the collage function GIF_MAX_FRAMERATE = 100.0 #GIF format in theory supports 100 FPS, but some programs display at 50 FPS ACTUAL_FRAMERATE = 60.0 image_list = [] current_animation, displayed_direction, _, palette_info, _, pose_list = self.get_image_arguments_from_frame_number( self.frame_getter()) if current_animation: for pose_number in range(len(pose_list)): image_list.append( self.sprite.get_image(current_animation, displayed_direction, pose_number, palette_info, 0)) #TODO: Factor this and the corresponding code in layoutlib.py out to common.py x_min = min([origin[0] for image, origin in image_list]) x_max = max( [image.size[0] + origin[0] for image, origin in image_list]) y_min = min([origin[1] for image, origin in image_list]) y_max = max( [image.size[1] + origin[1] for image, origin in image_list]) gif_x_size = x_max - x_min gif_y_size = y_max - y_min palette_duration = self.sprite.get_palette_loop_timer( current_animation, displayed_direction, palette_info) animation_duration = sum([pose["frames"] for pose in pose_list]) full_animation_duration = common.lcm(palette_duration, animation_duration) frames = [] durations = [] for frame_number in range(full_animation_duration): _, _, _, palette_info, _, _ = self.get_image_arguments_from_frame_number( frame_number) pose_number = self.get_pose_number_from_frames(frame_number) image, origin = self.sprite.get_image(current_animation, displayed_direction, pose_number, palette_info, frame_number) this_frame = Image.new("RGBA", (gif_x_size, gif_y_size)) this_frame.paste(image, (origin[0] - x_min, origin[1] - y_min), image) #PIL makes transparency so difficult... alpha = this_frame.split()[3] #reserve color number 255 this_frame = this_frame.convert('P', palette=Image.ADAPTIVE, colors=255) mask = Image.eval(alpha, lambda a: 255 if a == 0 else 0) #apply color number 255 this_frame.paste(255, mask) new_size = tuple(int(dim * zoom) for dim in this_frame.size) this_frame = this_frame.resize(new_size, resample=Image.NEAREST) if frames and common.equal(this_frame, frames[-1]): durations[-1] += 1 else: frames.append(this_frame) durations.append(1) gif_durations = [ 1000.0 * #millisecond conversion max(1.0 / GIF_MAX_FRAMERATE, round(duration / (speed * ACTUAL_FRAMERATE), 2)) for duration in durations ] if len(frames) > 1: frames[0].save(filename, format='GIF', append_images=frames[1:], save_all=True, transparency=255, disposal=2, duration=gif_durations, loop=0, optimize=False) return True elif len(frames) == 1: frames[0].save(filename) else: return False else: return False