Recreate the shape contained in SWF by MX draw API
This bug is creaed purely by draw API. No graph on the stage. Here is the code: myBug.txt
Well, in fact, I just extract the vector data from the sample SWF of Flash MX. Shadow is another shape. I did not include it here.
Create shape by draw API instead of graphic tools
You may have seen that some people create square or circle by draw API instead of graphic tools. Some even creates a button by draw API with long and complex scripts.
Personally, I dont favor this.
Why they do this ? One reason I have heard is that, they do this when they create components. In MX era, if we add a component to our fla, we see a bunch of new symbols in the asset folder of our libary. Yes, it is annoying. For the user, it might mess up his own symbols. For the author, those graphs and shapes are thus open to the user without protection.
So, they use draw API to create the assets of the component, By this, the component contains only scripts. No graphic symbol or linkage..
In Flash MX 2004, we can pack graph and script into a component package. Do not worry that any more.
There are other less important reasons. Some may want to adjust the thickness of stroke dynamically by redrawing the stroke with new lineStyle setting. This can only be done by draw API. There is no other way to change the stroke of a shape. Some may want to apply a complex skew change of a shape when implemented in 3-D rendering. This can only be done through drawing API.
Create graph by draw API has bad points such as the difficulties to debug, the increase of SWF size and the slowness of performance. Besides those, it is a hard work to figure out those x,y coordinate values to complete just a simple button. If we have a tool to help us, the job will be easier.
If we create our shape by drawing tools and then compiled it into SWF, all the vector data are stored into the swf file. What if we can extract the data out ?
ASV decompiles the SWF and for the shapes it gives out the outline coordinates. That helps much.
Here is my project. I want to use draw API to reproduce the shape in the SWF. I mean not just the outline but the gradient fill and solid fill.
The shape records in the SWF
Lets see how the vector data of a shape get stored in SWF file. Check the SWF file format document from Macromedia. We know several things:
1.First it stores in an array about the Fill type used. It may be a solid fill with color and alpha. It may be a gradient fill with type, colors, alphas, ratios and matrix. (Bitmap fill is beyond our discussion).
2. Second, it stores in an array about the lineStyle. Each stores the width, color and alpha.
3. Then here is steps of draw - the Shape records.
4. It selects two fill brushs (FillStyle1 and FillStyle0) and one lineStyle; It changes on needs.
5. Each steps, it contains of command like moveTo, lineTo and curveTo and coordinates values.
6. These data are stored in binary as less as possible bits.
So, this is not easy to understand. We just translate each shape records into draw API, then we finish our project. Right ?
No. Not so straight forward. There are still difference between the drawing of player and the drawing API.
The difference between draw API and shape records
1. Two brushes and one pencil at the same time
When drawing, the shape record selects two brushes and one pencil to work with. When we see a "lineTo" command in the shape record, all of them will do the lineTo action simultaneously. It draws a line and fills the right side region with a brush and also fills the left side region with another brush.
If we try to simulate the action, we need to create 3 clips. Two with solid fill or gradient fill, and the third one with lineStyle. All these clips should do lineTo command at the same time.
Macromedia is very proud of this. It does not store 3 actions. It stores only one. You see, how compact the drawing is stored in shape record.
2. The FillStyle1 and FillStyle0
As mentioned above, when we line from point A to point B, it fills both side. The brushes are named as FillStyle1 and FillStyle0 For example, if FillStyle1 is a solid Red brush and FillStyle0 is a solid Blue brush, it fills right side region by Red color and left side region by Blue color, Anyway, a command "lineTo" actually taking two brushes to work.
OK, we have create two clips with proper "beginFill" or "beginGradientFill". But, how do we handle the "left" and "right" things for FillStyle1 and FillStyle0 ?
In fact, we dont need to worry about this. Anyway, when we say "Fill", the lineTo's and curveTo's must at last result in a closed region. Flash will handle this automatically.
3. The interruption by moveTo
This is the most difficult one.
When we script by drawing API, a close region should contain continuous llineTo and curveTo without interruption. A "moveTo" script basically break the drawing and closes the present region and starts a new one. For example, the script "moveTo(0,0); lineTo(100,0); lineTo(0,100);" will draw a triangle with fill because of the two continuous "lineTo". However, if we insert a "moveTo" breaking it into two saparative "lineTo", then there is no triangle fill any more. This script "moveTo(0,0); lineTo(100,0);moveTo(100,0); lineTo(0,100);" , results in no fill region. The draw is divided into two parts. Each part is just a "line" , not enough to en-close a region.
This is not the case in shape records. The command "moveTo" simply is to move the brushes. No interuption or close-region effect.
The drawing in shape record is very flexible. To draw a fill of polygone A-B-C-D-E, shape records may first moves to C and draw C-D, and then suddenly moveTo A to draw A-B, then again moves to D to draw D-E-A and at last moves to B to draw the last line B-C. These 5 lines are drawn here and there without a continuous pattern. Anyway, these 5 lines finally creates a closed region, so it successfully draws a filled polygone region.
We can not do this by Action-script. The lines must be drawn continously without interruption. Each interruption will close the half-done drawing as a region. Of-course, we can divide the polygon into several small region and draw it piece by piece. This is another story we will talk later.
Well, can we solve it ?
I tried to pick those lines here and there together. Find the heads and tails. If the head of one line is the same as a tail of another line, then join these two lines together to form a continuous line.
It is too too too complex. In complex graph, the job is nearly impossible and frankly, I did not ever make an attempt to do so.
Here is a better solution.
4. To convert each lineTo into a triangle Fill
Consider the polygone ABCDE. If I take the point A as primary point and draw a line to each other points, (lines AB, AC, AD, AE), the polygone will be divided into several triangles. Each triangle with a peak points to primary point A and the side of polygone as the base side. If we convert each lineTo into that kind of triangle, then the sum of triangles will result in a polygone.
For example, for a command lineTo from B->C, we convert it to A lineTo B, B lineTo C and C lineTo A. This results in a triangular fill. By such conversion, when we complete 5 lineTo commands, we have created 5 triangle fills. And the result is a polygone.
Think about that, we can draw lineTo's here and there now. ¡@
In fact, the primary point can be anywhere. We can take any arbiturary point as the primary point. Here I take (0,0) as the primary point. We convert each "line" to a filled triangle. When the shape record shows a lineTo from (20, 40) to (140,60), I make the pencil to a line but convert the brush into more complex script as "moveTo(0,0); lineTo(20,40); lineTo(140,60); lineTo(0,0);" ;
When the primary point is set to point A, we seems to see '3' triangle in the polygone. When the primary point is set to (0,0), we see 5 triangles. The sum is the same.
Not difficult to understand if (0,0) is among the fill region of the polygon. Will it work, if the primary point is far off outside the polygone ? Do you believe that, even that, those 5 triangles will still results in a filled polygone ?
This is a little hard to explain. Just accept it. If you want to know more details, check my previous discussion about "calculating the area of a closed region".
We have a primary point at (0,0), our triangle all starts from that primary point. Then why the final result has nothing to do with the primary point ? Why there is no "tail" extending from the polygone to the primary point ?
5. The odd-even principle
This is because the so-called odd-even principle. Before we call endFill method, the region that gets painted odd times by the same brush will show. Those region that gets painted even times will be cancelled. So, a region get paint the first time, it shows. If it gets paint again, the fill is canceled. If it get paint again, this is the third time paint, it shows again. If we draw a fill of square and then draw a circle on it by the same brush before endFill, the result is a circular hole in the square.
For a polygon, the "tail" is created by those sides that are on the far side. The tail will be removed when we draw the lines on the near side.
The "tail" we expect from the polygone is cancelled due to even-times of fill.
4. Create translation module
OK, it is almost finished. We know how to modify and adjust the shape record now.
To apply our data extracted from SWF shape records to our fla, we have two ways.
The first one copy the shape record and adjust the drawing in our fla. The binary shape records are minimally modified into text data. We only make TWIP value into pixel values and binary code into text code such as "MT" means mean "moveTo". The text data is slightly bigger than the original compact binary data.
However, to execute this code, we must write our translation module in our fla. The translation module takes the codes and do the proper action. Each movie must have this translation modular scripts existing in the fla.
Another bad point is that, it is difficult to debug or modify. Because all brushes are set together , we can not eliminate any or add any. Each command are applied to 3 drawer, minor modification about the shape-records may ruin the shape completely.
There are several benefits. The most significant one is small in data size. Not so compact like binary SWF but is still compact text form.
Here is an example for push button. It is not a single shape in SWF. It consists of several shapes in SWF, so we render it separatively with separative codes.
The second benefits is capability of "stream animation". We can send the draw code code by code. However, the animation is still ugly. Not so beautiful as expected.
Here is the movie.
5. Re-write the structure into real action script
The other way is to translate these shape records into action script. We record all the actions that is done to each FillStyle (the brush) and LineStyle ( the pencil), then translate it by re-write into lines of real "action-script". So, we just copy-paste theresult to any frame script and we see the shape. No need to embed any translation script any more.
The pit-fall is, the resulted action script text data is really really huge. Each "lineTo" script is repeated 3 times for 3 clips. Each command is displayed with full sentence of action scripts. It is so huge that you wont like to do it for complex graph.
The good point is that, it is easier to modify and debug. Scripts are in each brush and pencil clips. You can remove a fill or pencil without ruining the whole shape. We can even add and eliminate part of the graph. It is useful when we need to construct a graph from several graph symbols.
Here is movie. with the script: myButton.txt
Download the zip file (PHP files, fla, swf); - 64K
In fact, I start to learn PHP one month ago. The best way to learn a language is to pick a project for myself. I pick this project for practice. Here is the PHP files I write. I would rather you read my article to understand how it works. Do not read the php file because I am not an experienced PHP scripter. The PHP file in the zip is incomplete. There is not parser code. It just shows how it translate the code after we parse out the data.¡@
In this article, I demonstrate the possibility of recreate the shape in SWF by drawing API. It also helps if one want to understand the file format of SWF. Usually the MX SWF are published with compression. For simplicity of demonstation and explanation, I dont want to handle the compression/decompression stuff. If you want to test your swf, make it published without compression.
Not every shape can be exracted out and applied directly. Some shape contains sub-shapes, grouped graph or structure of mask. To re-create the original appearance needs more manipulation.
I am not going to discuss this. Anyway, we need to peek the DefineSprite tag and peek the PlaceObject2 tag to see how these shapes are put on the stage. We can place the shapes as PlaceObject2 tags exept the "Skew" things.
Fortunately, if we want only the shape, there is no "skew".