Sandy3D 3.0.2 FlexBuilder et son debugger bloqué
Mar 21

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.

JavaScript:
  1. package
  2. {
  3. import flash.display.BitmapData;
  4. import flash.geom.Point;
  5. import flash.utils.Dictionary;
  6.  
  7. /**
  8. * ShapeExtractor class
  9. * Extracts the shape information from a bitmapdata instance
  10. * @author Thomas PFEIFFER (kiroukou)
  11. * @date March 19th 2008
  12. * @version 0.1
  13. * @licence http://creativecommons.org/licenses/by-sa/3.0/
  14. *
  15. * <example>
  16. *     var format:TextFormat = new TextFormat( null, 45 );
  17. *      var tf:TextField = new TextField();
  18. *      tf.textColor = 0xFFFFFF;
  19. *      tf.autoSize = TextFieldAutoSize.LEFT;
  20. *      tf.text = "D";
  21. *      tf.setTextFormat( format );
  22. *
  23. *      var b1:Bitmap = new Bitmap( new BitmapData( tf.textWidth + 5, tf.textHeight + 5, false, 0 ) );
  24. *      b1.bitmapData.draw( tf, null, null, null, null, true );
  25. *      addChild( b1 );
  26. *
  27. *      var extractor:ShapeExtractor = new ShapeExtractor( b1.bitmapData );
  28. *      extractor.extract();
  29. *      extractor.vectorize();
  30. *
  31. *      var l_oPt:Point;
  32. *      var l_oShape:Sprite = new Sprite();
  33. *
  34. *      l_oShape.graphics.beginFill( 0xFF0000 );
  35. *      for each( l_oPt in extractor.points )
  36. *      {
  37. *         l_oShape.graphics.drawRect( l_oPt.x, l_oPt.y, 1, 1 );
  38. *      }
  39. *      l_oShape.graphics.endFill();
  40. *
  41. *
  42. *      l_oShape.graphics.beginFill( 0x00FF );
  43. *      for each( l_oPt in extractor.controlPoints )
  44. *      {
  45. *         l_oShape.graphics.drawRect( l_oPt.x, l_oPt.y, 1, 1 );
  46. *      }
  47. *      l_oShape.graphics.endFill();
  48. *
  49. *      addChild( l_oShape );
  50. * </example>
  51. */
  52.  
  53. public class ShapeExtractor
  54. {
  55. public const controlPoints:Array = new Array();
  56. public const points:Array = new Array();
  57.  
  58. /**
  59. * @p_oBitmapData The bitmapdata instance to extract information from.
  60. */
  61. public function ShapeExtractor( p_oBitmapData:BitmapData )
  62. {
  63. setBitmap( p_oBitmapData );
  64. }
  65.  
  66. /**
  67. * @p_oBitmapData The bitmapdata instance to extract information from.
  68. */
  69. public function setBitmap( p_oBitmapData:BitmapData ):void
  70. {
  71. b = p_oBitmapData;
  72. h = b.height;
  73. w = b.width;
  74. }
  75.  
  76. /**
  77. * Clear all the arrays and storage objects
  78. * Important to call this before ShapeExtractor instance reuse
  79. */
  80. public function clear():void
  81. {
  82. controlPoints.splice(0);
  83. points.splice(0);
  84. for( var i:* in visited )
  85. visited[i] = null;
  86. crt = null;
  87. }
  88.  
  89. /**
  90. * Extract the edges pixels from the given bitmapdata object
  91. */
  92. public function extract():void
  93. {
  94. var startPos:Point = getStartPoint();
  95. if( startPos == null ) return;
  96. // --
  97. visited[ startPos.x * w + startPos.y ] = true;
  98. lastDir = 0;
  99. points.push( startPos );
  100. crt = startPos;
  101. // --
  102. while( true )
  103. {
  104. if( ! process() )
  105. {
  106. crtId -= 1;
  107. crt = points[ crtId ];
  108. lastDir = 0;
  109. // si on est revenu au depart...
  110. if( crt == startPos )
  111. return;
  112. }
  113. else
  114. {
  115. //crtId += 1;
  116. crtId = points.length-1;
  117. }
  118. }
  119. }
  120.  
  121. /**
  122. * Vectorize the information picked up by the extract method call.
  123. * THis method will populate the controlPoints public array which will contain all the control points of the shape.
  124. * TODO: Allows some optimization of the path when points have a quite close direction and for edges in stairs.
  125. * MANDATORY: call extract method first
  126. */
  127. public function vectorize():void
  128. {
  129. var crtDirX:int=points[1].x - points[0].x, crtDirY:int=points[1].y - points[0].y;
  130. // --
  131. controlPoints.push( points[0] );
  132. controlPoints.push( points[1] );
  133. // --
  134. var i:int = 0, l:int = points.length;
  135. var previous:Point = points[1];
  136. // --
  137. for( i=2; i &lt;l; i += 1 )
  138. {
  139. var p:Point = points[int(i)];
  140. // --
  141. var dx:Number = p.x - previous.x;
  142. var dy:Number = p.y - previous.y;
  143. // si pas meme direction
  144. if( dx == crtDirX &amp;&amp; dy == crtDirY )
  145. {
  146. controlPoints[ controlPoints.length - 1 ] = p;
  147. }
  148. else
  149. {
  150. crtDirX = dx;
  151. crtDirY = dy;
  152. // --
  153. controlPoints.push( p );
  154. }
  155. // --
  156. previous = p;
  157. }
  158. }
  159.  
  160. /**
  161. * @private
  162. */
  163. private function process():Boolean
  164. {
  165. var i:int;
  166. var dir:Point;
  167. var crx:int, cry:int;
  168. var max:int = 0;
  169. var bestDirId:int = -1;
  170. var nextPos:Point = new Point();
  171.  
  172. for( i=0; i &lt;8; i+= 1 )
  173. {
  174. dir = dirs[int(i)];
  175. // --
  176. crx = crt.x + dir.x;
  177. cry = crt.y + dir.y;
  178. if( crx &lt;0 || crx&gt;= w ) continue;
  179. if( cry &lt;0 || cry&gt;= h ) continue;
  180.  
  181. if( b.getPixel( crx, cry ) == 0 )
  182. continue;
  183. // si ce point a deja ete etudié, byebye
  184. if( visited[ (crx) * w + cry ] == true )
  185. continue;
  186.  
  187. var count:int = isExtremity( crx, cry );
  188. if( count&gt; 0 )
  189. {
  190. max = count;
  191. bestDirId = i;
  192.  
  193. nextPos.x = crx;
  194. nextPos.y = cry;
  195.  
  196. visited[ (nextPos.x) * w + nextPos.y ] = true;
  197. points.push( nextPos );
  198. crt = nextPos;
  199. lastDir = bestDirId;
  200. return true;
  201. }
  202. }
  203. return false;
  204. }
  205.  
  206. /**
  207. * @private
  208. */
  209. private function isExtremity( px:int, py:int ):int
  210. {
  211. var i:int, crx:int, cry:int, dir:Point, iin:int=0, out:int=0;
  212. for( i=0; i &lt;4; i+= 1 )
  213. {
  214. dir = dirs[int(i)];
  215. // --
  216. crx = px + dir.x;
  217. cry = py + dir.y;
  218.  
  219. if( crx &lt;0 || crx&gt;= w ) continue;
  220. if( cry &lt;0 || cry&gt;= h ) continue;
  221. if( visited[ (crx) * w + cry ] == true )
  222. continue;
  223.  
  224. if( b.getPixel( crx, cry ) == 0 ) out++;
  225. else iin++;
  226.  
  227. }
  228. return out;
  229. }
  230.  
  231. /**
  232. * @private
  233. */
  234. private function getStartPoint():Point
  235. {
  236. var i:int, j:int;
  237. for(  j=0; j &lt;h; j += 1 )
  238. {
  239. for(  i=0; i &lt;w; i += 1 )
  240. {
  241. if( b.getPixel( i, j )&gt; 0 )
  242. return new Point( i, j );
  243. }
  244. }
  245. return null;
  246. }
  247.  
  248. 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) ];
  249. private var visited:Dictionary = new Dictionary(false);
  250. private var lastDir:int = -1;
  251. private var crtId:int = 0;
  252. private var b:BitmapData;
  253. private var crt:Point;
  254. private var w:int;
  255. private var h:int;
  256. }
  257. }

Creative Commons License
Cette création est mise à disposition sous un contrat Creative Commons.

5 commentaires pour “Extraction des contours d’un bitmap”

  1. fab a dit :

    Très intéressant !
    On peux entrevoir de nombreuses applications possibles.
    Bravo

  2. kiroukou a dit :

    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...)

  3. jeanphilippe a dit :

    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

  4. kiroukou a dit :

    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...?

  5. LutinCapuche a dit :

    Très intéressant en effet, je me pencherai dessus très prochainement.
    Merci pour l'embryon :)

Répondre