refactor: gif handling

main.feat.python_and_ts_converters
Zeilenschubser 2 weeks ago
parent bbd958baca
commit 8f72f59558
  1. 142
      anim.py

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Convert BMP images to binary animation format. Convert BMP/GIF images to binary animation format.
written by flop, refactored by claude sonnet 4.5 written by me, refactored by claude sonnet 4.5
2025-12-28 2025-12-28
""" """
import argparse import argparse
from PIL import Image from PIL import Image, ImageSequence
STATIC = 1 STATIC = 1
ANIMATION = 2 ANIMATION = 2
@ -41,18 +41,97 @@ def bmp_to_bitmap_fn(im):
return img, xsize, ysize return img, xsize, ysize
return fn return fn
def get_image_dimensions(image_paths): def extract_frames_from_image(image_path):
"""Get the maximum dimensions needed for all images.""" """
Extract all frames from an image file (GIF or static).
Returns a list of PIL Image objects.
"""
img = Image.open(image_path)
frames = []
# Convert to RGB if needed
if img.mode not in ('RGB', 'L'):
img = img.convert('RGB')
if image_path.endswith(".gif"):
with Image.open(image_path) as im_gif:
index = 0
for img in ImageSequence.Iterator(im_gif):
frame_copy = img.copy()
if frame_copy.mode != 'RGB':
frame_copy = frame_copy.convert('RGB')
frames.append(frame_copy)
if len(frames) == 0:
try:
# Try to extract all frames (works for GIFs)
for frame_num in range(img.n_frames):
print("frame is ", frame_num)
img.seek(frame_num)
# Copy the frame to prevent issues with lazy loading
frame_copy = img.copy()
if frame_copy.mode != 'RGB':
frame_copy = frame_copy.convert('RGB')
frames.append(frame_copy)
print(f"Extracted frame {frame_num} from {image_path}")
except AttributeError:
# Not an animated image, just use the single frame
frames.append(img)
print(f"Loaded static image from {image_path}")
return frames
def extract_all_frames(image_paths, mode):
"""
Extract frames from all input images based on mode.
Args:
image_paths: List of image file paths
mode: 'frame' for sequential frames, 'differential' for alternating pattern
Returns:
List of PIL Image objects representing animation frames
"""
all_frames = []
if mode == 'differential':
# Differential mode: extract frames from each image, then create alternating pattern
if len(image_paths) != 2:
raise ValueError("Differential mode requires exactly 2 images (off and on states)")
off_frames = extract_frames_from_image(image_paths[0])
on_frames = extract_frames_from_image(image_paths[1])
# Use only the first frame from each for differential mode
off_frame = off_frames[0]
on_frame = on_frames[0]
# Create pattern: multiple off frames followed by on frame
all_frames = [off_frame] * 14 + [on_frame]
print(f"Created differential animation: 14 off frames + 1 on frame")
elif mode == 'frame':
# Frame mode: concatenate all frames from all images
for path in image_paths:
frames = extract_frames_from_image(path)
all_frames.extend(frames)
print(f"Total frames collected: {len(all_frames)}")
else:
raise ValueError(f"Unknown mode: {mode}. Use 'frame' or 'differential'")
return all_frames
def get_frame_dimensions(frames):
"""Get the maximum dimensions needed for all frames."""
max_width = 0 max_width = 0
max_height = 0 max_height = 0
for path in image_paths: for frame in frames:
img = Image.open(path)
# Find bounding box # Find bounding box
x = [xc for xc in range(img.size[0]) for yc in range(img.size[1]) x = [xc for xc in range(frame.size[0]) for yc in range(frame.size[1])
if sum(img.getpixel((xc, yc))) == 0] if sum(frame.getpixel((xc, yc))) == 0]
y = [yc for xc in range(img.size[0]) for yc in range(img.size[1]) y = [yc for xc in range(frame.size[0]) for yc in range(frame.size[1])
if sum(img.getpixel((xc, yc))) == 0] if sum(frame.getpixel((xc, yc))) == 0]
if x and y: if x and y:
width = max(x) - min(x) width = max(x) - min(x)
@ -65,7 +144,7 @@ def get_image_dimensions(image_paths):
def create_animation_binary(image_paths, output_path, startx, starty, def create_animation_binary(image_paths, output_path, startx, starty,
update_interval, mode): update_interval, mode):
""" """
Create binary animation file from BMP images. Create binary animation file from BMP/GIF images.
Args: Args:
image_paths: List of image file paths image_paths: List of image file paths
@ -76,33 +155,18 @@ def create_animation_binary(image_paths, output_path, startx, starty,
mode: 'frame' for one image per frame, 'differential' for alternating states mode: 'frame' for one image per frame, 'differential' for alternating states
""" """
# Determine dimensions # Extract all frames from input images based on mode
frame_width, frame_height = get_image_dimensions(image_paths) frames = extract_all_frames(image_paths, mode)
print(f"Using frame dimensions: {frame_width}x{frame_height}")
# Build animation based on mode if not frames:
if mode == 'differential': raise ValueError("No frames extracted from input images")
# Differential mode: alternate between images
if len(image_paths) != 2:
raise ValueError("Differential mode requires exactly 2 images (off and on states)")
image_off = Image.open(image_paths[0]) # Determine dimensions
image_on = Image.open(image_paths[1]) frame_width, frame_height = get_frame_dimensions(frames)
print(f"Using frame dimensions: {frame_width}x{frame_height}")
PNG_file_OFF = bmp_to_bitmap_fn(image_off)
PNG_file_ON = bmp_to_bitmap_fn(image_on)
# Create pattern: multiple off frames followed by on frame
animation = [PNG_file_OFF] * 14 + [PNG_file_ON]
elif mode == 'frame': # Convert frames to bitmap functions
# Frame mode: each image is a separate frame animation = [bmp_to_bitmap_fn(frame) for frame in frames]
animation = []
for path in image_paths:
img = Image.open(path)
animation.append(bmp_to_bitmap_fn(img))
else:
raise ValueError(f"Unknown mode: {mode}. Use 'frame' or 'differential'")
# Single frame case: write as static object # Single frame case: write as static object
if len(animation) == 1: if len(animation) == 1:
@ -165,14 +229,14 @@ def create_animation_binary(image_paths, output_path, startx, starty,
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Convert BMP images to binary animation format', description='Convert BMP/GIF images to binary animation format',
formatter_class=argparse.ArgumentDefaultsHelpFormatter formatter_class=argparse.ArgumentDefaultsHelpFormatter
) )
parser.add_argument( parser.add_argument(
'images', 'images',
nargs='+', nargs='+',
help='BMP image file(s). Single image creates static object, multiple create animation.' help='Image file(s) (BMP or GIF). GIF frames are extracted automatically. Single frame creates static object, multiple frames create animation.'
) )
parser.add_argument( parser.add_argument(
@ -207,7 +271,7 @@ def main():
'--mode', '--mode',
choices=['frame', 'differential'], choices=['frame', 'differential'],
default='frame', default='frame',
help='Animation mode: "frame" = each image is a frame, "differential" = alternate between 2 images (requires exactly 2 images)' help='Animation mode: "frame" = each image/frame is a frame, "differential" = alternate between 2 images (requires exactly 2 images)'
) )
args = parser.parse_args() args = parser.parse_args()

Loading…
Cancel
Save