« Zipizator » est une application mobile créée par Ludo et Hyperfiction que nous avons développée pour accompagner la sortie de la série télévisée d’animation française Zip Zip sur une chaîne française.
Elle a été entièrement créée avec Adobe AIR.
Contexte et objectif :
La série d’animation raconte l’histoire d’animaux sauvages qui essaient de s’intégrer à la société « civilisée » en se déguisant en animaux domestiques grâce à des costumes.
En partant de là, l’idée de l’application est d’utiliser votre visage dans l’un de ces costumes en le capturant directement avec la caméra, en le récupérant depuis la galerie de votre téléphone, ou même en utilisant des images prédéfinies (intégrées) comme d’autres visages d’animaux.

Une fois le « visage » sélectionné, chaque costume peut être personnalisé avec des accessoires, sa couleur peut être changée, l’arrière-plan aussi.

Enfin, chaque costume a une animation correspondante, ce qui vous permet de capturer une vidéo de cette animation avec tous les changements apportés au costume, aux accessoires et à l’arrière-plan, et même à votre visage ! Il est également possible d’« imprimer » des cartes, comme des cartes d’anniversaire, de vœux, etc., ou de les envoyer par mail. Concrètement, ça enregistre une image bitmap aux bonnes dimensions pour l’impression.
Écran d’introduction :

L’écran d’introduction est comme un costume par-dessus l’application, qu’il faut dézipper pour commencer. Ça engage l’utilisateur dès le début. C’était amusant à coder. Plutôt que de partir sur une animation, chaque élément a été découpé en sections pour créer un écran « dézippable »… la position en y de la fermeture éclair déterminait l’angle et la position de chaque section découpée, reproduisant ainsi l’animation de déballage d’un costume dont elle s’inspirait. Nous n’avions pas les ressources pour ajouter le son du « zip » qui aurait fait varier dynamiquement la hauteur et le volume, mais l’API Sound d’ActionScript 3 en était capable.
Le joueur peut faire glisser la fermeture éclair vers le haut ou vers le bas à n’importe quelle vitesse (et même tout refermer s’il ou elle le souhaite).
Cette instance de l’écran de fermeture éclair est réutilisée plus tard dans l’application, mais automatiquement zippée ou dézippée comme transition d’écran, ce qui colle très bien avec l’univers de la série animée.
Photos depuis votre caméra ou votre galerie
En utilisant les classes AS3 Camera et CameraRoll, la capture et la sauvegarde depuis la caméra ne posaient pas de problème. Cependant, il fallait aussi pouvoir récupérer des images depuis la galerie.
Et comme les images de la galerie (ou n’importe quelle image sur le disque) ne sont pas forcément orientées correctement (certains fichiers, par exemple, manquent des données Exif qu’un téléphone aurait enregistrées), nous analysons les données exif des images avant de faire quoi que ce soit avec elles, puis nous les redessinons avec la bonne orientation.
La bibliothèque choisie pour l’analyse des données Exif était le package exif de shichiseki.
Il contient un ExifLoader, qui est un Loader flash étendu utilisant les octets chargés pour déterminer si 1. des données exif sont présentes et si oui, 2. faire pivoter l’image.
L’ExifLoader, cependant, a été modifié pour les besoins de cette application afin de charger des FilePromise, puisque c’est ce qu’on récupère de la classe AS3 CameraRoll.
Voici la version complète de notre classe ExifLoader modifiée, qui se contente essentiellement de surcharger la fonction loadFilePromise du Loader flash qu’elle étend.
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;
}
}
}
Capture vidéo des personnages animés personnalisés
Une ANE est utilisée pour la partie « capture vidéo »… même si on devrait plutôt parler d’encodage vidéo puisqu’on fournit chaque frame une par une. Le problème majeur ici était que l’encodeur lui-même n’était pas complet pour nos besoins à l’époque, donc nous avons pu échanger avec les développeurs et obtenir des réponses et des changements rapidement. L’ANE s’appelle FlashyWrappers et les développeurs ont été vraiment très serviables.
Chaque personnage animé a été créé dans des fichiers .swf séparés, et de telle façon qu’il était possible de définir le visage ou les accessoires comme enfants de movieclips afin qu’ils restent présents pendant l’animation et soient correctement capturés. Cependant, un problème avec l’éditeur Flash (Flash CC à l’époque) nous a fait réaliser que parfois, des instances (celle de la tête par exemple) étaient recréées à des moments aléatoires pendant l’animation, supprimant ainsi toute personnalisation effectuée. Donc au final, toute la personnalisation est réappliquée à quasiment chaque frame - c’est pour cette raison qu’on ne capture pas réellement les frames d’animation en temps réel (ce que l’ANE peut faire). Ça nous permet aussi de ne jamais perdre de frames et d’avoir la meilleure synchronisation sonore possible.
Sortie
Elle est disponible sur l’App Store iOS et le Google Play Store.