“Zip zip” avatar creator and character animation capture

“Zipizator” is a mobile app created by Ludo and Hyperfiction we developed to accompany the Zip Zip french animated television series’s release on a french channel.

It was fully created with Adobe Air.

Background and goal :

The animated series is about wild animals trying to integrate into ‘civilized’ society by disguising themselves into domesticated animals with costumes.

Based on that, the app’s idea is to use your face in one of these costumes by capturing your face directly with the camera of get it from your phone’s gallery, or even use predefined pictures (built in) such as other animal faces.

Screenshot_2016-06-29-10-39-20

 

After the “face” is selected, each costume can be customized with accessories, its color can be changed, the background can also change.

Screenshot_2016-06-29-10-39-43

 

Finally, each costume has a corresponding animation so you can capture a video of this animation with all the changes you’ve made to the costume, the accessories and the background, and even your face ! It’s also possible to ‘print’ cards such as birthday cars, best wishes and so on, or send them by mail. Essentially, this saves a bitmap with the right dimensions for print.

 

Intro screen :

Screenshot_2016-06-29-10-39-02

 

The intro screen is like a costume over the app that you need to unzip to get started. This gets the user engaged right from the beginning. It was a fun thing to code. instead of going for an animation, each element here was cut in sections in order to create an unzippable screen… the y position of the zipper would determine what angle and position which cut sections would be at, effectively reproducing a costume unzipping animation it was based of of. We did not have the resources to add in the “zip” sound that would’ve dynamically changed pitch and volume though but the actionscript 3 Sound api was capable of it.

The player can drag the zip up or down at any speed (even close everything back up if he or she wishes).

This instance of the zipper screen is later reused during the app but automatically zipped up or down as a screen transition which goes very well with the context of the animated show.

Photos from your camera or your gallery

Using the Camera and CameraRoll as3 classes, the camera capture and save was not problem. However, we had to be able to get gallery pictures as well.

And because gallery pictures (or any picture on disk) are not necessarily rotate the right way (some file for example, lack the Exif data that a phone would save), we parse exif data from pictures before doing anything with them and draw them back with the right orientation.

The library chosen for Exif data parsing was shichiseki’s exif package.

It contains an ExifLoader which is an extended flash Loader that uses the loaded bytes to determine if 1. exif data is present and if so 2. rotate the picture.

The ExifLoader however, for the purpose of this app, was modified to load FilePromise’s since that’s what we get from the CameraRoll as3 class.

Here’s the full version of our modified ExifLoader class which essentially just override’s the loadFilePromise function of the flash Loader it extends.

package jp.shichiseki.exif {
	import flash.utils.IDataInput;
	import flash.desktop.IFilePromise;
	import flash.media.MediaPromise;
	import flash.display.Loader;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.events.*;

	/**
	 * The ExifLoader is a sub class of <code>Loader</code> class which is used to load JPG
	 * files with EXIF.
	 * @example A Typical usage is shown in the following code:
	 * <listing version="3.0">
	 * private function loadImage():void {
	 *   var loader:ExifLoader = new ExifLoader();
	 *   loader.addEventListener(Event.COMPLETE, onComplete);
	 *   loader.load(new URLRequest("http://www.example.com/sample.jpg"));
	 * }
	 *
	 * private function onComplete(e:Event):void {
	 *   // display image
	 *   addChild(e.target);
	 *   // display thumbnail image
	 *   var thumbLoader:Loader = new Loader();
	 *   thumbLoader.loadBytes(loader.exif.thumbnailData);
	 *   addChild(thumbLoader);
	 * }
	 * </listing>
	 *
	 * @see flash.display.Loader
	 */
	public class ExifLoader extends Loader {
		private var _exif : ExifInfo;

		/**
		 * Returns a ExifInfo object containing information about loaded JPG image file.
		 */
		public function get exif() : ExifInfo {
			return _exif;
		}

		private var _urlLoader : URLLoader;
		private var _dataSource:IDataInput;
		private var _eventSource:IEventDispatcher;
		private var _context : LoaderContext;

		/**
		 * Loads a JPG image file into an object that is a child of this ExifLoader object.
		 */
		override public function load(request : URLRequest, context : LoaderContext = null) : void {
			_context = context;

			_urlLoader = new URLLoader();
			_urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
			_urlLoader.addEventListener(Event.COMPLETE, onComplete, false, 0, true);
			_urlLoader.addEventListener(IOErrorEvent.IO_ERROR, forwardEvent, false, 0, true);
			_urlLoader.addEventListener(ProgressEvent.PROGRESS, forwardEvent, false, 0, true);
			_urlLoader.addEventListener(Event.OPEN, forwardEvent, false, 0, true);
			_urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, forwardEvent, false, 0, true);
			_urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, forwardEvent, false, 0, true);
			_urlLoader.load(request);
		}

		override public function loadFilePromise(filePromise : IFilePromise, context : LoaderContext = null) : void {
			
			_dataSource = filePromise.open();
			
			if(filePromise.isAsync)
			{
				_eventSource = _dataSource as IEventDispatcher;
				_eventSource.addEventListener( Event.COMPLETE,onComplete);           
			}else
			{
				onComplete(new Event(Event.COMPLETE));
			}
		}

		private function onComplete(e : Event = null) : void {
			var data : ByteArray;
			
			if (_dataSource) {
				data = new ByteArray();
				_dataSource.readBytes(data);
				
				data.position = 0;
				_exif = new ExifInfo(data);
				
				data.position = 0;
				super.loadBytes(data, _context);
				
				_dataSource = null;
				dispatchEvent(e);
			} else {
				data = _urlLoader.data as ByteArray;
				_exif = new ExifInfo(data);
				super.loadBytes(data, _context);
				_urlLoader = null;
				dispatchEvent(e);
			}
		}

		private function forwardEvent(e : Event) : void {
			dispatchEvent(e);
		}
		
		public function get dataSource():IDataInput {
			return _dataSource;
		}
	}
}

Video capture of animated customized characters

An ANE is used for the “video capture” part… but it should actually be called video encoding as we provide each frames one by one.

The major problem here was that the encoder itself was not complete for our needs at the time so we were able to communicate with the devs and get quick answers and changes.
The ANE is called FlashyWrappers and the devs were really helpful.

Now each animated character was created in separate .swf files and in such a way that it was possible to set the face or accessories as children of movieclips so that they will stay during the animation and be properly captured. However a problem with the flash editor (Flash CC at the time) led us to realize that some times, instances (of the head for example) would be recreated at random times during the animation, effectively removing any customization made. So in the end, all customization is reset almost on each frame – that’s the reason why don’t actually capture animation frames in real-time (which the ANE can do). This also enables us to never drop frames and get the best sound synchronization.

Release

It’s been available on iOS AppStore and Google Play Store.

One thought on ““Zip zip” avatar creator and character animation capture”

Leave a Reply to Jeff Thomson Cancel reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.