Sandy 3.0 RC0 - Première version officielle de Sandy AS3 Mise à jour Flashplayer
Sep 25

Ces dernier temps je me penche pas mal sur l'AS3 et principalement les méthodes graphiques.

Je suis tombé aujourd'hui durant mes expérimentations sur quelque chose que je considère comme surprenant voir aberrant.

Depuis le player 8, nous avons accès comme vous le savez tous à une nouvelle méthode de tracé graphique, la méthode nommée beginBitmapFill.

Cette méthode m'a permis de réaliser la classe DistortImage ainsi que de mettre en place les techniques de mapping texturé sur un moteur 3D.

Et bien depuis le temps que j'utilisais cette méthode, je n'avais finalement pas compris un élément important : l'objet Bitmapdata utilisé lors du tracé, reste toujours lié (par référence je suppose) à l'élément dans le movieclip (ou Graphics).

Un exemple valant plus qu'un explication voilà ce que je veux dire:

JavaScript:
  1. import flash.display.BitmapData;
  2. import flash.geom.Point;
  3. import flash.geom.Rectangle;
  4. import flash.filters.*;
  5.  
  6. var bmd:BitmapData = new BitmapData(80, 30, false, 0xFFCC00);
  7. var rect:Rectangle = new Rectangle(10, 10, 40, 10);
  8.  
  9. var pt:Point = new Point(10, 10);
  10. var filter:ColorMatrixFilter = new ColorMatrixFilter(  [1, 0, 0, 0, 0,
  11.                                                         0, 0, 0, 0, 0,
  12.                                                         0, 0, 0, 0, 0,
  13.                                                         0, 0, 0, 1, 0]);
  14. bmd.applyFilter(bmd, rect, pt, filter);
  15.  
  16. var lSprite:Sprite = new Sprite();
  17. lSprite.graphics.lineStyle();
  18. lSprite.graphics.beginBitmapFill( bmd );
  19. lSprite.graphics.moveTo( 0, 0 );
  20. lSprite.graphics.lineTo( 80, 0 );
  21. lSprite.graphics.lineTo( 80, 30 );
  22. lSprite.graphics.lineTo( 0, 30 );
  23. lSprite.graphics.lineTo( 0, 0 );
  24. lSprite.graphics.endFill();
  25.  
  26. addChild( lSprite );

Copiez/collez ce morceau de code dans Flash CS3 et vous devriez obtenir ceci :
rectangle 2

Jusque là, rien ne devrait vous sembler étrange.
Maintenant modifions cet objet bitmapdata une fois le dessin terminé.

JavaScript:
  1. import flash.display.BitmapData;
  2. import flash.geom.Point;
  3. import flash.geom.Rectangle;
  4. import flash.filters.*;
  5.  
  6. var bmd:BitmapData = new BitmapData(80, 30, false, 0xFFCC00);
  7. var rect:Rectangle = new Rectangle(10, 10, 40, 10);
  8.  
  9. var pt:Point = new Point(10, 10);
  10. var filter:ColorMatrixFilter = new ColorMatrixFilter(  [1, 0, 0, 0, 0,
  11.                                                         0, 0, 0, 0, 0,
  12.                                                         0, 0, 0, 0, 0,
  13.                                                         0, 0, 0, 1, 0]);
  14. bmd.applyFilter(bmd, rect, pt, filter);
  15.  
  16. var lSprite:Sprite = new Sprite();
  17. lSprite.graphics.lineStyle();
  18. lSprite.graphics.beginBitmapFill( bmd );
  19. lSprite.graphics.moveTo( 0, 0 );
  20. lSprite.graphics.lineTo( 80, 0 );
  21. lSprite.graphics.lineTo( 80, 30 );
  22. lSprite.graphics.lineTo( 0, 30 );
  23. lSprite.graphics.lineTo( 0, 0 );
  24. lSprite.graphics.endFill();
  25.  
  26. addChild( lSprite );
  27. // On modifie le bitmap afin de le remplir à nouveau d'orange
  28. bmd.fillRect(rect, 0xFFCC00);

Et en voici le résultat :
rectangle 1

Comme vous le voyez, modifier le bitmapdata, même une fois après l'avoir "rasterizé" (enfin ce que je pensais qu'il était le cas) Dans un élément graphique (Movieclip ou Graphics), provoque une répercussion sur cet élement graphique.

Je trouve ce comportement très étrange personnellement. Il semble pourtant clair que dessiner un bitmap dans un clip revient à rasterizer ce dernier, ou tout du moins, en créer une copie en interne.

Bon bien entendu, je suis allé voir la documentation pour vois si une note pourrait m'expliquer ceci, et voilà :

Le remplissage demeure en vigueur jusqu'à ce que vous appeliez la méthode beginFill(), beginBitmapFill() ou beginGradientFill()

Ce n'est pas le endFill qui stoppe le remplissage... Le endFill permet juste de rendre valide le tracé et de le mettre dans les routines de tracés du player.

Un comportement suprenant, et toujours bon à savoir je pense.

Note: Ceci est tout aussi vrai avec Flash 8 et Flash CS3.

9 commentaires pour “Comportement surprenant de Graphics.beginBitmapFill”

  1. Cedric a dit :

    Hum interessant. Personnellement je n'ai pas compris la note issue de la documentation que tu as cite...

    Si on y reflechit, ca veut dire qu'on pourrait gagner en ressource si on arrivait a forcer Flash a ne plus updater le remplissage depuis le bitmapData source. Non ?

  2. kiroukou a dit :

    Ca veut dire que nous ne sommes pas obligés de re-tracer le bitmap dans le sprite qd on modifie le bitmapdata. Peut etre qu'on peut y gagner en appliquant la transformation à la matrice du sprite et non refaire un tracé bitmap.

    Mais c'est surtot très handicapant comme fonctionnement je trouve. Et ça m'a fait perdre pas mal de temps .... :)

  3. Cedric a dit :

    Yes en fait, toutes les méthodes de Graphics sont vectorielles. Or dans la "philosophie" vectorielle, les tracés sont enregistrées de manière symbolique (données de tracés des lignes, de remplissqges...) et rasteriées à la volée à chaque rendu.

    Donc dans cette optique, la méthode fillBitmap ne fait qu'enregister les données parmettant de faire le remplissage (adresse mémoire vers le bitmapData source, matrice de transformation, les points du tracé de remplissage...). C'est au rendu que le rasterizer remplit les pixels à l'écran grace à ces données.

    Ca implique que les détails sont conservés. Par exemple : je trace un bitmap dans un clip en divisant sa taille par 10 par matrice: si ensuite je zoom sur le clip, on verra les détails du bitmap se révéler.

    Et si la matrice est aussi conservée sous forme d'adresse mémoire, alors il suffit effectivement de modifier la matrice pour modifier dynamiquement certains paramètres du remplissqge (position, skew, scale et rotation). Et qu'y gagne t'on en faisant cela plutot que de refaire un fillBitmap ? A mon avis tout le processus d'accès en mémoire pour écrire la procédure de remplissage.

    D'un autre coté, ça veut aussi dire que dans une appli, si on fait des fillBitmap dans des clips qui ne bougent pas, il y a tout intérêt à faire des cacheAsBitmap, car sinon le remplissage se refait à chaque rendu, en silence.

  4. Cedric a dit :

    Je viens de tester de modifier dynamiquement une reference de matrice qui a servi a faire un bitmapFill et ca ne modifie pas le remplissage. Donc quand on fait un bitmapFill, la matrice donnée en argument est copiée par la routine de tracé. Je suis déçu...

    Mais effectivement, on peut toujours tracer dans un Sprite intermédiaire, qui nous permet d'obtenir cette matrice dynamique, et ainsi déformer et changer le bitmap sans refaire un bitmapFill.

    Une chose me déçoit maintenant dans la classe Graphics, c'est que tous les points utilisés en argument dans ses méthodes sont écrits sous forme "x, y", au lieu d'utiliser la classe Point du package Geom.
    En gardant l'adresse mémoire des points passés en argument, on aurait pu animer des tracés en modifiant les points sources, sans etre obligé de faire clear() suivi d'une nouvelle routine de tracé... Bon tant pis, peut-être en AS4 !

  5. Bananatree a dit :

    bonjour,

    var p1:Point = new Point(x1, y1);
    myGraphics.moveTo(p1.x, p1.y);

    concernant le comportement de BitmapData je ne pense pas qu'il soit anormal. Le bitmap data créé est un object transmis au moteur de rendu du flash player. Le moteur se charge de rafraichir son contenu contenu à la fréquence donnée en fps. Si les objets auxquels il se réfère changent, le moteur ne fait qu'actualiser ces objets, qu'ils soient vectoriel ou bitmap.

    concernant les matrices, elles ne modifient l'affichage que si on redessine l'object. (beginBitmapFill ou draw)

    ++

  6. kiroukou a dit :

    Donc dans cette optique, la méthode fillBitmap ne fait qu'enregister les données parmettant de faire le remplissage (adresse mémoire vers le bitmapData source, matrice de transformation, les points du tracé de remplissage...). C'est au rendu que le rasterizer remplit les pixels à l'écran grace à ces données.

    C'est la conclusion que j'en tire aussi. Malheureusement pour moi, ce n'est pas vraiment ce à quoi je m'attendais. Une fois le endFill exécuté, j'aurais aimé pouvoir manipuler mon bitmapdata sans influencer ce que j'ai déssiné dans l'objet graphique préalablement.

    concernant les matrices, elles ne modifient l'affichage que si on redessine l'object.

    Ce n'est pas tout à fait vrai.
    Modifier la matrice d'un MovieClip (oui car je n'ai testé ceci que sur Flash8) revient en fait à faire la même chose qu'un beginBitmapFill au niveau performance et au niveau visuel.

    Ceci rejoint la conclusion tirée plus haut. Flash ne rasterize pas une bonne fois pour toute le bitmap lors de l'appel beginBitmapFIll. Il garde cette référence en mémoire (avec la matrice donc) jusqu'à ce que le player en fasse le rendu.

    Encore une fois, je ne considère pas cela comme un bug, mais comme un comportement surprenant, et dont la connaissance peut faire éviter une bonne heure de debug.

  7. Cedric a dit :

    Banatree quand tu dis :

    "var p1:Point = new Point(x1, y1);
    myGraphics.moveTo(p1.x, p1.y);"

    Je ne vois pas le rapport, ici modifier p1 sans refaire les routines n'ira pas modifier le tracé à l'écran... et c'est ça que j'aurai aimer avoir dans les routines de Graphics.

  8. Bananatree a dit :

    Cedric

    ok j'avais pas saisis. tu veux que l'object Graphics change qd le point est modifié, sans le redessiner.

  9. Samothtronicien a dit :

    Je viens d'avoir un probleme dans le meme style :

    Méthode de rendu :

    var bmp1:BitmapData = logo.bitmapData;
    this.graphics.beginBitmapFill(bmp1);
    this.graphics.drawRect(0,0,300,300);
    this.graphics.endFill();

    Le framerate de l'anim état à 120, ca démarre à 120, puis brutalement ca chute encore et encore ...
    Je me suis apercu qu'en rajoutant un clear de la sorte :

    var bmp1:BitmapData = logo.bitmapData;
    this.graphics.clear();
    this.graphics.beginBitmapFill(bmp1);
    this.graphics.drawRect(0,0,300,300);
    this.graphics.endFill();

    Tout marche beaucoup mieux ... purée le truc vraiment con :(

Répondre