Flash Player 10 Drawing API

Introduction

Flash Player 10 expands greatly on the drawing API in ActionScript, more so than any other version of the Flash Player since the initial introduction of the drawing API in Flash Player 6. New features include:

  • Support for Pixel Bender (Hydra) filters
  • Use of vectors (typed arrays) for improved throughput and use of memory
  • Support for non-zero winding rules
  • An API for drawing triangles with support for 3D perspective rendering
  • Drawing API data objects

Though the older APIs still exist, and to much of an extent work with the newer APIs, these new enhancements mostly render those old APIs obsolete. This is especially the case with the IGraphicsData objects which serve as data representations of all the available APIs.

Note: This is not an official reference

The contents of this tutorial do not represent an official reference to the APIs in Flash Player 10. Those APIs are subject to change prior to a final Flash Player 10 public release.

New Graphics Methods

Drawing APIs are located within the Graphics class. Display objects which support the drawing APIs contain a Graphics instance from which these APIs are called. As of Flash Player 9, these APIs include:

  • beginBitmapFill()
  • beginFill()
  • beginGradientFill()
  • clear()
  • curveTo()
  • drawCircle()
  • drawEllipse()
  • drawRect()
  • drawRoundRect()
  • endFill()
  • lineGradientStyle()
  • lineStyle()
  • lineTo()
  • moveTo()

These remain active and unchanged. Flash Player 10 introduces the following new Graphics APIs:

  • beginShaderFill()
  • drawGraphicsData()
  • drawPath()
  • drawTriangles()
  • lineBitmapStyle()
  • lineShaderStyle()

Of the new APIs, lineBitmapStyle sticks out a little as it fills a hole in the older API set for strokes.

public function lineBitmapStyle(bitmap:BitmapData, matrix:Matrix=null, repeat:Boolean=true, smooth:Boolean=false);

The beginShaderFill and lineShaderStyle APIs are similar, but these utilize a new feature in Flash Player 10, support for Pixel Bender (code named Hydra) filters.

Shader Strokes and Fills

Pixel Bender support is another amazing feature of Flash Player 10. Pixel Bender (code named Hydra), if you're not familiar with it, is a programming language for image processing. Flash Player 10 will be able to use Pixel Bender for filters, blend modes, and not surprisingly, styles for the drawing API.

For the drawing API, there will be two new Graphics class methods, beginShaderFill and lineShaderStyle.

public function lineShaderStyle(shader:Shader=null, matrix:Matrix=null);
public function beginShaderFill(shader:Shader=null, matrix:Matrix=null);

The new Shader class is a container for hosting and working with Pixel Bender bytecode. Pixel Bender bytecode (.pbj) is compiled from Pixel Bender source code (.pbk). You write Pixel Bender source code and compile it into bytecode using the Pixel Bender Toolkit. Once you have bytecode, it needs to be loaded into the player and supplied to a Shader instance. That instance can then be used as a display object filter, a blend mode, or be used with the new shader drawing APIs.

When working with the drawing APIs, you'll need to be sure to supply an input image (or input images, as it's possible to have more than one) to your shader directly. This is not the case with filters since they use the target display object as the input. From there, additional values can be set as determined by the definition of the Pixel Bender file.

For an example I will use a slightly modified version of Petri Leskinen's Kaleidoscope Pixel Bender filter (retrieved from Adobe Labs Pixel Bender Gallery). You can check out the source here. It defines one input with the identifier src, and five parameters, fadeColor, position, size, fade, and angle. In ActionScript these are defined and modified through the shader's data property. Since filters created in Pixel Bender can define any properties they wish, these values will change with each filter. The data property in the a Shader instance will change to match those properties.

Because the shader (Pixel Bender) bytecode is located in a separate file, it will have to be loaded before used. RoseImage is an BitmapData image in the library.

// image for shader fill
var bitmapData:BitmapData = new RoseImage(0,0);
// shader fill shader; it will use
// the image as an input
var shader:Shader = new Shader();
// loader to load pixel bender bytecode 
// for shader instance
var shaderLoader:URLLoader = new URLLoader();
shaderLoader.dataFormat = URLLoaderDataFormat.BINARY;
shaderLoader.addEventListener(Event.COMPLETE, shaderLoaded);
shaderLoader.load(new URLRequest("kaleidoscope.pbj"));
// shaderLoader complete handler
function shaderLoaded(event:Event):void {
try {
// set up shader
shader.byteCode = shaderLoader.data; // error if invalid
shader.data.src.input = bitmapData; // image input
shader.data.size.value = [100]; // constant size
shader.data.fade.value = [.5]; // constant fade value
// draw the shader every frame
addEventListener(Event.ENTER_FRAME, drawKaleidoscope);
}catch(error:Error){}
}
// enterframe handler
function drawKaleidoscope(event:Event):void {
// base position on mouse location
shader.data.position.value = [mouseX, mouseY];
// rotate constantly over time
shader.data.angle.value = [getTimer()/500];
// draw a rectangle with the shader fill
graphics.clear();
graphics.beginShaderFill(shader);
graphics.drawRect(0,0,300,250);
}


Preview (Requires Flash Player 10)

 

As the movie plays, the kaleidoscope effect is constantly being redrawn with new parameters based on time and the position of the mouse. Amazingly this is all happening in real time and performs well even without GPU acceleration.

Drawing Paths Through Vectors

Another new feature to ActionScript is typed arrays, or vectors. These are not to be confused with the vectors used to describe vector-based (non-bitmap) drawings. Here, vector refers to a kind of an array that can only store one type of value. Those familiar with C++ or Java may recognize the name. To others, it may just sound out of place. Rest assured, it's an established term.

Vectors are almost exactly like arrays at their core, having pretty much the same API. There are only a few real differences. These include:

  • Elements within a vector all share the same type (vectors instances define those types using the syntax Vector.<Type>)
  • Vectors have an additional property, fixed, which determines whether or not the length of the vector can change dynamically
  • The vector constructor allows for two optional arguments, length and fixed; vector values cannot be defined in the constructor
  • There is no equivalent to the bracket ([]) array constructor shorthand for vectors

Additionally, there may be one or two array-specific APIs in ActionScript that do not carry over into vector instances. I think currently, that is limited to sortOn. For all other events and purposes, vectors are just like arrays.

// creating a vector that contains int elements
var primeNumbers:Vector.<int> = new Vector.<int>(4, true);
primeNumbers[0] = 2;
primeNumbers[1] = 3;
primeNumbers[2] = 5;
primeNumbers[3] = 7;

So why use vectors? Given that vectors are dense, typed arrays, they offer a performance advantage over arrays - this especially for primitive values such as Numbers and ints. A few methods of the new drawing API such as drawPath and drawTriangles use vectors for this very reason.

drawPath

The drawPath method is the new drawing API workhorse for defining shapes and other path.

public function drawPath(commands:Vector.<int>, data:Vector.<Number>, winding:String="evenOdd");

It consolidates moveTo, lineTo, and curveTo into a single method that can handle each of those operations with a single call. Instead of each of those being called separately, they are abstracted into numeric identifiers. For example, a moveTo operation is signified by a 1, while a lineTo operation is a 2, etc. Those values are then stored in a commands Vector.<int>. Each command corresponds to position values stored into a data Vector.<Number> where two consecutive numbers define a point in the target coordinate space. Note that these are not Point objects; the vector is simply a series of numbers where each group of two numbers represents a point value. When both the command and data vectors are supplied to drawPath, each command is matched up with their respective point values (a collection of 2 or 4 numbers) to generate a path in the Graphics object from which drawPath was called.

The command values used by drawPath are stored in an enumeration class called GraphicsPathCommand. Its values are defined as:

public static const NO_OP:int = 0;
public static const MOVE_TO:int = 1;
public static const LINE_TO:int = 2;
public static const CURVE_TO:int = 3;
public static const WIDE_MOVE_TO = 4;
public static const WIDE_LINE_TO:int = 5;

The WIDE variants of moveTo and lineTo use an extra set of coordinates, relying on 4 number values instead of the standard 2. Because of this, it can be easy to quickly change a moveTo or lineTo command into a curveTo without having to add additional values to the data vector. The extra coordinate used by curveTo would already be there, just ignored by the wide moveTo and wide lineTo operations.

Example:

var commands:Vector.<int> = new Vector.<int>(5, true);
commands[0] = GraphicsPathCommand.MOVE_TO;
commands[1] = GraphicsPathCommand.LINE_TO;
commands[2] = GraphicsPathCommand.LINE_TO;
commands[3] = GraphicsPathCommand.LINE_TO;
commands[4] = GraphicsPathCommand.LINE_TO;
var data:Vector.<Number> = new Vector.<Number>(10, true);
data[0] = 10; // x
data[1] = 10; // y
data[2] = 100;
data[3] = 10;
data[4] = 100;
data[5] = 100;
data[6] = 10;
data[7] = 100;
data[8] = 10;
data[9] = 10;
graphics.beginFill(0x800000);
graphics.drawPath(commands, data);

Notice that there are twice as many data values as there are commands. For non-curveTo and non-wide commands, commands.length = data.length/2. When using curveTo and wide commands, commands.length = data.length/4. Combinations of each could result in different ratios, but commands.length should always be an even value.

I know what you're thinking, that's a lot of code just for a square. Keep in mind that once you have those commands and data vectors, they can easily be reused again. You'll see this even more with the IGraphicsData objects.

That's not to say that this code cannot be reduced; it can. You can use a couple of shortcuts such as starting with vectors without a fixed length and adding all values with a single push call, or using the a vector conversion function to convert an array to a vector directly within the drawPath call. Here is the same code condensed:

graphics.beginFill(0x800000);
graphics.drawPath(
Vector.<int>([1,2,2,2,2]),
Vector.<Number>([10,10, 100,10, 100,100, 10,100, 10,10]));

winding

The third parameter in drawPath, not used here, is the winding parameter. This is a string that specifies the winding or fill rule for intersecting or overlapping paths. Its values are defined in the GraphicsPathWinding enumeration class:

public static const EVEN_ODD:String = "evenOdd";
public static const NON_ZERO:String = "nonZero";

Winding refers to the direction in which a path is drawn. When paths are drawn clockwise, they're considered positively wound. Paths drawn counter-clockwise are considered negatively wound.

Even-odd is the standard fill rule and is the rule used by all of the original drawing APIs. This is also the default for drawPath. The alternate rule is the non-zero rule.

With even-odd, any overlapping paths will alternate between open and closed fills. If two squares drawn with the same fill intersect, the area in which they intersection occurs will not be filled. No adjacent areas will be either both filled or both unfilled. Unlike the non-zero rule, winding with the even-odd rule is inconsequential.

The non-zero rule, on the other hand, depends on winding to determine whether or not areas defined by intersecting paths are filled. When paths of opposite winding intersect, the area defined is unfilled, much like with even-odd. For paths of the same winding, the area that would be unfilled gets filled.

The names even-odd and non-zero refer to a more specific rule that defines just how these fills are handled. Positively wound paths are given a weight of +1; negatively wound paths are given a weight of -1. Given a point within an enclosed area of a shape defined by one or multiple paths, when an arbitrary line is drawn from that point extending on into indefinitely, the number of times that line crosses a path, and the combined weights of those paths are used to determine the fill. For even-odd, the count is used. When the count is odd, the area is filled. For even counts, the area is unfilled. Non-zero uses the weights. When the combined weights are not 0, the area is filled. For 0 weights, the area is unfilled.

It may seem a little over-complicated to have these fill rules, but there are situations where, when understood, they can be of use. For example, consider drawing a star shape. With the standard even-odd rule, that shape would need to be drawn using 10 different lines. With non-zero, that can be reduced to just 5.

Here is that star in ActionScript using drawPath with 5 lines and a non-zero fill.

graphics.beginFill(0x60A0FF);
graphics.drawPath(
Vector.<int>([1,2,2,2,2]),
Vector.<Number>([66,10, 23,127, 122,50, 10,49, 109,127]),
GraphicsPathWinding.NON_ZERO);

So far the shapes used in these last couple of examples have been fairly simple and could have, for the most part, just as easily been created with the original drawing APIs. As you work with move complicated paths, the advantages of the new APIs (namely drawPath) will be much more apparent, especially in terms of performance.

With drawPath, you're avoiding much of the function overhead that results from multiple, consecutive calls to lineTo, curveTo, etc. Now, a complicated path can be drawn with a single method call. Additionally, the use of vectors streamlines the data being sent to the renderer. In fact, a new pipeline is being used to handle the drawing data more efficiently and accurately than before. That means drawPath should not only work faster, but also be more precise than the path drawn from lineTo and curveTo. These enhancements can go a long way as you begin to work with more complicated, dynamic drawings in ActionScript.

Drawing Triangles

Along with the new drawPath method comes another addition to the Graphics class, the drawTriangles method.

public function drawTriangles(vertices:Vector.<Number>, indices:Vector.<int>=null, uvtData:Vector.<Number>=null, culling:String="none");

This method is somewhat similar to drawPath in that it too uses a Vector.<Number> to specify point locations for a path to be drawn, only with drawTriangles, there is no commands list. The commands are predefined to use the point locations to draw triangles. Every 3 points (6 numbers) represents a triangle path to be drawn.

Though I'm sure there are a lot of people who enjoy drawing a bunch of random triangles on the screen, the real purpose for the drawTriangles method is facilitate 3D rendering through ActionScript. Though Flash and ActionScript still does not directly support 3D per se, the drawing API can be used to take a 3D model stored in memory and draw it to the screen in Flash as 2D. Because 3D models (in the end) are represented by a collection of triangles in space, drawTriangles can be used to quickly make that translation from 3D to 2D. How does it do this?

  • All data points (vertices) inherently define triangle paths (no commands necessary)
  • Indices can be set to allow for the reuse of vertices reducing redundant data
  • UV texture mapping can be applied to each vertex
  • Perspective bitmap distortion can be applied to bitmap textures (T in UVT)

For the most part, the advantages of drawTriangles only show when drawing textured meshes, notably with a 3D perspective. And that is really the big feature with drawTriangles, use of the new perspective renderer in Flash Player 10. This lets you take a bitmap and distort it in a way that makes it appear in 3D.

This may not exactly be entirely new to Flash. You've probably seen images like this appear in 3D using Flash before. Those examples, however, had to break up an image into many smaller pieces (triangles, in fact) in order to get that effect, and the quality wasn't always so great - often showing jagged straight edges or distortions in the image because of the affine transformations used to make it appear as though a perspective transform was being applied. Now, perspective handling can all be done through Flash Player with as little as one triangle drawn through drawTriangles (though it is more common to use 2 triangles to construct a single rectangular plane).

drawTriangles

The first parameter of drawTriangles is the only required parameter, the vertices parameter. This is a vector of numbers defining the points from which your triangles are drawn. Without using indices, the length of the vector used should always be a factor of 6 since each triangle requires 3 points (3 sets of two x,y values).

The following example draws two triangles:

graphics.beginFill(0xFF8000);
graphics.drawTriangles(
Vector.<Number>([
10,10,  100,10,  10,100,
110,10, 110,100, 20,100]));

Neither of these triangles share any points, but if they did, the second drawTriangles parameter, indices, could be used to reuse values in the vertices vector for more than one triangle.

One thing to be careful with when dealing with indices is that the indices they represent are point indices, not indices that relate directly to the vertices elements. In other words, an index in the vertices vector as defined by indices is actually the real index divided 2. For the third point of a vertices vector, for example, an indices index of 2 would be used, even though the first numeric value of that point starts at the vector index of 4.

Merging the triangles in the example above to share the diagonal edge, a new example can be developed to utilize indices:

graphics.beginFill(0xFF8000);
graphics.drawTriangles(
Vector.<Number>([10,10, 100,10, 10,100, 100,100]),
Vector.<int>([0,1,2, 1,3,2]));

Notice that though a square has now been drawn using two triangles, only 4 points were specified in the vertices vector. Using indices, the two points shared by the two triangles are reused for each triangle reducing the overall vertices count from 6 (12 numbers) to 4 (8 numbers).

This becomes particularly conservative when dealing with larger triangle meshes where most points are shared by multiple triangles.

Once you start working with textures, you'll want to make use of the uvtData parameter of drawTriangles. This parameter allows you to set up UV mapping for bitmap fills.

UV mapping is a method for texturing objects. It relies on two values, a U horizontal (x) and a V vertical (y). Rather than being based on pixel values, they're based on percentages, where 0 U and 0 V is the top left of an image and 1 U and 1 V is the bottom right.

Vectors of a triangle can be given UV coordinates to associate themselves with the respective locations on an image.

  • Point A UV: (.25, .33)
  • Point B UV: (.25, .66)
  • Point C UV: (1, .66)

As the triangle is transformed in space, the image is mapped to the triangle based on those coordinates.

You can imagine how well this approach will work with 3D models drawn in Flash. No longer will complicated matrix calculations be required to transform a bitmap to a pseudo-3D plane. Now UV values can be set once and work consistently throughout the lifetime of a model.

As with vertices, UV coordinates are stored in a Vector.<Number> vector named uvtData (the T value will be covered a little later). Each UV value relates directly to the corresponding vertex in the vertices vector at that same index. Taking the previous triangle example, we can now map a bitmap to the square using UV values. To mix things up a little, the bottom right corner can be extended to the bottom right to show how the UV values react. BridgeImage here is a BitmapData image in the library.

graphics.beginBitmapFill(new BridgeImage(0,0));
graphics.drawTriangles(
Vector.<Number>([10,10, 100,10, 10,100, 150,150]),
Vector.<int>([0,1,2, 1,3,2]),
Vector.<Number>([0,0, 1,0, 0,1, 1,1]));

At this point, there is no 3D perspective rendering being applied to the texture. All image manipulation is being done in 2D. When you want to provide a 3D perspective to a bitmap, you need to include a T value with the UV coordinates in uvtData.

The T value in uvtData represents the 3D perspective, or more specifically, the scale factor of the associated vertex. For example, if an object is positioned in 3D space off in the distance so that it is 50% it's "original" size, it's T value would be .5. As triangles are drawn to represent objects in 3D space, their locations along the z axis determine their T value. The magic equation that specifically determines this is:

T = focalLength/(focalLength + z);

Where focalLength represents a focal length or calculated "screen" location which dictates the amount of perspective provided in the view.

The value of T here can be used to scale basic shapes to make them seem further in the distance and is usually the value used to convert 3D points to 2D points. In the case of UVT, it is also used to scale a bitmap between the points within a triangle with perspective.

When defining UVT values, the T value directly follows the UV values defined for a vertex. With the inclusion of T, every 3 values in uvtData (U, V, and T) match up with every 2 values in vertices (x, and y). With just UV, uvtData.length == vertices.length. The inclusion of of T means uvtData.length = 1.5*vertices.length.

Taking a step up in complexity, the following example shows a plane being rotated in 3D space using UVT data.

// plane vertex coordinates (and t values)
var x1:Number = -100,	y1:Number = -100,	z1:Number = 0,	t1:Number = 0;
var x2:Number = 100,	y2:Number = -100,	z2:Number = 0,	t2:Number = 0;
var x3:Number = 100,	y3:Number = 100,	z3:Number = 0,	t3:Number = 0;
var x4:Number = -100,	y4:Number = 100,	z4:Number = 0,	t4:Number = 0;
var focalLength:Number = 200; // focal length
var bitmapData:BitmapData = new BridgeImage(0,0); // texture
// 2 triangles for 1 plane, indices will always be the same
var indices:Vector.<int> = new Vector.<int>();
indices.push(0,1,3, 1,2,3);
var container:Sprite = new Sprite(); // container to draw triangles in
container.x = 200;
container.y = 200;
addChild(container);
function rotatePlane(event:Event):void {
// rotate vertices over time
var ticker = getTimer()/400;
z2 = z3 = -(z1 = z4 = 100*Math.sin(ticker));
x2 = x3 = -(x1 = x4 = 100*Math.cos(ticker));
// calculate t values
t1 = focalLength/(focalLength + z1);
t2 = focalLength/(focalLength + z2);
t3 = focalLength/(focalLength + z3);
t4 = focalLength/(focalLength + z4);
// determine traingle vertices based on t values
var vertices:Vector.<Number> = new Vector.<Number>();
vertices.push(x1*t1,y1*t1, x2*t2,y2*t2, x3*t3,y3*t3, x4*t4,y4*t4);
// set T values allowing perspective to change
// as each vertex moves around in z space
var uvtData:Vector.<Number> = new Vector.<Number>();
uvtData.push(0,0,t1, 1,0,t2, 1,1,t3, 0,1,t4);
// draw
container.graphics.clear();
container.graphics.beginBitmapFill(bitmapData);
container.graphics.drawTriangles(vertices, indices, uvtData);
}
// animage every frame
addEventListener(Event.ENTER_FRAME, rotatePlane);


Preview (Requires Flash Player 10)

With this example, a number of 3D points are defined in simple timeline variables x1, y1, z1, x2, y2, etc. These are then transformed into 2D points based on T values calculated from their z position and the focalLength variable. Notice how the same T values used to calculate the position of each 3D point in 2D space directly related to the T value in UVT; quite simple.

There's one more aspect of drawTriangles not touched upon, back-face culling, or simply culling. Culling refers to the determination of a triangle (face) being visible during the render process.

Inherently all triangles are always rendered no matter their size, shape, or position. To save on rendering cycles, there are often times when you would want a number triangles to be skipped by the render. Consider a cube rotating in space. At any given time you'll never see more than 3 sides of that cube since the sides not in view would be facing the other direction on the other side of the cube.

Since those sides are not going to be seen, the renderer shouldn't worry about drawing them. Using culling, you can do exactly that.

The way that drawTriangles determines culling is through winding. Assuming that all out-facing triangles of a 3D shape are drawn with the same winding, once a face is turned around, it's winding also changes. At that point it can be culled and removed from rendering.

The fourth parameter, culling, lets you set culling rules for triangles drawn with drawTriangles. It's a string with 3 possible values as defined in the TriangleCulling enumeration class:

public static const NONE:String = "none";
public static const POSITIVE:String = "positive";
public static const NEGATIVE:String = "negative";

A culling value of "none" (default) performs no culling. A setting of "positive" culls, or removes, triangles drawn positively wound while "negative" culls triangles negatively wound. When drawing the cube, if all of its out-facing triangles are drawn with positively wound, its culling should be set to "negative" so reversed faces not in view would not be rendered to the screen.

The last drawTriangles example with the rotating image did not set a culling value. If set, only one side of the rotated image would be visible as it rotated. You can test this by adding in a culling value in the call to drawTriangles.

// from earlier example
container.graphics.drawTriangles(vertices, indices, uvtData, TriangleCulling.NEGATIVE);

Want to determine the winding of 3 points that make up a triangle? Use this function:

function getWinding(path:Vector.<Number>):int {
if (path == null || path.length < 6) return 0;
var winding:int = (path[0]-path[4])*(path[3]-path[5]) - (path[1]-path[5])*(path[2]-path[4]);
if (winding < 0) return -1;
else if (winding > 0) return 1;
return 0;
}

It takes a Vector.<Number> of the 3 coordinate points of a triangle and will return 1 if winding is positive, -1 if winding is negative, and 0 if winding cannot be determined (if, for example, the triangle is flat).

drawGraphicsData

The final addition to the Graphics class is drawGraphicsData.

public function drawGraphicsData(graphicsData:Vector.<IGraphicsData>):void

This method is used to render drawings stored in the new graphics data classes.

New Graphics Data Classes

Along with new methods available in the Graphics class, a new collection of classes of the type IGraphicsData (an interface each of the classes implement) have been created to serve as data containers for the methods of the drawing API. These classes include:

  • GraphicsBitmapFill
  • GraphicsEndFill
  • GraphicsGradientFill
  • GraphicsPath
  • GraphicsShaderFill
  • GraphicsSolidFill
  • GraphicsStroke
  • GraphicsTrianglePath

The idea behind these classes is that you can store complete drawings in list (Vector.<IGraphicsData>) that can be passed around as data and reused with any target. This also makes it really easy to save drawings for use at a later time.

You may notice that there are multiple fill classes for each style of fill, but only one stroke class. This is because the stroke class actually uses the fill classes to define its style. So every stroke is actually made up of the stroke class and a fill class. Other than that, the API for these classes mirror the methods they represent. The basic run down of Graphics methods to these classes is as follows:

Method Class
beginFill GraphicsSolidFill
beginBitmapFill GraphicsBitmapFill
beginGradientFill GraphicsGradientFill
beginShaderFill GraphicsShaderFill
lineStyle GraphicsStroke + GraphicsSolidFill
lineBitmapStyle GraphicsStroke + GraphicsBitmapFill
lineGradientStyle GraphicsStroke + GraphicsGradientFill
lineShaderStyle GraphicsStroke + GraphicsShaderFill
moveTo
lineTo
curveTo
drawPath
GraphicsPath
drawTriangles GraphicsTrianglePath

In addition, the GraphicsPath class has moveTo, lineTo, curveTo, wideLineTo, and wideMoveTo utility methods to easily define those commands for a GraphicsPath instance. This can ease the pain of trying to define or update the commands and data values directly.

Unfortunately, there are no equivalents to drawCircle, drawEllipse, drawRect, or drawRoundedRect. These would have to be drawn into a GraphicsPath instance using lineTo and curveTo or by supplying the data manually.

Once you have a collection of IGraphicsData instances, you can draw them through the Graphics method, drawGraphicsData. This method takes a vector of IGraphicsData instances and runs them through the drawing API in sequential order.

Example:

// stroke object
var stroke:GraphicsStroke = new GraphicsStroke(3);
stroke.joints = JointStyle.MITER;
stroke.fill = new GraphicsSolidFill(0x102020); // solid stroke
// fill object
var fill:GraphicsGradientFill = new GraphicsGradientFill();
fill.colors = [0x0000FF, 0xEEFFEE];
fill.matrix = new Matrix();
fill.matrix.createGradientBox(70,70, Math.PI/2);
// path object
var path:GraphicsPath = new GraphicsPath(new Vector.<int>(), new Vector.<Number>());
path.commands.push(1,2,2);
path.data.push(125,0, 50,100, 175,0);
// combine objects for complete drawing
var drawing:Vector.<IGraphicsData> = new Vector.<IGraphicsData>();
drawing.push(stroke, fill, path);
// draw the drawing multiple times
// change one value to modify each variation
graphics.drawGraphicsData(drawing);
path.data[2] += 200;
graphics.drawGraphicsData(drawing);
path.data[2] -= 150;
graphics.drawGraphicsData(drawing);
path.data[2] += 100;
graphics.drawGraphicsData(drawing);
path.data[2] -= 50;
graphics.drawGraphicsData(drawing);

In the example above, the original drawing definition only drew one of the icicle shapes. By modifying one value in the path used by the drawing, it could be redrawn multiple times for a more complex image.

Though IGraphicsData objects can define fill and stroke styles, they don't have to. Graphics class methods can be used to style while IGraphicsData objects can be used to draw a saved collection of paths, or vise versa. They work together all the same.

You may have also noticed that there is no clear object. The clear method remains solely as a method of the Graphics class. It is also still required to clear out a previous drawing before starting a new one, unless you're adding on to the original, as seen in the example above. Though you can change a single portion of a path or collection of IGraphicsData objects that make up a drawing, that entire drawing will have to be redrawn from scratch for those changes to be made visible. Nevertheless, having object representations of your drawing greatly simplify the ability to modify or even animate dynamic graphics.

Conclusion

With all of the new drawing API features in Flash Player 10, it might take a while to get caught up. Having 3D (or 2.5D) support is a big one, and the drawing API has been updated to be a part of that - a part outside of the DisplayObject integration (not covered here).

Other drawing API features challenge you to rethink how you draw images dynamically in Flash Player. With IGraphicsData objects, you now have a standardized data format for maintaining and reusing dynamic drawings whose usage is completely different from that of the older APIs. Existing systems may need to be ported, but the benefit may be well worth it.

One thing is for sure, with 3D, Pixel Bender integration, and the other enhancements, it's certainly an exciting time for Flash Player on the visual front. I'm excited to see how these features will be used to create totally new and compelling content.


出处:http://www.senocular.com/flash/tutorials/flash10drawingapi/

posted on 2008-06-04 00:00 牛牛猪 阅读(620) 评论(0)  编辑 收藏 引用 所属分类: Flex / AS3

只有注册用户登录后才能发表评论。
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(12)

随笔分类

随笔档案

文章分类

收藏夹

搜索

最新评论

阅读排行榜

评论排行榜