Skip to content

Instantly share code, notes, and snippets.

@cmerrill
Created August 3, 2012 23:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cmerrill/3252778 to your computer and use it in GitHub Desktop.
Save cmerrill/3252778 to your computer and use it in GitHub Desktop.
Win8 spinner
### Made this a repo. Not sure why I didn't before.
Link: https://github.com/cmerrill/win8-spinner
This needs PIL installed.
Run 8spinner.py to generate a file spinner.gif that animates. All of the settings are up top.
Preview of this version:
https://www.dropbox.com/s/tbsk6cdcib50q0c/spinner.gif
Sadly, dropbox makes it not animate, so download it and take a look...
from PIL import Image, ImageDraw, ImageSequence
import images2gif as i2g
import math
FILENAME = "spinner.gif"
frames = 32 # no of frames
total_duration = 1200 # no of miliseconds
radius = 30 # outer circle radius
no_dots = 8 # must divide into number of frames evenly or weird shit
no_empty = 4 # no of empty positions
dot_radius = 2 # small
padding = 10
images = []
angles = [math.pi*(1+math.cos(math.pi*float(x)/frames))-math.pi/2 for x in range(frames)]
size = (radius*2+dot_radius*2+padding*2, radius*2+dot_radius*2+padding*2)
x = [-radius*math.cos(t)+size[0]/2 for t in angles]
y = [radius*math.sin(t)+size[1]/2 for t in angles]
indices = []
for j in xrange(no_dots):
indices.append(int((math.cos(float(j)/no_dots*math.pi) + 1) / 2 * frames)-1)
print indices
for i in xrange(frames):
img = Image.new("L",size,255)
draw = ImageDraw.Draw(img);
for n in xrange(len(indices)):
if n+no_empty+1 == no_dots:
break
j = indices[n]
bbox = (x[j] - dot_radius, y[j] - dot_radius,
x[j] + dot_radius, y[j] + dot_radius)
draw.ellipse(bbox, fill=0)
images.append(img)
indices = [(z + 1) % frames for z in indices]
i2g.writeGif(FILENAME, images, duration=total_duration/frames/1000.0)
""" MODULE images2gif
Provides a function (writeGif) to write animated gif from a series
of PIL images or numpy arrays.
This code is provided as is, and is free to use for all.
Almar Klein (June 2009)
- based on gifmaker (in the scripts folder of the source distribution of PIL)
- based on gif file structure as provided by wikipedia
"""
try:
import PIL
from PIL import Image, ImageChops
from PIL.GifImagePlugin import getheader, getdata
except ImportError:
PIL = None
try:
import numpy as np
except ImportError:
np = None
# getheader gives a 87a header and a color palette (two elements in a list).
# getdata()[0] gives the Image Descriptor up to (including) "LZW min code size".
# getdatas()[1:] is the image data itself in chuncks of 256 bytes (well
# technically the first byte says how many bytes follow, after which that
# amount (max 255) follows).
def intToBin(i):
""" Integer to two bytes """
# devide in two parts (bytes)
i1 = i % 256
i2 = int( i/256)
# make string (little endian)
return chr(i1) + chr(i2)
def getheaderAnim(im):
""" Animation header. To replace the getheader()[0] """
bb = "GIF89a"
bb += intToBin(im.size[0])
bb += intToBin(im.size[1])
bb += "\x87\x00\x00"
return bb
def getAppExt(loops=0):
""" Application extention. Part that secifies amount of loops.
if loops is 0, if goes on infinitely.
"""
bb = "\x21\xFF\x0B" # application extension
bb += "NETSCAPE2.0"
bb += "\x03\x01"
if loops == 0:
loops = 2**16-1
bb += intToBin(loops)
bb += '\x00' # end
return bb
def getGraphicsControlExt(duration=0.1):
""" Graphics Control Extension. A sort of header at the start of
each image. Specifies transparancy and duration. """
bb = '\x21\xF9\x04'
bb += '\x08' # no transparancy
bb += intToBin( int(duration*100) ) # in 100th of seconds
bb += '\x00' # no transparant color
bb += '\x00' # end
return bb
def _writeGifToFile(fp, images, durations, loops):
""" Given a set of images writes the bytes to the specified stream.
"""
# init
frames = 0
previous = None
for im in images:
if not previous:
# first image
# gather data
palette = getheader(im)[1]
data = getdata(im)
imdes, data = data[0], data[1:]
header = getheaderAnim(im)
appext = getAppExt(loops)
graphext = getGraphicsControlExt(durations[0])
# write global header
fp.write(header)
fp.write(palette)
fp.write(appext)
# write image
fp.write(graphext)
fp.write(imdes)
for d in data:
fp.write(d)
else:
# gather info (compress difference)
data = getdata(im)
imdes, data = data[0], data[1:]
graphext = getGraphicsControlExt(durations[frames])
# write image
fp.write(graphext)
fp.write(imdes)
for d in data:
fp.write(d)
# # delta frame - does not seem to work
# delta = ImageChops.subtract_modulo(im, previous)
# bbox = delta.getbbox()
#
# if bbox:
#
# # gather info (compress difference)
# data = getdata(im.crop(bbox), offset = bbox[:2])
# imdes, data = data[0], data[1:]
# graphext = getGraphicsControlExt(durations[frames])
#
# # write image
# fp.write(graphext)
# fp.write(imdes)
# for d in data:
# fp.write(d)
#
# else:
# # FIXME: what should we do in this case?
# pass
# prepare for next round
previous = im.copy()
frames = frames + 1
fp.write(";") # end gif
return frames
def writeGif(filename, images, duration=0.1, loops=0, dither=1):
""" writeGif(filename, images, duration=0.1, loops=0, dither=1)
Write an animated gif from the specified images.
images should be a list of numpy arrays of PIL images.
Numpy images of type float should have pixels between 0 and 1.
Numpy images of other types are expected to have values between 0 and 255.
"""
if PIL is None:
raise RuntimeError("Need PIL to write animated gif files.")
images2 = []
# convert to PIL
for im in images:
if isinstance(im,Image.Image):
images2.append( im.convert('P',dither=dither) )
elif np and isinstance(im, np.ndarray):
if im.dtype == np.uint8:
pass
elif im.dtype in [np.float32, np.float64]:
im = (im*255).astype(np.uint8)
else:
im = im.astype(np.uint8)
# convert
if len(im.shape)==3 and im.shape[2]==3:
im = Image.fromarray(im,'RGB').convert('P',dither=dither)
elif len(im.shape)==2:
im = Image.fromarray(im,'L').convert('P',dither=dither)
else:
raise ValueError("Array has invalid shape to be an image.")
images2.append(im)
else:
raise ValueError("Unknown image type.")
# check duration
if hasattr(duration, '__len__'):
if len(duration) == len(images2):
durations = [d for d in duration]
else:
raise ValueError("len(duration) doesn't match amount of images.")
else:
durations = [duration for im in images2]
# open file
fp = open(filename, 'wb')
# write
try:
n = _writeGifToFile(fp, images2, durations, loops)
print n, 'frames written'
finally:
fp.close()
if __name__ == '__main__':
im = np.zeros((200,200), dtype=np.uint8)
im[10:30,:] = 100
im[:,80:120] = 255
im[-50:-40,:] = 50
images = [im*1.0, im*0.8, im*0.6, im*0.4, im*0]
writeGif('lala3.gif',images, duration=0.5, dither=0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment