Skip to content

Instantly share code, notes, and snippets.

@digarok
Last active July 31, 2022 16:11
Show Gist options
  • Save digarok/01a1711b4fe4a7290c7fade26f1f744c to your computer and use it in GitHub Desktop.
Save digarok/01a1711b4fe4a7290c7fade26f1f744c to your computer and use it in GitHub Desktop.
Convert PaintWorks Animation (Apple IIgs $C2 filetype) to Animated GIF
# Example: python c2-to-animgif.py anim.c2 outputname.gif
from PIL import Image,ImageDraw # `pip3 install pillow==7.0.0` if you don't have it
from sys import argv, stdout
import glob, tempfile, struct, os
def get_scanline_palette(scanline):
scb = shr_buffer[0x7D00+scanline] # SCBs start at $E19D00 (SHR RAM starts offset +$2000)
cur_pal_num = scb & 0x0F
pal_offset = 0x7E00+(cur_pal_num*16*2) # each pal is 16 colors * 2 bytes per color
# now make a dict of each index => (r,g,b) tuple
result = {}
for i in range(0,16):
g = shr_buffer[pal_offset+(i*2)] >> 4 & 0x0F
b = shr_buffer[pal_offset+(i*2)] & 0x0F
r = shr_buffer[pal_offset+(i*2)+1]
# expand to 24-bit color colorspace for png/rgb
r += (r*16)
g += (g*16)
b += (b*16)
result[i] = (r,g,b)
return result
def return_frame():
# Create new indexed (P) image
img = Image.new('P', (320, 200), color = 'red')
d = ImageDraw.Draw(img)
# foreach scanline
for y in range(0,200):
# get scanline palette (as dict k=>(r,g,b))
curPal = get_scanline_palette(y)
# foreach byte (2 pixels)
for x in range(0,160): # 320/2=160 because 2 pixels per byte = 160 bytes per scanline row
data = shr_buffer[(y*160)+x]
px1 = data >> 4
px2 = data & 0x0F
d.point((x*2,y),curPal[px1])
d.point(((x*2)+1,y),curPal[px2])
return img
def get_long():
result = get_word() + (get_word() << 16) # get two words ;)
return result
def get_word():
global decode_stream_ptr
result = c2_buffer[decode_stream_ptr] + \
(c2_buffer[decode_stream_ptr+1] << 8)
decode_stream_ptr += 2
return result
def decode_next_frame():
global shr_buffer
zero_offset = False
while not zero_offset:
offset = get_word()
if offset == 0:
zero_offset = True
get_word()
continue
value = get_word()
shr_buffer[offset] = value & 0xFF
shr_buffer[offset+1] = value >> 8
return True
# READ PAINTWORKS ANIMATION FILE
c2_buffer = bytearray(open(argv[1], "rb").read())
# INITIALIZE
tickscaler = 19 # this scales the delay in output gif; 19 seems close to Platinum Paint delay
decode_stream_ptr = 0x8000
animdata_len = get_long()
tickcount = get_word()
decode_stream_ptr = 0x800C # Skip to anim frames now
finished = False
outfile = argv[2]
shr_buffer = c2_buffer[0:0x8000] # first $8000 is just raw frame to put in SHR RAM
frames = []
while decode_stream_ptr < len(c2_buffer):
stdout.write(".")
stdout.flush()
img = return_frame()
frames.append(img)
decode_next_frame()
print("\nSaving {} frames to {}".format(len(frames), outfile))
# Save into a GIF file that loops forever
frames[0].save(outfile, dither=None, append_images=frames[1:], save_all=True, duration=tickcount*tickscaler, loop=0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment