Voilà une petite expérimentation, déja expérimentée en AS2 avec les moyens de l'époque, inspirée d'un effet similaire réalisé par JL Gaujal sur Flash-France il y a déja pas mal de temps, et ça rendait super bien.
Et comme dernièrement je voulais me lancer dans un peu de traitement d'image afin de réaliser un vieux projet de composant texte 3D dans Sandy, j'ai commencé par le tout début, l'extraction des contours du bitmap. Après il y aura énormément de choses à rajouter, notament la gestion des trous, puis la tesselation etc etc. Mais bon, fallait bien commencer
Une fois tous les points du contours extraits, j'applique une très simple vectorisation, en essayant de garder que les points de contrôle. Mais là, c'est encore vraiment très très sommaire, faudrait l'améliorer, si ça tente d'autres personnes que moi d'ailleurs.
Sur cet exemple, cliquez dans le swf pour avoir le focus, puis pressez une touche du clavier pour changer de caractère. Son contours devrait être automatiquement tracé. On remarque qu'a cause de l'aliasing les parties en diagonale sont très mal vectorisées pour l'instant et ca devient presque du dessin point par point.
L'embryon de classe si vous voulez jouer un peu avec vous aussi.
-
package
-
{
-
import flash.display.BitmapData;
-
import flash.geom.Point;
-
import flash.utils.Dictionary;
-
-
/**
-
* ShapeExtractor class
-
* Extracts the shape information from a bitmapdata instance
-
* @author Thomas PFEIFFER (kiroukou)
-
* @date March 19th 2008
-
* @version 0.1
-
* @licence http://creativecommons.org/licenses/by-sa/3.0/
-
*
-
* <example>
-
* var format:TextFormat = new TextFormat( null, 45 );
-
* var tf:TextField = new TextField();
-
* tf.textColor = 0xFFFFFF;
-
* tf.autoSize = TextFieldAutoSize.LEFT;
-
* tf.text = "D";
-
* tf.setTextFormat( format );
-
*
-
* var b1:Bitmap = new Bitmap( new BitmapData( tf.textWidth + 5, tf.textHeight + 5, false, 0 ) );
-
* b1.bitmapData.draw( tf, null, null, null, null, true );
-
* addChild( b1 );
-
*
-
* var extractor:ShapeExtractor = new ShapeExtractor( b1.bitmapData );
-
* extractor.extract();
-
* extractor.vectorize();
-
*
-
* var l_oPt:Point;
-
* var l_oShape:Sprite = new Sprite();
-
*
-
* l_oShape.graphics.beginFill( 0xFF0000 );
-
* for each( l_oPt in extractor.points )
-
* {
-
* l_oShape.graphics.drawRect( l_oPt.x, l_oPt.y, 1, 1 );
-
* }
-
* l_oShape.graphics.endFill();
-
*
-
*
-
* l_oShape.graphics.beginFill( 0x00FF );
-
* for each( l_oPt in extractor.controlPoints )
-
* {
-
* l_oShape.graphics.drawRect( l_oPt.x, l_oPt.y, 1, 1 );
-
* }
-
* l_oShape.graphics.endFill();
-
*
-
* addChild( l_oShape );
-
* </example>
-
*/
-
-
public class ShapeExtractor
-
{
-
public const controlPoints:Array = new Array();
-
public const points:Array = new Array();
-
-
/**
-
* @p_oBitmapData The bitmapdata instance to extract information from.
-
*/
-
public function ShapeExtractor( p_oBitmapData:BitmapData )
-
{
-
setBitmap( p_oBitmapData );
-
}
-
-
/**
-
* @p_oBitmapData The bitmapdata instance to extract information from.
-
*/
-
public function setBitmap( p_oBitmapData:BitmapData ):void
-
{
-
b = p_oBitmapData;
-
h = b.height;
-
w = b.width;
-
}
-
-
/**
-
* Clear all the arrays and storage objects
-
* Important to call this before ShapeExtractor instance reuse
-
*/
-
public function clear():void
-
{
-
controlPoints.splice(0);
-
points.splice(0);
-
for( var i:* in visited )
-
visited[i] = null;
-
crt = null;
-
}
-
-
/**
-
* Extract the edges pixels from the given bitmapdata object
-
*/
-
public function extract():void
-
{
-
var startPos:Point = getStartPoint();
-
if( startPos == null ) return;
-
// --
-
visited[ startPos.x * w + startPos.y ] = true;
-
lastDir = 0;
-
points.push( startPos );
-
crt = startPos;
-
// --
-
while( true )
-
{
-
if( ! process() )
-
{
-
crtId -= 1;
-
crt = points[ crtId ];
-
lastDir = 0;
-
// si on est revenu au depart...
-
if( crt == startPos )
-
return;
-
}
-
else
-
{
-
//crtId += 1;
-
crtId = points.length-1;
-
}
-
}
-
}
-
-
/**
-
* Vectorize the information picked up by the extract method call.
-
* THis method will populate the controlPoints public array which will contain all the control points of the shape.
-
* TODO: Allows some optimization of the path when points have a quite close direction and for edges in stairs.
-
* MANDATORY: call extract method first
-
*/
-
public function vectorize():void
-
{
-
var crtDirX:int=points[1].x - points[0].x, crtDirY:int=points[1].y - points[0].y;
-
// --
-
controlPoints.push( points[0] );
-
controlPoints.push( points[1] );
-
// --
-
var i:int = 0, l:int = points.length;
-
var previous:Point = points[1];
-
// --
-
for( i=2; i <l; i += 1 )
-
{
-
var p:Point = points[int(i)];
-
// --
-
var dx:Number = p.x - previous.x;
-
var dy:Number = p.y - previous.y;
-
// si pas meme direction
-
if( dx == crtDirX && dy == crtDirY )
-
{
-
controlPoints[ controlPoints.length - 1 ] = p;
-
}
-
else
-
{
-
crtDirX = dx;
-
crtDirY = dy;
-
// --
-
controlPoints.push( p );
-
}
-
// --
-
previous = p;
-
}
-
}
-
-
/**
-
* @private
-
*/
-
private function process():Boolean
-
{
-
var i:int;
-
var dir:Point;
-
var crx:int, cry:int;
-
var max:int = 0;
-
var bestDirId:int = -1;
-
var nextPos:Point = new Point();
-
-
for( i=0; i <8; i+= 1 )
-
{
-
dir = dirs[int(i)];
-
// --
-
crx = crt.x + dir.x;
-
cry = crt.y + dir.y;
-
if( crx <0 || crx>= w ) continue;
-
if( cry <0 || cry>= h ) continue;
-
-
if( b.getPixel( crx, cry ) == 0 )
-
continue;
-
// si ce point a deja ete etudié, byebye
-
if( visited[ (crx) * w + cry ] == true )
-
continue;
-
-
var count:int = isExtremity( crx, cry );
-
if( count> 0 )
-
{
-
max = count;
-
bestDirId = i;
-
-
nextPos.x = crx;
-
nextPos.y = cry;
-
-
visited[ (nextPos.x) * w + nextPos.y ] = true;
-
points.push( nextPos );
-
crt = nextPos;
-
lastDir = bestDirId;
-
return true;
-
}
-
}
-
return false;
-
}
-
-
/**
-
* @private
-
*/
-
private function isExtremity( px:int, py:int ):int
-
{
-
var i:int, crx:int, cry:int, dir:Point, iin:int=0, out:int=0;
-
for( i=0; i <4; i+= 1 )
-
{
-
dir = dirs[int(i)];
-
// --
-
crx = px + dir.x;
-
cry = py + dir.y;
-
-
if( crx <0 || crx>= w ) continue;
-
if( cry <0 || cry>= h ) continue;
-
if( visited[ (crx) * w + cry ] == true )
-
continue;
-
-
if( b.getPixel( crx, cry ) == 0 ) out++;
-
else iin++;
-
-
}
-
return out;
-
}
-
-
/**
-
* @private
-
*/
-
private function getStartPoint():Point
-
{
-
var i:int, j:int;
-
for( j=0; j <h; j += 1 )
-
{
-
for( i=0; i <w; i += 1 )
-
{
-
if( b.getPixel( i, j )> 0 )
-
return new Point( i, j );
-
}
-
}
-
return null;
-
}
-
-
private const dirs:Array = [new Point(1,0), new Point(0,1), new Point(-1, 0), new Point(0, -1), new Point(1,-1), new Point(-1, -1), new Point(-1, 1), new Point(1,1) ];
-
private var visited:Dictionary = new Dictionary(false);
-
private var lastDir:int = -1;
-
private var crtId:int = 0;
-
private var b:BitmapData;
-
private var crt:Point;
-
private var w:int;
-
private var h:int;
-
}
-
}

Cette création est mise à disposition sous un contrat Creative Commons.
21 March 2008 à 12:33 pm
Très intéressant !
On peux entrevoir de nombreuses applications possibles.
Bravo
21 March 2008 à 2:03 pm
Hello,
oui je pense aussi. C'est pour ça que je donne les sources d'ailleurs, car je pense que l'on peut vraiment le faire évoluer pour obtenir des choses bien plus interessantes. Et comme chacun à ses idées et ses compétences, on pourra peut etre voir des choses sympathiques (enfin si tout le monde respecte la licence...)
23 March 2008 à 9:09 am
Salut Thomas,
Ça a l'air super intéressant cependant quand je teste ton exemple j'ai cette erreur >>
TypeError: Error #1010: Un terme n'est pas défini et n'a pas de propriété.
at ShapeExtractor/vectorize()
at FontVector/applyChar()
at FontVector/onKey()
voilà
+
JP
23 March 2008 à 10:11 am
Hello JP.
J'ai eu aussi parfois cette erreur sur mon MAC. Je crois que c'est lorque j'utilise la touche majuscule...
As tu reussi à changer de caractère au moins une fois? Car ça je l'ai testé sur 2 MAC et ca marche sans soucis. Peut etre que cela vient du navigateur...?
26 March 2008 à 9:16 pm
Très intéressant en effet, je me pencherai dessus très prochainement.
Merci pour l'embryon