Vector Graphics at Runtime

0
273

Vector graphics scale well, and are therefore reusable and reduce production time, but they render slowly.

Scaling

Create your vector art at medium size and resize it on the fly as needed. This is a great technique for multiple-screen deployment. Let’s assume your original art was created for an 800×480 device and the application is detecting a tablet at 1,024×600, resulting in a scale difference of about 30%. Let’s scale up the art:

[code]

var dpi:int = Capabilities.screenDPI;
var screenX:int = Capabilities.screenResolutionX;
var screenY:int = Capabilities.screenResolutionY;
var diagonal:Number = Math.sqrt((screenX*screenX)+(screenY*screenY))/dpi;
// if diagonal is higher than 6, we will assume it is a tablet
if (diagonal >= 6) {
myVector.scaleX = myVector.scaleY = 1.30;
}

[/code]

cacheAsBitmap

If the object’s only transformation is along the axes, use cacheAsBitmap:

[code]

myVector.cacheAsBitmap = true;
this.addEventListener(Event.ENTER_FRAME, moveArt);
function moveArt(event:Event):void {
myVector.x += 1;
}

[/code]

cacheAsBitmapMatrix

To rotate, scale, or alpha the object, use cacheAsBitmapMatrix along with cacheAsBit map: Both are required for the caching to work.

[code]

import flash.geom.Matrix;
myVector.cacheAsBitmap();
myVector.cacheAsBitmapMatrix = new Matrix();
this.addEventListener(Event.ENTER_FRAME, onMoveArt);
function onMoveArt(event:Event):void {
myVector.x += 1;
myVector.rotation += 1;
}

[/code]

Vector to Bitmap

If the object is animated with filters or if multiple vector assets need to be composited, draw them into a bitmap. Here, if the device is a tablet (as determined by the diagonal size), we scale the art and apply a drop shadow filter. We then draw the vector art into a bitmap.

[code]

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.filters.DropShadowFilter;
if (diagonal >= 6) {
myVector.scaleX = myVector.scaleY = 1.30;
}
var shadow:DropShadowFilter = new DropShadowFilter();
shadow.distance = 5;
shadow.angle = 35;
myVector.filters = [shadow];
var bitmapData:BitmapData = new BitmapData(myVector.width, myVector.height);
bitmapData.draw(vector);
var bitmap:Bitmap = new Bitmap(bitmapData);

[/code]

Compositing Vector Graphics

A common use case for vector graphics is the creation of a customized avatar in a virtual world. Discrete parts, such as hair, shirt, and shoes, are separate assets. They can be changed and saved at runtime. The avatar, however, animates best as a whole, so once the avatar information is collected and all the discrete parts are loaded, they can be composited as one single object.

Use the BitmapData.draw method to render vector graphics into a bitmap. The first parameter, the only one required, is the source, bitmap, or vector. The following parameters are a Matrix, a ColorTransform, a blend mode, a Rectangle, and smoothing (only for the BitmapData source).

In this example, we are using the Matrix to scale the assets down to half their original size. Define the dimension of the BitmapData object used to store the pixels of the assets loaded and a Matrix:

[code]

import flash.display.BitmapData;
import flash.geom.Matrix;
const CELL_WIDTH:int = 50;
const CELL_HEIGHT:int = 100;
var composite:BitmapData = new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
var matrix:Matrix = new Matrix(0.50, 0, 0, 0.50, 0, 0);

[/code]

Create a Vector to store the path of the assets. The art must be loaded in the order it will be composited, as you would with layers in Flash or Photoshop:

[code]

var assets:Vector.<String> = new Vector.<String>;
assets[0] = PATH_SKIN_ASSET;
assets[1] = PATH_HAIR_ASSET;
assets[2] = PATH_SHIRT_ASSET;
var counter:int = 0;

[/code]

Load one image at a time using a Loader object. A counter variable is used to traverse the Vector and load all the assets. Every time one is available in AIR, it is converted to a bitmap and added to BitmapData. Note that the draw method has the alpha argument set to true to preserve the area of the vector without pixels so that it remains transparent:

[code]

import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
var loader:Loader = new Loader();
loader loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaded);
loading();
function loading():void {
loader.load(new URLRequest(assets[counter]));
}
function onLoaded(event:Event):void {
composite.draw(event.target.content, matrix);
counter++;
if (counter < assets.length) {
loading();
} else {
loader.contentLoaderInfo.removeEventListener(
Event.COMPLETE, onLoaded);
display();
}
}

[/code]

Once all the assets are loaded and composited, create a Bitmap to render the image to the screen:

[code]

import flash.display.Bitmap;
function display():void {
var bitmap:Bitmap = new Bitmap(composite);
addChild(bitmap);
}

[/code]

MovieClip with Multiple Frames

Art production may require the use of a movie clip with multiple frames. This is a familiar workflow for many designers while also having the flexibility of being resizable.

Converting a MovieClip with vector art to a bitmap for better rendering is a good practice, but neither cacheAsBitmap nor cacheAsBitmapMatrix works for a MovieClip with multiple frames. If you cache the art on the first frame, as the play head moves, the old bitmap is discarded and the new frame needs to be rasterized. This is the case even if the animation is just a rotation or a position transformation.

GPU rendering is not the technique to use for such a case. Instead, load your Movie Clip without adding it to the display list.

This time, we need to load a single .swf file and traverse its timeline to copy each frame. Load the external .swf file comprising 10 frames:

[code]

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest(PATH_TO_SWF));

[/code]

Create a rectangle for the cell at a predefined size. Traverse the MovieClip, and use draw, as before, to copy the vector into a bitmap. Then use copyPixels to add the new pixels into another BitmapData that is the width of the total number of frames:

[code]

function onLoadComplete(event:Event):void {
event.target.removeEventListener(Event.COMPLETE, onLoaded);
var mc:MovieClip = event.target.content as MovieClip;
var totalWidth:int = mc.totalFrames*CELL_WIDTH;
var complete:BitmapData =
new BitmapData(totalWidth, CELL_HEIGHT, true, 0);
var rectangle:Rectangle = new Rectangle(0, 0, CELL_WIDTH, CELL_HEIGHT);
var bounds:int = mc.totalFrames;
for (var i:int = 0; i < bounds; i++) {
mc.gotoAndStop(i+1);
var frame:BitmapData =
new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
frame.draw(mc, scaleMatrix);
complete.copyPixels(image, rectangle, new Point(i*CELL_WIDTH, 0));
}
frame.dispose();
loader.unloadAndStop(mc);
mc = null;

[/code]

Display the large BitmapData into a Bitmap to render it to the screen:

[code]

var bitmap:Bitmap = new Bitmap(complete);
bitmap.smoothing = true;
addChild(bitmap);

[/code]

Use the reverse process to display a single frame. Here we show the third frame:

[code]

var cellBitmap:BitmapData = new BitmapData(CELL_WIDTH, CELL_HEIGHT, true, 0);
var rectangle:Rectangle =
new Rectangle(CELL_WIDTH*3, 0, CELL_WIDTH+CELL_WIDTH*3, CELL_HEIGHT);
cellBitmap.copyPixels(complete, rectangle, new Point(0, 0));
var bitmap:Bitmap = new Bitmap(cellBitmap);
addChild(bitmap);

[/code]

Next we will cover creating an animation from a sprite sheet bitmap.