Making GIF animations with Transitions

This article discusses making a GIF which fades between three different images of different ideal color palettes, using Numpy to determine the transitions. The code that produced this output is here.

2pics

The Python Imaging Library (PIL) in python provides for robust image manipulation including animated GIFs.

My first attempt at making a GIF was very simple. It was quick to find an example of using the GIF functionality of PIL, called images2gif. I spat out a dozen png’s of the data I wanted to animate and turned it into a GIF.

	from PIL import Image
	from images2gif import writeGif

	images = [Image.open('%s.png' % i) for i in range(12)]
	durations = ['1.5']*len(images)
	writeGif('my.gif',images, duration=durations)

Some complications

With simple images of a few colors, all using the same colors, and no transitions that caused new colors, this process went very quickly. However, to make animated GIFs where the frames of the animation have very different colors, it turns out there are a few unexpected turns.

The GIF uses an indexed color palette. Each pixel of a GIF image is an 8-bit pointer to a color in a color map. That’s 256 possible colors, or, 255 possible colors and one transparency layer. By default, PIL uses a standard web safe palette when converting images with more than this many colors to this format. This will cause reduced quality.

img = PIL.Image.open('some.png')
img2 = img.convert("P")		#Convert true color image to Palette.
img2.save('some.gif')		#web safe representation of the png

For better quality, we should use an adaptive palette. I found the web safe palette butchered my png’s, but the adaptive palette result was not distinguishable from the png to me.

	img = PIL.Image.open('some.png')
	img2 = img.convert("P",palette=PIL.Image.ADAPTIVE)		#Convert true color image to Palette.
	img2.save('some.gif')		#web safe representation of the png

But if some frames of the animation use different colors, then the ideal palettes for those images will be different. The GIF will only use one palette for the whole animation, and so the colors will distort on those frames of different colors (or this is what it seems to me based on many frames having crazy colors).

The use of PIL.Image.blend to create fading transitions makes this problem even worse, as it creates intermediate colors.

Get the Best Palette with GIMP

For me, the solution was to use GIMP to give all three distinct images the same palette. To do this, have all relevant images open in GIMP as different layers. If you don’t have this already, just open one in gimp and import the others as layers. Goto Image->Mode->Indexed and select an optimized palette of 255 colors. Assuming all layers are stacked on top of each other and only the top frame is visible, just use File->Export as an aptly named png. Then make that layer invisible and export the next layer as a png, and so on.

 

Transitions with Numpy Masks

Once we have imported the images as index or palette mode png’s, there next step is generating the transition frames. We can use Image.composite which will replace one image by the next pixel by pixel, using masks we can define as numpy arrays. This avoids creating intermediate colors that won’t be in the palette. Slicing numpy arrays makes many transition patterns fairly easy.

for example:

	def v_fade(width = 8):
		'''make masks for fading from one image to the next through a vertical sweep.  Does this through numpy slices'''
		global img1				#from img1 = Image.open('one.png')
		n = np.array(img1)		#make a numpy.array the same size as the frame
		n.dtype=np.uint8		#correct the data type
		n[:][:] = 0				#the first mask lets everything through
	    #copy the array, else all masks will be the last one
		result=[Image.fromarray(n.copy())]
		for i in range(n.shape[0]/width+1):
			#add vertical strips of width one at a time.
			y=i*(width)
			n[y:y+width][:] = 255
			result.append(Image.fromarray(n.copy()))
		return result

Then we simply need a loop that calls Image.composite for each mask, and creates a list of images and durations for the GIF animation.

	img1, img2 = (Image.open('%s' % x) for x in ['1.png','2.png'])

	#fade from 1 to 2
	images = [Image.composite(img2,img1,mask) for mask in v_fade()]
	#fast on transition frams but hold end images longer
	durations = ['.10']*len(images)
	durations[0] = durations[-1] = '2.5'

	#fade from 2 to 1
	images.extend([Image.composite(img1,img2,mask) for mask in v_fade()])
	durations.extend(durations)

	writeGif('my.gif',images, duration=durations)

Once you know how to do it, making transitions in animated GIFs with python is very clean looking.

Leave a Reply

Your email address will not be published. Required fields are marked *