views:

433

answers:

1

Basically i'm just trying to create a high-performance AnimatedBitmap Class. I have investigated some Sprite-based AnimatedBitmap classes out there that use timers and some variation of BitmapData.copyPixels() or BitmapData.draw(), but these don't seem to be as fast as simply staggering the assets on a MovieClip timeline and letting the timeline play.

So what im thinking is a class that extends MovieClip and features something along the lines of the following pseudocode:

 public function contstructTimeline(frameContent:Vector<BitmapData>):void {
  //Iterate through the frameContent vector and add a bitmap to each keyframe....I do NOT want bitmaps to persist beyond the frame they have been added to.
  var thisBitmap:Bitmap;
  for(var i:int = 0 ; i < frameContent.length; i++){
   gotoAndStop(i);
   thisBitmap = new Bitmap(frameContent[i]);
   addChild(thisBitmap);    
  }
 }

Is this a fools errand?

+2  A: 

well, the idea is cool, but i guess, you can't just go and jump to a nonexistent frame, and then add content (at least i did not succeed) ...

i have two ideas for you:

  • compile a sufficiently long empty clip (with enough frames to do whatever you like, or multiple ones, with lengths being power of 2 (so you'll have no more than 50% overhead)). spread your bitmaps over the frames, and then use MovieClip::addFrameScript, to add a gotoAndPlay(0) at the last frame.
  • create an swf at runtime, using a suitable library, as hxformat (ok, you'll need haXe for that, but that is something i'd suggest anyhow, if you really are into performance), and load it with Loader::loadBytes. i'm sure there are similar libraries around for as3 (you'll probably want to be using corelib's png encoder so you can embed the slices into the swf). best would be, to create a movieclip, containing the desired animation as an asset, so you load the clip once, grab the class and then instantiate (makes the operation synchronous).

of course, you could combine both, simply creating an empty clip at runtime, and then filling it up with bitmap data ...

apart from that, i'd have other suggestions:

  • put all slices into one sprite, and use DisplayObject::visible to do the trick.
  • do the conversion on the server from which you distribute the swf (maybe inject it using swfmill or so)
  • check out banana slice

i'd also note, that the methods you described using BitmapData::copyPixels or BitmapData::draw, if well implemented, usually do perform better at the cost of memory ... this is because flash player rendering, anti-aliasing, caching, clipping and many other things are not optimal at all ... there was a discusion about this here lately ... you might want to search for it ...

well then, good luck ... ;)


edit: well, i agree, that isn't really nice, although setting visibility comes nearly at no cost (as long as you don't force rendering just afterwards) ...

anyways ... the trick is to keep track of the last visible object, like so:

package  {
    import flash.display.*;
    import flash.events.Event;
    public class SliceBitmap extends Sprite {
     private var _currentFrame:uint = 0;
     private var _totalFrames:uint;
     private var _playing:Boolean;
     public function SliceBitmap(slices:Array) {
      for each (var slice:BitmapData in slices) 
       this.addChild(new Bitmap(slice)).visible = false;
      this.getChildAt(0).visible = true;
      this._totalFrames = slices.length;
      this.play();
     }
     private function onEnterFrame(e:Event):void {
      this.goto((this._currentFrame + 1) % this._totalFrames);
     }
     private function goto(frame:uint):void {
      if ((frame < 0) && (frame >= this._totalFrames)) throw "this should be handled";
      this.getChildAt(this._currentFrame).visible = false;
      this.getChildAt(this._currentFrame = frame).visible = true;
     } 
     public function play():void { this.playing = true; }
     public function stop():void { this.playing = false; }
     public function get playing():Boolean { return _playing; }
     public function set playing(value:Boolean):void {
      if (this._playing != value) {
       if (this._playing = value) 
        this.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, int.MAX_VALUE);
       else 
        this.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
      }
     }
     public function gotoAndPlay(frame:uint):void {
      this.goto(frame);
      this.play();
     }
     public function gotoAndStop(frame:uint):void {
      this.goto(frame);
      this.stop();
     }   
    }

}

and here's some code to test:

var slices:Array = [];
for (var i:int = 0; i < 20; i++) {
 var b:BitmapData = new BitmapData(200, 200, true);
 b.perlinNoise(4, 4, 4, i, true, false, 7, true);
 slices.push(b);
}
this.addChild(new SliceBitmap(slices);


back2dos
Thanks for the reply. I gave it a try and my approach was pretty much exactly what you recommended. Only problem is that addchild/removeChild are not limited to the keyframe the code executes on. So I ended having to use this beast (which was just as bad as Bitmapdata.copyPixels: (both onEnterFrame)public function onEnterFrame(e:Event):void { if(_bitmapDataVector==null || _bitmapDataVector.length<1){ return; } for(var i:int = 1; i <= numChildren; i++ ){ if( i != currentFrame){ getChildAt(i-1).visible = false; continue } getChildAt(i-1).visible = true; }}
ganepatti
Sorry: public function onEnterFrame(e:Event):void { if(_bitmapDataVector==null || _bitmapDataVector.length<1){ return; } for(var i:int = 1; i <= numChildren; i++ ){ if( i != currentFrame){ getChildAt(i-1).visible = false; continue } getChildAt(i-1).visible = true; } }
ganepatti