refactor: cli + rewritten by claude

main.feat.python_and_ts_converters
Zeilenschubser 2 weeks ago
parent 1571d11322
commit bbd958baca
  1. 254
      anim.py

@ -1,93 +1,225 @@
#!/usr/bin/env python3
"""
Convert BMP images to binary animation format.
written by flop, refactored by claude sonnet 4.5
2025-12-28
"""
import argparse
from PIL import Image from PIL import Image
STATIC = 1 STATIC = 1
ANIMATION = 2 ANIMATION = 2
startx, starty = 16,25
image_on = Image.open("file_on_m.bmp")
image_off = Image.open("file_off_m.bmp")
# im = image.convert(mode='')
# im_pixels = im.load()
# # access pixels via [x, y]
# for col in range(im.size[0]):
# for row in range(im.size[1]):
# brightness = sum(im_pixels[col, row])
# if brightness < 255*2:
# im_pixels[col, row] = (0, 0, 0)
# else:
# im_pixels[col, row] = (255,255,255)
# im.save('file_on_m.bmp')
def bmp_to_bitmap_fn(im): def bmp_to_bitmap_fn(im):
"""Create a function that converts image to bitmap data."""
def fn(img): def fn(img):
x = [xc if sum(im.getpixel((xc,yc))) == 0 else None for xc in range(im.size[0]) for yc in range(im.size[1])] # Find bounding box of black pixels
y = [yc if sum(im.getpixel((xc,yc))) == 0 else None for xc in range(im.size[0]) for yc in range(im.size[1])] x = [xc for xc in range(im.size[0]) for yc in range(im.size[1])
x = list(filter(lambda x: x!=None, x)) if sum(im.getpixel((xc, yc))) == 0]
y = list(filter(lambda y: y!=None, y)) y = [yc for xc in range(im.size[0]) for yc in range(im.size[1])
if sum(im.getpixel((xc, yc))) == 0]
if not x or not y:
return img, 0, 0
xmin, xmax = min(x), max(x) xmin, xmax = min(x), max(x)
ymin, ymax = min(y), max(y) ymin, ymax = min(y), max(y)
# print(xmin, xmax, ymin, ymax)
xsize = (xmax - xmin) xsize = (xmax - xmin)
ysize = (ymax - ymin) ysize = (ymax - ymin)
print(len(img))
print(f"Image bounds: x={xmin}-{xmax}, y={ymin}-{ymax}, size={xsize}x{ysize}")
for y in range(ysize): for y in range(ysize):
for x in range(0,xsize): for x in range(xsize):
# print(xmin+x+bit, ymin+y)
if im.size[0] >= x and sum(im.getpixel((xmin + x, ymin + y))) == 0: if im.size[0] >= x and sum(im.getpixel((xmin + x, ymin + y))) == 0:
# print( im.size[0], x,x+bit, (xmin+x+bit,ymin+y))
pos = ((y * xsize) + x) // 8 pos = ((y * xsize) + x) // 8
bit = ((y * xsize) + x) % 8 bit = ((y * xsize) + x) % 8
print(pos, 1<<bit)
if pos < len(img): if pos < len(img):
img[pos] |= 1 << bit img[pos] |= 1 << bit
return img, xsize, ysize return img, xsize, ysize
return fn return fn
def get_image_dimensions(image_paths):
"""Get the maximum dimensions needed for all images."""
max_width = 0
max_height = 0
for path in image_paths:
img = Image.open(path)
# Find bounding box
x = [xc for xc in range(img.size[0]) for yc in range(img.size[1])
if sum(img.getpixel((xc, yc))) == 0]
y = [yc for xc in range(img.size[0]) for yc in range(img.size[1])
if sum(img.getpixel((xc, yc))) == 0]
if x and y:
width = max(x) - min(x)
height = max(y) - min(y)
max_width = max(max_width, width)
max_height = max(max_height, height)
return max_width, max_height
def create_animation_binary(image_paths, output_path, startx, starty,
update_interval, mode):
"""
Create binary animation file from BMP images.
Args:
image_paths: List of image file paths
output_path: Output binary file path
startx: X coordinate for animation start
starty: Y coordinate for animation start
update_interval: Frame update interval
mode: 'frame' for one image per frame, 'differential' for alternating states
"""
# Determine dimensions
frame_width, frame_height = get_image_dimensions(image_paths)
print(f"Using frame dimensions: {frame_width}x{frame_height}")
# Build animation based on mode
if mode == 'differential':
# 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])
image_on = Image.open(image_paths[1])
PNG_file_OFF = bmp_to_bitmap_fn(image_off) PNG_file_OFF = bmp_to_bitmap_fn(image_off)
PNG_file_ON = bmp_to_bitmap_fn(image_on) 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':
# Frame mode: each image is a separate frame
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
if len(animation) == 1:
print("Single frame detected, creating static object")
with open(output_path, "wb") as fx:
# Write magic header
fx.write(bytearray([0x42, 0x4e, 0x17, 0xee]))
with open("animation.bin", "wb") as fx: # Write number of objects
# fx.write(bytearray([1]))
fx.write(bytearray([0x42,0x4e,0x17,0xee])) #magic header
fx.write(bytearray([2])) # objecets
# Write static object
fx.write(bytearray([STATIC, startx, starty]))
fx.write(bytearray([frame_width, frame_height]))
img = bytearray([0] * ((frame_width * frame_height + 7) // 8))
img, xsize, ysize = animation[0](img)
fx.write(img)
file_size = fx.tell()
print(f"Total size: {file_size} bytes")
if file_size >= 50000:
print("WARNING: File size exceeds 50000 bytes!")
print(f"Static image created: {output_path}")
return
# Multiple frames: write as animation
with open(output_path, "wb") as fx:
# Write magic header
fx.write(bytearray([0x42, 0x4e, 0x17, 0xee]))
# Write number of objects
fx.write(bytearray([2]))
# Write static background object (empty)
fx.write(bytearray([STATIC, 0, 0])) fx.write(bytearray([STATIC, 0, 0]))
fx.write(bytearray([120, 60])) fx.write(bytearray([120, 60]))
fx.write(bytearray([0] * ((120 * 60 + 7) // 8))) fx.write(bytearray([0] * ((120 * 60 + 7) // 8)))
# Write animation object
fx.write(bytearray([ANIMATION, startx, starty]))
fx.write(bytearray([frame_width, frame_height, len(animation), update_interval]))
fx.write(bytearray([ANIMATION, startx, starty])) # we start at 0,0 for now # Write animation frames
# print(PNG_file_OFF([0]*120*60)[1],PNG_file_OFF([0]*120*60)[2]) for i, bmp_fun in enumerate(animation):
# print(PNG_file_ON([0]*120*60)[1],PNG_file_ON([0]*120*60)[2]) img = bytearray([0] * ((frame_width * frame_height + 7) // 8))
ww, hh = 100, 10
uu = 3# update interval, 0 every tick, 1 every second tick
animation = [
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_OFF,
PNG_file_ON,
]
fx.write(bytearray([ww,hh,len(animation),uu]))
for bmp_fun in animation:
img = bytearray([0]*((ww*hh+7)//8))
img, xsize, ysize = bmp_fun(img) img, xsize, ysize = bmp_fun(img)
print(f"Frame {i}: start={fx.tell()}")
print("framestart=",fx.tell())
fx.write(img) fx.write(img)
print("frameend=",fx.tell())
print("size:", fx.tell()) file_size = fx.tell()
assert fx.tell() < 50000, "do not use too much image space" print(f"Total size: {file_size} bytes")
if file_size >= 50000:
print("WARNING: File size exceeds 50000 bytes!")
print(f"Animation binary created: {output_path}")
def main():
parser = argparse.ArgumentParser(
description='Convert BMP images to binary animation format',
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
'images',
nargs='+',
help='BMP image file(s). Single image creates static object, multiple create animation.'
)
parser.add_argument(
'--output',
type=str,
default='animation.bin',
help='Output binary file path'
)
parser.add_argument(
'--start-x',
type=int,
default=0,
help='X coordinate for start position'
)
parser.add_argument(
'--start-y',
type=int,
default=0,
help='Y coordinate for start position'
)
parser.add_argument(
'--update-interval',
type=int,
default=3,
help='Update interval (0=every tick, 1=every second tick, etc.)'
)
parser.add_argument(
'--mode',
choices=['frame', 'differential'],
default='frame',
help='Animation mode: "frame" = each image is a frame, "differential" = alternate between 2 images (requires exactly 2 images)'
)
args = parser.parse_args()
create_animation_binary(
args.images,
args.output,
args.start_x,
args.start_y,
args.update_interval,
args.mode
)
if __name__ == '__main__':
main()
Loading…
Cancel
Save