AS3 Quickie – drawWithQuality()

Since Flash Player 9, we’ve been able to use the BitmapData.draw() method in order to capture visual data from a display object. The major limitation of using this method, is that it will render the visual at the stage quality with which the swf has been embedded/compiled. One trick to get around this is to switch the stage quality on the fly, via ActionScript – yet this is not a supported workflow. With Flash Player 11.3 we have a new method with which to render drawn visuals at any desited stage quality: BitmapData.drawWithQuality().

In the example below, the swf is embedded with a stage quality of “medium”. Clicking on the top square will invoke the regular draw() command which uses the default stage quality to render the data. The second square will employ drawWithQuality() and render with the stage quality set to “low”, while the bottom square will do the same – but use stage quality “best” to render the image. Essentially; medium, low, and best – from top to bottom.


Requires Flash Player 11.3 or above!

Go ahead and toggle between each quality setting to see the differences. I've rendered the resulting BitmapData scaled nearly 2x the original size and with a white background to further accentuate the quality differences in the image. Also, a variety of display elements exist within the MovieClip symbol itself for further illustration: shapes, images, filters, et cetera.

To accomplish this, we must first perform the necessary imports:

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.StageQuality;

We need to declare a Bitmap instance to display the BitmapData gathered from the stage using either the draw() or drawWithQuality() methods of the BitmapData class:

private var savaBitmap:Bitmap;

..and add it to the display list:

savaBitmap = new Bitmap();
addChild(savaBitmap);

With the Bitmap instance in place and a MovieClip instance already on stage- we simply need to craete a new BitmapData instance to hold our data, invoke drawWithQuality() using the desired stage quality, and then apply that data to our existing Bitmap on stage:

var qualityBitmapData:BitmapData = new BitmapData(savanovic.width, savanovic.height);
qualityBitmapData.drawWithQuality(savanovic, null, null, null, null, false, StageQuality.BEST);
savaBitmap.bitmapData = qualityBitmapData;

We can substitute any stage quality we wish in place of that last argument. Here is a quick list of those which are available through flash.display.StageQuality:

  • StageQuality.BEST
  • StageQuality.HIGH
  • StageQuality.HIGH_16X16
  • StageQuality.HIGH_16X16_LINEAR
  • StageQuality.HIGH_8X8
  • StageQuality.HIGH_8X8_LINEAR
  • StageQuality.MEDIUM
  • StageQuality.LOW

The full code is below. Note that the FLA contains upon the Stage a MovieClip with the instance ID of savanovic from which we are gathering the visual data:

package  {
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.StageQuality;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.geom.Matrix;

	public class Main extends Sprite {
		public var savanovic:MovieClip;

		private var savaBitmap:Bitmap;
		private var normalBitmapData:BitmapData;
		private var yuckyBitmapData:BitmapData;
		private var qualityBitmapData:BitmapData;

		public function Main() {
			this.addEventListener(Event.ADDED_TO_STAGE, init);
		}

		protected function init(e:Event):void {
			savaBitmap = new Bitmap();
			savaBitmap.x = 45;
			savaBitmap.y = 10;
			addChild(savaBitmap);

			drawData();
		}

		protected function drawData():void {
			var scaleFactor:Number = 1.7;
			var savaMatrix:Matrix = new Matrix();
			savaMatrix.scale(scaleFactor, scaleFactor);

			normalBitmapData = new BitmapData(savanovic.width*scaleFactor, savanovic.height*scaleFactor);
			normalBitmapData.draw(savanovic,savaMatrix);
			yuckyBitmapData = new BitmapData(savanovic.width*scaleFactor, savanovic.height*scaleFactor);
			yuckyBitmapData.drawWithQuality(savanovic, savaMatrix, null, null, null, false, StageQuality.LOW);
			qualityBitmapData = new BitmapData(savanovic.width*scaleFactor, savanovic.height*scaleFactor);
			qualityBitmapData.drawWithQuality(savanovic, savaMatrix, null, null, null, false, StageQuality.BEST);

			renderButtons();
		}

		protected function renderButtons():void {
			var b1:Sprite = new Sprite();
			b1.graphics.beginFill(0x000000, 0.7);
			b1.graphics.drawRect(0,0,25,25);
			b1.graphics.endFill();
			b1.addEventListener(MouseEvent.CLICK, drawNormalPixels);
			b1.x = 10;
			b1.y = 10;
			addChild(b1);

			var b2:Sprite = new Sprite();
			b2.graphics.beginFill(0x000000, 0.7);
			b2.graphics.drawRect(0,0,25,25);
			b2.graphics.endFill();
			b2.addEventListener(MouseEvent.CLICK, drawYuckyPixels);
			b2.x = 10;
			b2.y = b1.y + b1.height + 10;
			addChild(b2);

			var b3:Sprite = new Sprite();
			b3.graphics.beginFill(0x000000, 0.7);
			b3.graphics.drawRect(0,0,25,25);
			b3.graphics.endFill();
			b3.addEventListener(MouseEvent.CLICK, drawQualityPixels);
			b3.x = 10;
			b3.y = b2.y + b2.height + 10;
			addChild(b3);
		}

		protected function drawNormalPixels(e:MouseEvent):void {
			savaBitmap.bitmapData = normalBitmapData;
		}

		protected function drawYuckyPixels(e:MouseEvent):void {
			savaBitmap.bitmapData = yuckyBitmapData;
		}

		protected function drawQualityPixels(e:MouseEvent):void {
			savaBitmap.bitmapData = qualityBitmapData;
		}

	}

}

Want the full package? Download below:
Packaged code and Flash Professional CS6 project



This Quickie has been brought to you by the rampaging Serbian vampire Sava Savanovic. Let's hope they fix his watermill soon so that he can get some rest!

AS3 Quickie – Event.VIDEO_FRAME and Camera.drawToBitmapData()

In the past, if we wanted to copy some data from a camera object to a bitmap, we would need to draw the data from the display object using flash.display.BitmapData.draw() and then manipulate it in some way. This is problematic at times… if there is no way of knowing whether we have valid bitmap data to draw from!

Using Flash Player 11.4, we have a number of alternatives to this workflow which allow us to both listen for an event to fire once a frame is available to have its data harvested, and a number of methods from retrieving the frame data from the camera object in order to manipulate it.

The example below demonstrates this through the capture of bitmap data through a Camera object. This data is then sent directly to a Bitmap object on the display list, enabling a sort of picture-in-picture view of the camera feed.

If you have a camera attached to whatever you are using to read this, try out the demo below. You will need to be sure an allow camera and microphone access for Flash Player.


Requires Flash Player 11.4 or above!

To get this working only takes a few steps in your ActionScript code. The main classes we need to work with are flash.events.Event to listen for a video frame event, and flash.media.Camera to grab a local camera object to attach the event listener to. For any of this to work, you must target Flash Player 11.4 or above.

Now... this tutorial SORT OF builds upon the Quickie which demos how to attach a Camera to StageVideo - so we won't be covering those bits, exactly... have a look at that demo if you need a refresher for this area.

In ActionScript, we must perform the necessary imports:

import flash.events.Event;
import flash.media.Camera;
import flash.display.Bitmap;
import flash.display.BitmapData;

The first thing we do is to assign an event listener to our Camera instance. To do this, it is safest to wait until our object has been added to the stage, and then add an event listener to check for flash.events.Event.VIDEO_FRAME.

camera = Camera.getCamera();
camera.setMode(512, 384, 24);
camera.addEventListener(Event.VIDEO_FRAME, onVideoFrame);

The second bit is to listen for a flash.events.Event.VIDEO_FRAME to fire; at which time we can feel safe that the camera bitmap data is available for us to use in some way. In this example, we are invoking Camera.drawToBitmapData() to render the video frame to a bitmap object on the stage. We could alternatively use Camera.copyToByteArray() or Camera.copyToVector() as well!

protected function onVideoFrame(e:Event):void {
	camera.drawToBitmapData(bmd);
}

The full code is below. Note that the FLA contains upon the Stage a TextField with the instance ID of statusField:

package  {
	import flash.display.MovieClip;
	import flash.text.TextField;
	import flash.media.StageVideo;
	import flash.media.StageVideoAvailability;
	import flash.media.Camera;
	import flash.events.Event;
	import flash.events.StageVideoAvailabilityEvent;
	import flash.events.StageVideoEvent;
	import flash.geom.Rectangle;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	
	public class Main extends MovieClip {
		
		public var statusField:TextField;
		
		private var camera:Camera;
		private var stageVideo:StageVideo;
		private var bmd:BitmapData;
		private var bm:Bitmap;
		
		public function Main() {
			this.addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		protected function init(e:Event):void {
			stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, availabilityChanged);
		}
		
		protected function availabilityChanged(e:StageVideoAvailabilityEvent):void {
			statusField.appendText("StageVideo => " + e.availability + "\n");
			if(e.availability == StageVideoAvailability.AVAILABLE){
				stageVideo = stage.stageVideos[0];
				bmd = new BitmapData(512, 384);
				bm = new Bitmap(bmd);
				bm.scaleX = 0.25;
				bm.scaleY = 0.25;
				bm.x = stage.stageWidth-bm.width;
				bm.y = stage.stageHeight-bm.height;
				addChild(bm);
				attachCamera();
			}
		}
		
		protected function attachCamera():void {
			statusField.appendText("Camera.isSupported => " + Camera.isSupported + "\n");
			if(Camera.isSupported){
				camera = Camera.getCamera();
				camera.setMode(512, 384, 24);
				camera.addEventListener(Event.VIDEO_FRAME, onVideoFrame);
				stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, onRenderState);
				stageVideo.attachCamera(camera);
			}
		}
		
		protected function onRenderState(e:StageVideoEvent):void {
			stageVideo.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
		}
		
		protected function onVideoFrame(e:Event):void {
			camera.drawToBitmapData(bmd);
		}
		
	}
	
}

Want the full package? Download below:
Packaged code and Flash Professional CS6 project

AS3 Quickie – Attach a Camera to StageVideo

In the past, we’ve been able to attach a local camera to Video display objects within Flash Player with relative ease. The flash.media.Video object, of course, is part of the traditional display list and is not accelerated by the system GPU whatsoever. Using the newer flash.media.StageVideo object, we can implement a GPU-accelerated video display beneath the Flash display list… but previous to Flash Player 11.4 we haven’t been able to attach a camera to StageVideo as we could with Video. Thankfully, now we can!

If you have a camera attached to whatever you are using to read this, try out the demo below. You will need to be sure an allow camera and microphone access for Flash Player. Trust me – I’m not capturing any data through this :)


Requires Flash Player 11.4 or above!

To get this working only takes a few steps in your ActionScript code. The main classes we need to work with are flash.media.StageVideo for the GPU-accelerated video, and flash.media.Camera to grab a local camera object to feed an image to our video. For any of this to work, you must target Flash Player 11.4 or above.

In ActionScript, we must perform the necessary imports (and there are a few!):

import flash.media.StageVideo;
import flash.media.StageVideoAvailability;
import flash.media.Camera;
import flash.events.StageVideoAvailabilityEvent;
import flash.events.StageVideoEvent;
import flash.geom.Rectangle;

The first thing we do is to set up our StageVideo instance. To do this, it is safest to wait until our class has been added to the Stage, and then add an event listener to check for flash.events.StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY.

stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, availabilityChanged);

Once the StageVideo availability changes, we can then set out StageVideo instance to stage.stageVideos[0], assigning a reference to a variable in ActionScript.

stageVideo = stage.stageVideos[0];

Now we need to grab a flash.media.Camera object and attach that Camera to our StageVideo.

camera = Camera.getCamera();
stageVideo.attachCamera(camera);

The final task is to listen for a flash.events.StageVideoEvent.RENDER_STATE to fire, at which time we can properly size our StageVideo. We absolutely need to do this since StageVideo will initially have a width and height of 0.

stageVideo.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);

The full code is below. Note that the FLA contains upon the Stage a TextField with the instance ID of statusField:

package  {
	import flash.display.MovieClip;
	import flash.text.TextField;
	import flash.media.StageVideo;
	import flash.media.StageVideoAvailability;
	import flash.media.Camera;
	import flash.events.Event;
	import flash.events.StageVideoAvailabilityEvent;
	import flash.events.StageVideoEvent;
	import flash.geom.Rectangle;
	
	public class Main extends MovieClip {
		
		public var statusField:TextField;
		
		private var camera:Camera;
		private var stageVideo:StageVideo;
		
		public function Main() {
			this.addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		
		protected function init(e:Event):void {
			stage.addEventListener(StageVideoAvailabilityEvent.STAGE_VIDEO_AVAILABILITY, availabilityChanged);
		}
		
		protected function availabilityChanged(e:StageVideoAvailabilityEvent):void {
			statusField.appendText("StageVideo => " + e.availability + "\n");
			if(e.availability == StageVideoAvailability.AVAILABLE){
				stageVideo = stage.stageVideos[0];
				attachCamera();
			}
		}
		
		protected function attachCamera():void {
			statusField.appendText("Camera.isSupported => " + Camera.isSupported + "\n");
			if(Camera.isSupported){
				camera = Camera.getCamera();
				stageVideo.addEventListener(StageVideoEvent.RENDER_STATE, onRenderState);
				stageVideo.attachCamera(camera);
			}
		}
		
		protected function onRenderState(e:StageVideoEvent):void {
			stageVideo.viewPort = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
		}
		
	}
	
}

Want the full package? Download below:
Packaged code and Flash Professional CS6 project

AS3 Quickie – Frame Label Events

With Flash Player 11.3 and above, the ability to listen for a frame label event is now included in the runtimes. For example; if you have a MovieClip symbol with a certain set of animation within it, and you have a number of frame labels defined upon the symbol’s timeline… you can now listen for these particular frames through their individual label events and then respond to them in some way.


Requires Flash Player 11.3 or above!

To get this working only takes a few steps in your ActionScript code. For each frame label you wish to respond to, you must create a new flash.display.FrameLabel object and add an event listener to it. In order to utilize Event.FRAME_LABEL you must target Flash Player 11.3 or above.

You'll (of course) need to define some frame labels across the timeline. Here, we've established 4 frame labels within a MovieClip symbol:

In ActionScript, we must perform the necessary imports:

import flash.events.Event;
import flash.display.FrameLabel;

We then need to assign an event listener to each frame label we wish to respond to. In order to accomplish this, we read all of the symbol frame labels into an array in order to dynamically create flash.display.FrameLabel objects which have event listeners attached to them, listening specifically for Event.FRAME_LABEL:

labelsArray = angyFace.currentLabels;
for(var i:uint=0; i<labelsArray.length; i++){
	var frameLabel:FrameLabel = labelsArray[i];
	frameLabel.addEventListener(Event.FRAME_LABEL, onFrameLabel);
}

And follow through with a method to handle the event, itself:

private function onFrameLabel(e:Event):void {
	//do something
}

Of course, depending upon our requirements, we may want to register each frame label object specifically in order to manage it appropriately. For simplicity - this example project has no cleanup routine associated with it.

The full code is below. Note that the FLA contains upon the Stage a TextField with the instance ID of statusField and a MovieClip symbol instace called angryFace which contains the 4 frame labels we are registering:

package  {
	import flash.display.MovieClip;
	import flash.display.FrameLabel;
	import flash.text.TextField;
	import flash.events.Event;
	
	public class Main extends MovieClip {
		public var statusField:TextField;
		public var angyFace:MovieClip;
		
		private var labelsArray:Array;
		
		public function Main() {
			this.addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event):void {
			labelsArray = angyFace.currentLabels;
			for(var i:uint=0; i<labelsArray.length; i++){
				var frameLabel:FrameLabel = labelsArray[i];
				frameLabel.addEventListener(Event.FRAME_LABEL, onFrameLabel);
			}
		}
		
		private function onFrameLabel(e:Event):void {
			statusField.appendText("\n" + e.target.frame + ": " + e.target.name);
		}
		
	}
	
}

Want the full package? Download below:
Packaged code and Flash Professional CS6 project

AS3 Quickie: Check if a variable is Numeric

I’m still doing loads of ActionScript work in both my regular job and in side projects – so thought I might start posting some tiny, useful snippets of AS3 here as I encounter them. This is, I suppose, both for my own archive and for anyone out in the community that cares to pick up on these bits and pieces.

So this quickie comes from a project where I actually have an AIR application running as a service on a Windows 2008 virtual machine. The application monitors a remote folder for uploads from a web application in order to process these uploaded files for distribution.

The files themselves are renamed, upon upload, to a numeric value representing the ID of the object they are associated with in the web application. The server-based AIR app uses this ID to communicate with both the web application and an additional, remote Flash Media Server instance.

The problem is that, sometimes, when the file is uploaded… previous to it being renamed by ColdFusion… it will get snatched up by this AIR app and so the file name is whatever the user named the file previous to upload – not the expected system designated ID. So what’s the solution? Check to see whether the file name is truly numeric or not. Granted – if the user creates a file for upload which is numeric, it will get around this code… the chance of that happening is quite slim though, and even if it does happen, can be dealt with in other ways down the road.

var fileName:String = "2739.mp4";
var extIndex:int = fileName.indexOf(".");
var fileID:Number = Number(fileName.substr(0, extIndex));
if(!isNaN(fileID)){
	trace("Ok! Process file.");
}else{
	trace("Wait for next cycle...");
}