Ascii Art au coin du feu

février 7th, 2008 by Tony

C’était à l’approche de noël, j’étais tranquillement assis dans la salle à manger, près du feu de cheminée, tapotant sur mon ordinateur portable, à la recherche de quelque chose de drôle à coder…

Alors, je me suis rappelé de cette démonstration de Ascii Art, qui constitue l’un des exemples fournis par Adobe, pour démontrer les possibilités de l’ActionScript3. Je me suis souvenu, qu’à l’époque, j’avais été assez séduit par le style et que je m’étais promis d’essayer, moi aussi, de coder une forme d’ascii art…

Flash CS3 et FlashDevelop en tête, je m’embarque dans cette folle aventure :)

je ne savais pas trop comment m’y prendre, mais je savais qu’il me faudrait nécessairement passer par une étape d’analyse de l’image à ascii-iser.

Analysons, analysons…

Mais avant de discuter des moyens, présentons la fin…

(Notez que le navigateur n’est pas le meilleur conteneur pour ce programme qui utilise massivement le processeur. Préférez tester localement sur votre poste en enregistrant la cible de ce lien)

Je suis partit sur quelque chose qui ressemblait à çà :

// Instancions donc un sous-type de BitmapData présent dans la bibliothèque de notre fla
// Il s'agit de l'image originale sur laquelle nous allons travailler.
     var originale:BitmapData = BitmapData(new Fruit0(0,0));
 
     var c:uint      = 0; // Une simple variable que nous pourrons incrémenter pour servir de compteur (toujours utile)
     var dim:uint    = 2; // La taille des blocs de pixels que l'on souhaite analyser (ici 2*2 soit des blocs des 4 pixels)
 
 // Une valeur de décalage entre les blocs. On analyse des blocs de 2 pixels de largeurs et hauteurs et on a un
 // décalage de 3 pixels, ce qui nous laisse un pixel vide entre chaque bloc.
     var offset:uint = 3;
 
 // La position de chaque nouveau bloc. L'idée, c'est de recréer l'image en la simplifiant, donc on va créer un
 // certain nombres de blocs de couleurs à partir de l'image originale.
     var posX:uint = 0;
     var posY:uint = 0;
 
  // Compte tenu de ces paramètres, on va se retrouver avec un certain nombre de lignes et de colonnes de blocs
     var iCols:uint = Math.floor(originale.width / offset);
     var iLines:uint = Math.floor(originale.height / offset);
 
  // A partir de là, l'idée c'est d'analyser l'image bloc par bloc (de 4 pixels) et d'obtenir les couleurs de
  // chaque pixel du bloc.
 
  for ( var i:uint = 0 ; i < iCols ; i++ ){
      for( var j:uint = 0 ; j < iLines ; j++ ){
 
           // Créons un object Rectangle de dimension dim * dim, positionné à posX, posY
           var rect:Rectangle = new Rectangle(posX, posY, dim, dim);
 
           // Dès lors, nous pouvons obtenir un tableau d'octets des pixels du bloc de dimension dim * dim situé aux coordonnées {posX, posY} de notre image
           var bytes:ByteArray = originale.getPixels(rect);
	   bytes.position = 0; // Ici, on repositionne l'itérateur du tableau à 0
 
           // ...
 
           // Incrémentons afin de passer au bloc suivant...
           posY += offset;
	   c++;
	}
	posY = 0;
	posX += offset;
}

Où en sommes-nous ?

Il semblerait que à chaque itération, nous obtenons un objet ByteArray constitué des couleurs 32 bits de chaque pixel du bloc en cours d’analyse.

Et alors… ?

Hé bien, après différents tests, je me suis dit que je pourrais trouver la couleur médiane (ou moyenne) de chaque bloc de couleurs. Cela peux se faire à partir des valeurs des différents canaux qui composent chaque pixel d’un bloc de couleur (pour des couleurs 32 bits, il y a bien sûr 4 canaux : Alpha, Rouge, Vert et Bleu).

J’obtiens ainsi un ensemble d’informations qui dérivent (dans un certains sens) de l’image originale, informations à partir desquelles je peux reconstituer une “image” simplifiée de l’originale.
Complétons le bout de code précédant :

// Passons le ByteArray de pixels du bloc à une méthode chargée de renvoyer la couleur
// moyenne de ce bloc
   var medianeColor:String = this.analyse( bytes );	 
 
// Créons un nouvel objet ByteArray
   var inputByteArray:ByteArray = new ByteArray();
 
 // Ici, nous écrivons pour chaque pixel du bloc, la couleur dominante du bloc dans l'objet ByteArray créé pour l'occasion
 
     for ( var a:uint = 0 ; a < bytes.length/4 ; a++){
		inputByteArray.writeUnsignedInt( uint( dominanteColor )) ;
      }
      inputByteArray.position = 0; // On replace l'itérateur au début
 
 // Nous disposons des informations nécessaires, mais il faut les représenter en mémoire dans un
 // objet qui permettra de les afficher ultérieurement à l'écran ; un BitmapData est approprié
 
      var bmd:BitmapData = new BitmapData(dim, dim, true, 0xFFFF0000);
 
  // Reste à écrire la couleur dans le BitmapData...
 
      bmd.setPixels(new Rectangle(0,0,dim,dim), inputByteArray);
 
  // Afin de permettre l'affichage du BitmapData à l'écran, il faut l'envelopper dans un objet Bitmap
       var b:Bitmap = new Bitmap(bmd);
       b.name = "bit" + c++; // Notre variable c va servir à nommer les différentes instances de Bitmap
 
  // Et afin d'afficher ce bitmap de façon à reconstituer l'image, on lui affecte posX et posY
  // comme valeurs de ses propriétés x et y
        b.x = posX;
	b.y = posY;
 
  // Je voulais pouvoir réutiliser ces objets Bitmap d'affichage, il fallait donc les stocker.
  // Pour stocker des données, j'utilise en général une classe dédiée (un modèle) et globale (singleton)
	Model.getInstance().dessin.push(b); // Stocke chaque bitmap dans un tableau dynamique
 
  // Ici, on affiche chaque bloc à l'écran
        _pCanvas.addChild(b);
 
  // ...

Visuellement, on est passé de ceci :
originale
à ceci :
etape1

Alors, bien sûr…je ne vais pas prétendre qu’il s’agit là d’Ascii-art… :)

Mais je peux en revanche assurer que l’on a là tous les éléments pour passer à ceci :

ascii_nozoom

Bon, d’accord, on ne vois pas trop la différence…

ascii_superzoom

Ah ! On y voit plus clair là… :D

Pour obtenir ce dernier motif, je reprends les informations contenues dans le tableau dynamique de mon modèle : ‘dessin’.

Pour chaque chaque élément, j’ai la couleur et la position, je peux donc générer des TextFields de telle couleur à telle position.

Afin d’obtenir des signes plus exotiques, j’ai utilisé cette formule :

String.fromCharCode( Math.round( Math.random() * 400 + 10000 ));

C’est à ce moment là que je me suis dit que j’allais utiliser Papervision3D et Wiiflash pour pousser le truc plus loin, mais il n’y avait plus une seule bûche dans la cheminée…dommage, @+ :D