LLX > Neil Parker

Neil Discovers Flash

(In case you haven't guessed already, you'll need Flash Player to view the examples on this page. They should work with Flash Player 6 or greater.)

Since I'm too frugal (read: "too much of a cheapskate") to spend all that money on a product I would only rarely use, I had long assumed that Flash animations were a game I would never get a chance to play. But then I discovered SWFTools, a set of open-source utilities for creating and manipulating Flash. SWFTools includes swfc, the SWF compiler, which reads a script from a text file and converts it to Flash. (I've been using a development snapshot, but I think the code shown here ought to work on the latest official release too.)

I've become a SWFTools fan, and I don't want to discourage people from using it, but it must be admitted that the journey has not always been smooth. First, there's the problem of documentation--there's no real manual for the swfc language. The SWFTools distribution includes a file showing examples of supported commands, but it's old and out of date. The SWFTools web site has a swfc manual, but it's really a tutorial rather than a manual, and its examples, though helpful, don't tell the whole story. I studied the examples, and other examples from other web sites (links can be found on the SWFTools links page), but in the end I had to use the swfc source code as my reference manual.

Second, the Flash Player environment iself throws roadblocks in the way of certain kinds of programming. I'll have more to say about that below.

My first efforts with swfc were mostly just variations on the examples shown in the swfc manual, and aren't really worth showing off here. Then, having learned to dog-paddle, I decided to dive into the deep end by arming myself with Adobe's ActionScript documentation and trying out some real Flash programming.

My first ActionScript success was a variation on an old Apple II demo called Brian's Theme. I've kept the original moire affect, but I've left out the agonizingly slow drawing of the original. This was a very straightforward translation of the original BASIC code, with no significant pitfalls along the way--so straightforward, in fact, that I was unable to resist enhancing it by adding some randomly-chosen color gradients.

Here's the original Applesoft BASIC source code, as printed in the Applesoft BASIC reference manual:

80  HOME : REM  CLEAR THE TEXT AREA
100  VTAB 24: REM  MOVE THE CURSOR TO BOTTOM LINE
120  HGR : REM  SET HIGH-RESOLUTION GRAPHICS MODE
140 A =  RND (1) * 279: REM  PICK AN X FOR "CENTER"
160 B =  RND (1) * 159: REM  PICK A Y FOR "CENTER"
180 I% = ( RND (1) * 4) + 2: REM  PICK A STEP SIZE
200  HTAB 15: PRINT "STEPPING BY ";I%;
220  FOR X = 0 TO 278 STEP I%: REM  STEP THRU X VALUES
240  FOR S = 0 TO 1: REM  2 LINES, FROM X AND X+1
260  HCOLOR= 3 * S: REM  FIRST LINE BLACK, NEXT WHITE
280  REM  DRAW LINE THROUGH "CENTER" TO OPPOSITE SIDE
300  HPLOT X + S,0 TO A,B TO 279 - X - S,159
320  NEXT S,X
340  FOR Y = 0 TO 158 STEP I%: REM  STEP THRU Y VALUES
360  FOR S = 0 TO 1: REM  2 LINES, FROM Y AND Y+1
380  HCOLOR= 3 * S: REM  FIRST LINE BLACK, NEXT WHITE
400  REM  DRAW LINE THROUGH "CENTER" TO OPPOSITE SIDE
420  HPLOT 279,Y + S TO A,B TO 0,159 - Y - S
440  NEXT S,Y
460  FOR PAUSE = 1 TO 1500: NEXT PAUSE: REM  DELAY
480  GOTO 120: REM  DRAW A NEW PATTERN

And here's the swfc translation:

.flash bbox=280x200 filename=brian.swf export=0
	.font f filename=/usr/X11/lib/X11/fonts/TTF/luximr.ttf glyphs=" 2345Sbeginpty"
	.edittext stepby font=f size=12pt width=280 height=14 noselect align=center color=white readonly
	.put stepby 140 180 pin=center
	.action:
		var scrn = this;

		function randcomp() {
			return Math.floor(Math.random()*192)+64;
		}

		function rgb(r, g, b) {
			return (r<<16)+(g<<8)+b;
		}

		function runBriansTheme() {
			var x, rn, gn, bn;
			var a = Math.floor(Math.random()*279); /* center X */
			var b = Math.floor(Math.random()*159); /* center Y */
			var s = Math.floor(Math.random()*4)+2; /* step */
			var r1 = randcomp(); /* color 1 */
			var g1 = randcomp();
			var b1 = randcomp();
			var r2 = randcomp(); /* color 2 */
			var g2 = randcomp();
			var b2 = randcomp();
			var rn = r1; /* current color */
			var gn = g1;
			var bn = b1;
			var rs = (r2-r1)*s/280; /* color step */
			var gs = (g2-g1)*s/280;
			var bs = (b2-b1)*s/280;
			scrn.clear();
			stepby.text = "Stepping by "+s;
			for(x = 0; x<279; x += s) {
				scrn.lineStyle(1, 0);
				scrn.moveTo(x, 0);
				scrn.lineTo(a, b);
				scrn.lineTo(279-x, 159);
				scrn.lineStyle(1, rgb(rn, gn, bn));
				scrn.moveTo(x+1, 0);
				scrn.lineTo(a, b);
				scrn.lineTo(278-x, 159);
				rn += rs;
				gn += gs;
				bn += bs;
			}
			rn = r2;
			gn = g2;
			bn = b2;
			rs = (r1-r2)*s/160;
			gs = (g1-g2)*s/160;
			bs = (b1-b2)*s/160;
			for(x = 0; x<159; x += s) {
				scrn.lineStyle(1, 0);
				scrn.moveTo(279, x);
				scrn.lineTo(a, b);
				scrn.lineTo(0, 159-x);
				scrn.lineStyle(1, rgb(rn, gn, bn));
				scrn.moveTo(279, x+1);
				scrn.lineTo(a, b);
				scrn.lineTo(0, 158-x);
				rn += rs;
				gn += gs;
				bn += bs;
			}
		}

		setInterval(runBriansTheme, 5000);
		runBriansTheme();
	.end
.end

Next I tried another Apple II demo, Rod's Color Pattern, a.k.a. Kaleidoscope, which I've already tackled a couple of times before in 6502 machine language and in Java. Since Brian's Theme went so smoothly, I expected this one to be no different. I was in for a rude awakening.

My first attempt was a straight translation of the code from the Java version. It eventually drew the final pattern, but during the several minutes it took to reach the final pattern, it drew absolutely nothing at all, not even the black background.

The reason for this appears to be that when Flash Player is running ActionScript, it's not doing anything else, not even refreshing the display. In Java, I could put the drawing into its own thread, but that doesn't seem to be an option in ActionScript.

Fixing this required totally rewriting the code. The original uses three nested loops, the inner one executing a total of 18,240 times, which is far more that Flash Player can deal with comfortably. After a bit of banging my head against some walls, I eventually came up with a routine that runs the inner loop just once, and then updates the loop indices for the next time, and then returns to let Flash Player do its redrawing and event handling. I used setInterval() with a short time interval to make sure it gets called repeatedly.

At first it looked like this approach was working. But as I watched the patterns draw, I realized that it was gradually getting slower, and s.l.o.w.e.r, and s..l..o..w..e..r, etc. I never did figure out the real reason for this, but whatever the cause was, it turned out that my block-drawing code was triggering it. Originally I drew the blocks by simply drawing and filling a rectangle. The solution turned out to be to make each block a separate character (to use Macromedia/Adobe's terminology) on the display, and change its color as necessary, rather than drawing new colors on top of old colors.

Perhaps someobdy more familiar than I with Flash's guts could tell me exactly why this is an issue. My theory is that Flash Player is trying to do some sort of buffering, saving the previous contents of each block in case it might get uncovered again later, but I'm not sure.

Anyway, here's how I finally got it to work:

.flash bbox=280x200 filename=kaleid.swf export=0
	.box idle 50 20 color=white fill=black
	.box hover 50 20 color=white fill=#555555
	.box press 50 20 color=white fill=#AAAAAA
	.outline tri:
		M -4 7 L 4 0 L -4 -7 L -4 7
	.end
	.filled ftri outline=tri color=white fill=green
	.button gobut
		.show idle as=area
		.show idle as=idle
		.show hover as=hover
		.show press as=pressed
		.on_release:
			runRCP();
		.end
	.end
	.sprite scrn
	.end
	.put gobut 140 180 pin=center
	.put ftri 140 180
	.put scrn
	.action:
		/* classic NTSC colors, from Linards Ticmanis: */
		/* var pal = [0, 0x8A2140, 0x3C22A5, 0xC847E4,
			0x07653E, 0x7B7E80, 0x308FE3, 0xB9A9FD,
			0x3B5107, 0xC77028, 0x7B7E80, 0xF39AC2,
			0x2FB81F, 0xB9D060, 0x6EE1C0, 0xFFFFFF]; */
		/* IIGS RGB colors, from IIGS Technical Note #63: */
		var pal = [0, 0xDD0033, 0x000099, 0xDD22DD,
			0x007722, 0x555555, 0x2222FF, 0x66AAFF,
			0x885500, 0xFF6600, 0xAAAAAA, 0xFF9988,
			0x11DD00, 0xFFFF00, 0x44FF99, 0xFFFFFF];
		var intid = null;
		var w, i, i40, j, k, k40, iw, iw12, s, dp = 1;

		s = new Array(40);
		for(i = 0; i<40; i++)
			s[i] = new Array(40);

		function plot(x, y, c) {
			var t;
			if(s[x][y] == undefined) {
				t = new Object;
				t.b = scrn.createEmptyMovieClip("b"+dp, dp);
				dp++;
				t.b._x = x*7;
				t.b._y = y*4;
				t.b.beginFill(0);
				t.b.moveTo(0, 0);
				t.b.lineTo(7, 0);
				t.b.lineTo(7, 4);
				t.b.lineTo(0, 4);
				t.b.lineTo(0, 0);
				t.b.endFill();
				t.c = new Color(t.b);
				s[x][y] = t;
			}
			s[x][y].c.setRGB(c);
		}

		function doOne() {
			var c = pal[Math.floor(j*3/(i+3))+iw12&15];
			plot(i, k, c);
			plot(k, i, c);
			plot(i40, k40, c);
			plot(k40, i40, c);
			plot(k, i40, c);
			plot(i40, k, c);
			plot(i, k40, c);
			plot(k40, i, c);
			j++;
			k++;
			k40--;
			if(j>19) {
				j = 0;
				i++;
				i40--;
				iw += w;
				if(i>19) {
					i = 1;
					i40 = 39;
					w++;
					iw = w;
					if(w>50) {
						if(intid!=null) {
							clearInterval(intid);
							intid = null;
						}
						return;
					}
				}
				k = i;
				k40 = 40-k;
				iw12 = Math.floor(iw/12);
			}
			updateAfterEvent();
		}

		function runRCP() {
			if(intid!=null)
				clearInterval(intid);
			for(i = 0; i<40; i++)
				for(j = 0; j<40; j++)
					plot(i, j, 0);
			w = 3;
			i = 1;
			i40 = 39;
			j = 0;
			k = 1;
			k40 = 39;
			iw = 3;
			iw12 = 0;
			intid = setInterval(doOne, 10);
		}

		_quality = "LOW";
		runRCP();
	.end
.end

The point of the 6502 version was to make it run quickly. The point of the Flash version ended up being just to get it running somehow...there doesn't seem to be much than can be done to speed it up. Decreasing the setInterval() time might help on really fast machines, but on my computer (a 2 GHz Core 2 Duo), the current value (10) has already reached the point of diminishing returns, with a total running time of about 4 minutes and 10 seconds.

That's about all I have to show off for now, but as as the mood strikes me to come up with new examples, I'll add them to this page.

LLX > Neil Parker

Original: October 20, 2008
Modified: November 10, 2008--Fixed title; fancier colors for Brian's Theme; minor update to Rod's Color Pattern.