Posts Tagged ‘AS3’

AS3 – Creating a Convex Polygon from Unordered Points

Monday, September 21st, 2009

Let’s pretend you have an application that lets users create shapes to be used in a physics simulation and that the user must click on the screen to set the vertices of the shape. Many physics engines only support convex polygons, or shapes that don’t have inlets, bites, or coves, basically shapes that don’t have inward facing edges. With this limitation we have to be able to restrict [read as "guide"] the user to make only convex polygons. For this we are going to need an algorithm that takes an unordered set of points and finds the convex hull that encloses those points. This way the user can click and add points at random, if desired, and your program will keep track of what points create a convex polygon, while the others are thrown away [or dealt with however you see fit].

The Graham Scan Algorithm is a process of ordering a random set of points and then calculating jumps to the points in that set that constitute a convex polygon. In this algorithm there are three steps. First is to find a corner point, usually the topmost, leftmost point in the set. The second step is to order all other points by the polar angle between the corner point and the point in question. The last step is to traverse the set, taking each proceeding subset of three points (n, n-1, n-2) to determine whether the angle made by these three points is a left turn, right turn, or a straight line. If the turn made is our desired turn [which is usually left - but in Flash it's right, due to the flipped y-axis] then we add that point to the convex hull. If the turn is not our desired turn, we get rid of that point and move on.

Here is an example that shows first the data set drawn from point to point. Each successive line gets progressively whiter. In the second step we find the corner point, order the other points and then show the outer polygon.

Example

Example

Here’s the code for the class:

/**
 *  Use this class freely - 2009 blog.efnx.com
 */


package
{
    import flash.geom.Point;
   
public class GrahamScan extends Object
{
    /**
     *  The Graham scan is a method of computing the convex hull of a finite set of points
     *  in the plane with time complexity O(n log n). It is named after Ronald Graham, who
     *  published the original algorithm in 1972. The algorithm finds all vertices of
     *  the convex hull ordered along its boundary. It may also be easily modified to report
     *  all input points that lie on the boundary of their convex hull.
     */

   
    public function GrahamScan()
    {
        super();
    }
   
    /**
     *  Returns a convex hull given an unordered array of points.
     */

    public static function convexHull(data:Array):Array
    {
        return findHull( order(data) );
    }
    /**
     *  Orders an array of points counterclockwise.
     */

    public static function order(data:Array):Array
    {
        trace("GrahamScan::order()");
        // first run through all the points and find the upper left [lower left]
        var p:Point = data[0];
        var n:int   = data.length;
        for (var i:int = 1; i < n; i++)
        {
            //trace("   p:",p,"d:",data[i]);
            if(data[i].y < p.y)
            {
                //trace("   d.y < p.y / d is new p.");
                p = data[i];
            }
            else if(data[i].y == p.y && data[i].x < p.x)
            {
                //trace("   d.y == p.y, d.x < p.x / d is new p.");
                p = data[i];
            }
        }
        // next find all the cotangents of the angles made by the point P and the
        // other points
        var sorted  :Array = new Array();
        // we need arrays for positive and negative values, because Array.sort
        // will put sort the negatives backwards.
        var pos     :Array = new Array();
        var neg     :Array = new Array();
        // add points back in order
        for (i = 0; i < n; i++)
        {
            var a   :Number = data[i].x - p.x;
            var b   :Number = data[i].y - p.y;
            var cot :Number = b/a;
            if(cot < 0)
                neg.push({point:data[i], cotangent:cot});
            else
                pos.push({point:data[i], cotangent:cot});
        }
        // sort the arrays
        pos.sortOn("cotangent", Array.NUMERIC | Array.DESCENDING);
        neg.sortOn("cotangent", Array.NUMERIC | Array.DESCENDING);
        sorted = neg.concat(pos);
       
        var ordered :Array = new Array();
            ordered.push(p);
        for (i = 0; i < n; i++)
        {
            if(p == sorted[i].point)
                continue;
            ordered.push(sorted[i].point)
        }
        return ordered;
    }
    /**
     *  Given an array of points ordered counterclockwise, findHull will
     *  filter the points and return an array containing the vertices of a
     *  convex polygon that envelopes those points.
     */

    public static function findHull(data:Array):Array
    {
        trace("GrahamScan::findHull()");
        var n   :int    = data.length;
        var hull:Array  = new Array();
            hull.push(data[0]); // add the pivot
            hull.push(data[1]); // makes first vector
           
        for (var i:int = 2; i < n; i++)
        {
            while(direction(hull[hull.length - 2], hull[hull.length - 1], data[i]) >= 0)
                hull.pop();
            hull.push(data[i]);
        }
       
        return hull;
    }
    /**
     *
     */

    private static function direction(p1:Point, p2:Point, p3:Point):Number
    {
        // > 0  is right turn
        // == 0 is collinear
        // < 0  is left turn
        // we only want right turns, usually we want right turns, but
        // flash's grid is flipped on y.
        return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
    }
}

}

Game Progress 1

Saturday, July 11th, 2009

I’ve been working on another game lately, it’s called Machinista – it’s a game where you control motors in a 2D physics simulation. The entire thing is built around Box2D, which is a great physics system. Last night I worked on using Brownian Bridge fractals for explosions – check it out! Use keys W, A, S, D and shift+click to control the tank and make explosions, respectively.

Pure Actionscript 3 Window Class

Thursday, November 13th, 2008

I’ve been working on an easily skinnable windowing system, and this is what I have so far. I’ve attempted to mimic mac’s theme for Leopard.

Actionscript 3 Leopard GUI

Actionscript 3 Leopard GUI

The source and documentation are in their usual spots -> My classes. I’ll be updating them pretty regularly. Here’s the source for the project itself -> project source

Flash Actionscript 3 Waveform Generation Class

Friday, October 24th, 2008

In my last post, Plotting a Sound Wave in Flash AS3 I detailed a method to use when displaying audio data. The method itself works great, but due to Flash’s frame-based code execution and event processing the user looses input capabilities while the flash player chugs through millions of numbers adding, rounding and comparing. In order to make displaying an audio waveform easier on both the programmer and the user I wrote a class that analyzes a Sound object progressively, and dispatches a special event containing the analyzed data. The class will construct a left and right channel Vector, each containing one data point [a number between 0 and 1] for a given number of windows, between two positions in the sound. The left and right position are measured in samples and two types of analyzation are offered. Here is a demo of the class in action:

Screen Capture of Waveform Plot

Screen Capture of Waveform Plot


The calculated data can be reached incrementally through the WaveformEvent object which is dispatched every frame, or at the end of all analyzation in the Waveform object’s leftChannel and rightChannel properties. The details are listed in the documentation below.

Thanks to the Summit Projects Flash Blog and Thibault Imbert at ByteArray for their posts on the different techniques that went into my class.

Source
Here is the source for my TextMate project: Sources
Documentation: class and event

And here is a Flex version (made in windows): // Thanks dem!
Sources (Flex version)

Plotting a Sound Wave in Flash AS3

Friday, September 26th, 2008

I’ve always been really into wave editors. I used to make songs in Amadeus by piecing together samples from other songs. Tedious but very rewarding. In a post I made not a long time ago I detailed a little Theremin project which included some wave data visualization. In this post I”ll be going further into detail about plotting sound data.

Digital audio in it’s rawest form [PCM wave data] is a long list of numbers from 1 to -1, which represent the sound’s amplitude. Another way of thinking about this is that each number represents your speaker’s distance away from it’s rest position. At 1 the speaker is fully extended, blowing out your ears and scaring your cats, while at -1 it is fully retracted, blowing out your ears and scaring your cats. To make a meaningful visual out of this we just set up a graph where time is plotted on the horizontal axis and amplitude on the vertical. So at a really high resolution, that might look like this:
80's Oscilloscope
Okay, maybe it would look like that if you were living in the 80′s. Or if you were really into oscilloscopes. In reality with most popular songs being two to five minutes long, we’d be looking at HUGE graphs. One three minute song sampled at 44.1kHz/s comes out to be eight million samples per channel. Per channel. Since most modern music is in stereo, we’re looking at two graphs now. So how do we compress this data and view it in a meaningful way? We cheat a little. We kinda scrap the whole graph/function thing. Well kinda. Let’s say you have a window 1000 pixels wide and a sound 3 minutes long. We have to compress enough samples together in order to represent them using each pixel [about 7,938 samples per pixel]. Averaging doesn’t work because with values oscillating between -1 and 1 the mean is usually zero. We could take one sample every so often to represent an entire chunk and plot that, but that’s just resampling at a much lower resolution, which results in aliasing and all sorts of bogus stuff. Take this video for instance:

Just instead of helicopter blades not moving, it’ll be your data points. No, instead what we do is we scan every single sample and pick out the largest and the smallest numbers from the chunk, which in our case means running through every 7,938 samples and picking the biggest and smallest ones. Then we plot them on top of each other, maybe with a line connecting them. Flash gets a little slow working with lists and arrays this big, but I’m sure we’ll figure out some neat tricks to get this stuff working fast. That said, here’s a little demo. If you can figure out the song I’ll give you a million bucks. [Let it chug for a while, Flash is a slow beast]:

pcm wave data plotted in flash

pcm wave data plotted in flash

Project Source

ps – don’t cheat, guess the song before you download the source, goofball.

New Updated Tools/Classes For Actionscript 3

Monday, August 18th, 2008

Button
Calculating Frames Per Second [and memory usage]
MultiLoader
Example and Project Source

I’ve created a number of tools for people to use in the past, most of which I update regularly. In this post I’d like to revisit some of my old classes and post new updated code for those classes, as in their current form they don’t much represent my coding style. Please note that my class hierarchy has also changed, so make sure to update your includes if you are using these in your projects.

Button
I posted code for a button class here ->here about a year ago, since then I’ve changed things and really cleaned up that class. It’s simpler than before and faster. Given one to three images this class will create a clickable button that has the option to trigger up to four functions [on roll over, mouse down, mouse up and roll out]. Here is a snippet showing its use:

        var button:Button = new Button();
       
        // bitmapData of buttons "up" state
        var bmd_up:BitmapData = new BitmapData(50, 20, true, 0xFFBDE052);
        // bitmapData of buttons "over" state
        var bmd_over:BitmapData = new BitmapData(50, 20, true, 0xFF52C4E0);
        // bitmapData of buttons "down" state
        var bmd_down:BitmapData = new BitmapData(50, 20, true, 0xFFFFAF1A);
       
        button.x = 20;
        button.y = 20;
        // we pass our bitmapData to the button, one for each state. if only one bitmapData is supplied, it will be
        // copied to each state, after which each new bitmapData supplied will replace the first for the state
        // specified.
        button.up = bmd_up;
        button.over = bmd_over;
        button.down = bmd_down;
       
        // we supply functions to the buttons function references, if no functions are supplied, no functions will be called.
        button.overFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button over");};
        button.downFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button down");};
        button.upFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button up");};
        button.outFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button out");};
       
        addChild(button);
       
        // adding a label is as simple as creating a textfield and adding it to the button.
        var label:TextField = new TextField();
            label.textColor = 0xDDD4F7;
            label.mouseEnabled = false;
            label.text = "Button";
            label.autoSize = "left";
            label.x = button.width/2 - label.width/2;
            label.y = button.height/2 - label.height/2;
           
        button.addChild(label);

Calculating Frames Per Second
The next class I’d like to revisit is my FPS counter. It’s basically a textfield that displays an instantaneous fps count, and an averaged count. I have now removed the average frames per second and included a display of used memory in MB. Before I had used a timer to calculate the fps, which wasn’t quite accurate enough. Now the class uses flash.utils.getTimer() and is very precise and accurate. The class extends TextField so custom coloring, etc is very straightforward:

        var fps:FPSBox = new FPSBox();
        addChild(fps);

MultiLoader and MultiLoaderEvent
The next entry is a class created for downloading and importing files from your server or local file system into your flash application. It is what I use in place of my Multiple Bitmap Loader and my Bitmap Resource Handler. MultiLoader extends Loader to support multiple asynchronous downloads of images/swfs. They trigger specific (read “specially named”) ProgressEvents based on the names of entries supplied by you in their load functions. Alternatively, they also trigger a MultiLoaderEvent for every ProgressEvent or complete Event dispatched. One can access the bytesLoaded and bytesTotal for each event, as well as the entry name of the event (which matches the entry name given to MultiLoader). For example, to load a gif and listen to progress and complete events, code the following:

        var multiLoader:MultiLoader = new MultiLoader();
            multiLoader.load("http://www.efnx.com/images/experiment.gif", "efnx");
                       multiLoader.load("http://www.efnx.com/images/experiment.gif", "efnx", "Bitmap");
            multiLoader.addEventListener("efnx_Progress", efnxProgress, false, 0, true);// specially named
            multiLoader.addEventListener("efnx_Complete", efnxComplete, false, 0, true);// specially named
               multiLoader.addEventListener("progress", progress, false, 0, true);// will trigger a MultiLoaderEvent with an entry member for identification
               multiLoader.addEventListener("complete", complete, false, 0, true);

    private function efnxProgress(event:ProgressEvent):void
    {
        trace("UpdatedTools::efnxProgress()", (event.bytesLoaded/event.bytesTotal*100));
    }
    private function efnxComplete(event:ProgressEvent):void
    {
        trace("UpdatedTools::efnxComplete()");
    }
    private function progress(event:MultiLoaderEvent):void
    {
        trace("UpdatedTools::progress()", event.entry, (event.bytesLoaded/event.bytesTotal*100));
    }
    private function complete(event:MultiLoaderEvent):void
    {
        trace("UpdatedTools::complete()", event.entry);
    }

In this example my entry name is “efnx” and so MultiLoader triggers events of type “efnx_Progress” and “efnx_Complete”. By listening for these events, we can get updated information about our downloads. Another option is to listen for “progress” and “complete,” which will trigger an event of type MultiLoaderEvent, which extends ProgressEvent to include an “entry” member. The value of event.entry will be the name of the entry triggering the event, in this case “efnx.” Now to use our images we “get” the resource from MultiLoader, which has been storing it for us. A great feature of this class is being able to specify what the return type of your loaded object when calling the “get” function. Here I’m going to want to return a Bitmap object, so for the third parameter of the “load” function I’ll pass the string “Bitmap”. Here’s an example:

multiLoader.load("http://www.efnx.com/images/experiment.gif", "efnx", "Bitmap");

Now inside my efnxComplete function I’ll add the get function to get my Bitmap and display it.

        // if we are listening for "efnx_Complete"
    private function efnxComplete(event:Event):void
    {
        trace("UpdatedTools::efnxComplete()");
        var efnxBitmap:Bitmap = multiLoader.get("efnx");
            efnxBitmap.x = button.x + button.width + 10;
            efnxBitmap.y = button.y;
        addChild(efnxBitmap);
    }
        // or if we are listening for "complete"
    private function complete(event:MultiLoaderEvent):void
    {
        trace("UpdatedTools::complete()");
        var efnxBitmap:Bitmap = multiLoader.get(event.entry);
            efnxBitmap.x = button.x + button.width + 10;
            efnxBitmap.y = button.y;
        addChild(efnxBitmap);
    }

Using the third parameter of the load() function is optional and if omitted Multiloader will by default choose an appropriate return type, here is a list of the valid strings to pass as a return type:

MovieClip
Sprite
Bitmap
BitmapData

If no third parameter is supplied, MultiLoader will return a type it thinks is appropriate, given the extension of the loaded material. For example loading a .swf will return a MovieClip, where as loading a .jpeg, .png or .gif will return a BitmapData.

Example App and Project Source
Here is a project that shows all three updated classes being used:

package {

    import flash.events.Event;
    import flash.events.ProgressEvent;
    import flash.display.Sprite;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.text.TextField;
   
    import efnx.gui.Button;
    import efnx.time.FPSBox;
    import efnx.net.MultiLoader;
    import efnx.events.MultiLoaderEvent;
   
/**
 *  Application entry point for UpdatedTools post.
 *
 *  @langversion ActionScript 3.0
 *  @playerversion Flash 9.0
 *
 *  @author Schell Scivally
 *  @since 18.08.2008
 */

public class UpdatedTools extends Sprite {
   
    public static const testing:Boolean = false;
   
    public var button:Button = new Button();
    public var multiLoader:MultiLoader = new MultiLoader();
   
    /**
     *  @constructor
     */

    public function UpdatedTools()
    {
        super();

        stage.frameRate = 30;
        stage.scaleMode = "noScale";
        stage.align = "TL";
        stage.addEventListener("resize", resize, false, 0, true);
       
        // bitmapData of buttons "up" state
        var bmd_up:BitmapData = new BitmapData(50, 20, true, 0xFFBDE052);
        // bitmapData of buttons "over" state
        var bmd_over:BitmapData = new BitmapData(50, 20, true, 0xFF52C4E0);
        // bitmapData of buttons "down" state
        var bmd_down:BitmapData = new BitmapData(50, 20, true, 0xFFFFAF1A);
       
        button.x = 20;
        button.y = 20;
        // we pass our bitmapData to the button, one for each state. if only one bitmapData is supplied, it will be
        // copied to each state, after which each new bitmapData supplied will replace the first for the state
        // specified.
        button.up = bmd_up;
        button.over = bmd_over;
        button.down = bmd_down;
       
        // we supply functions to the buttons function references, if no functions are supplied, no functions will be called.
        button.overFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button over");};
        button.downFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button down");};
        button.upFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button up");};
        button.outFunction = function():void{trace("UpdatedTools::UpdatedTools()", "button out");};
       
        addChild(button);
       
        // adding a label is as simple as creating a textfield and adding it to the button.
        var label:TextField = new TextField();
            label.textColor = 0xDDD4F7;
            label.mouseEnabled = false;
            label.text = "Button";
            label.autoSize = "left";
            label.x = button.width/2 - label.width/2;
            label.y = button.height/2 - label.height/2;
           
        button.addChild(label);
       
        var fps:FPSBox = new FPSBox();
        addChild(fps);
       
        MultiLoader.testing = true;
        MultiLoader.usingContext = true;
       
        multiLoader.load("http://www.efnx.com/images/experiment.gif", "efnx", "Bitmap");
        multiLoader.load("http://www.efnx.com/images/808.jpg", "808", "Sprite");
        multiLoader.addEventListener("progress", progress, false, 0, true);
        multiLoader.addEventListener("complete", complete, false, 0, true);
    }
   
    private function progress(event:MultiLoaderEvent):void
    {
        trace("UpdatedTools::progress()", event.entry, ": ", (event.bytesLoaded/event.bytesTotal*100));
    }
    private function complete(event:MultiLoaderEvent):void
    {
        trace("UpdatedTools::complete()");
        switch(event.entry)
        {
            case "efnx":
                var efnxBitmap:Bitmap = multiLoader.get("efnx");
                    efnxBitmap.x = button.x + button.width + 10;
                    efnxBitmap.y = button.y;
                addChild(efnxBitmap);
                break;
            case "808":
                var sprite808:Sprite = multiLoader.get("808");
                    sprite808.x = button.x;
                    sprite808.y = button.y + button.height + 20;
                addChild(sprite808);
                break;
            default:
        }
    }
   
    /**
     *  Resize stub.
     */

    private function resize(event:Event):void
    {
        if(testing) trace( "UpdatedTools::resize()" );
    }
   
}

}

And here is the source to my classes:
Example Project and Source [includes classes]

Here is a little example:
updatedExample

And as always you can find the classes by themselves [along with all my other classes] here efnx AS3 classes, as well as some limited documentation efnx Class Documentation

Actionscript 3 Browser Theremin / Wave Data Visualization

Saturday, August 2nd, 2008

Yesterday I started fooling around with FP10′s sound generation capabilities and I got the idea to code a little Theremin. It came out pretty good, but was bland in aesthetics. I decided to try my hand at coding a visualization algorithm similar to the type of visualization one would see while using Amadeus or Digital Performer, etc. I wanted to plot the wave form on the stage. Taking a copy of the sound buffer and selecting equally spaced samples I plotted them on the bitmapData of a Bitmap object and drew lines between data points. This is what it looks like:
Theremin Wave Data

The mouse position in x controls the frequency of a sine wave [from 220Hz to 440Hz or A3 to A4], while the y position controls volume. There’s some tearing that occurs when modulating volume, I haven’t figured that out how to fix that yet. As you move your mouse the wave that is generated is plotted on the stage. The pink dots represent sampled data points [which correspond to speaker movement] while the purple lines are approximations of the in-betweens [just lines drawn from point to point]. After playing with that for a while I wrote a class that matches note names to frequencies and drew markers indicating where on the x-axis of the stage one would have to place the cursor to generate a given note between A3 and A4. This is just a small example of the possibilities of sound in flash, and one way to interact with it. I’m looking forward to building on the classes I made for this example. I’ve included my entire TextMate project and source below [which will be the foundation of many more experiments], you can chop it up and use it however you want, although I didn’t copy any license to it, but trust me I won’t sue.

src -> Theremin Project/Visualizing Wave Data

Flash 10 [Astro] Debug Install

Wednesday, July 30th, 2008

I had some problems installing the under-the-radar Flash 10 Debug version mentioned in a couple blogs in the past months. So here is my work around just in case anyone else can’t seem to get Flash 10 to trace to their flashlog.txt.

1. Download installer from Adobe’s svn repository and select your OS [the rest of these steps are for mac, mind you, but you'll get the idea].
2. Unarchive and mount “Install Flash Player 10 UB.dmg”
3. Here’s the funny part, I couldn’t install the package. I got an error saying “nothing to install”, so instead, right click on “Adobe Flash Player.pkg” and choose “Show Package Contents”. It should show two archives, a plist, pkginfo and a resources folder.
4. Unarchive “Archive.pax.gz” to your desktop.
5. Inside you’ll find “Flash Player.plugin,” “flashplayer.xpt” and “version.txt.” The version.txt file says the player is version 9.0.45.0, but after looking at the info for “Flash Player.plugin” you’ll see it’s version 10.0.0.525, which isn’t the newest, but it traces and that’s what I need it for.
6. Copy “Flash Player.plugin” and “flashplayer.xpt” to your internet plugin folder, on my machine that’s /Library/Internet Plug-Ins
7. Restart FireFox/Safari/whatever.

That’s it!

I’m sorry if this info is un-needed, I definitely needed it, maybe everyone else can simply install that package, but I couldn’t, so that’s the fix. Happy coding.

Merging Layered BitmapData in AS3

Thursday, February 14th, 2008

This tutorial is for those of your who might have to write your own Photo-shop-a-like applications or anyone who has to merge [flatten] two BitmapData together at pixel-level, without using BitmapData.copyPixels or BitmapData.merge. Furthermore this tutorial deals with bitwise operations, which are super fast and rad for color computation. Boom. Color. If you’re not familiar with bitwise operators I suggest you check out Polygonal Labs’ Bitwise Gems: Fast Integer Math. Done? Great. Okay, on with our discussion. I hope this stuff makes sense because this is my first tutorial. Ever…. …so first let’s create some colored Bitmaps on the page.

var bmd1:BitmapData = new BitmapData(100, 100, true, 0xFF00FF00);
var bm1:Bitmap = new Bitmap(bmd1);
addChild(bm1);
var bmd2:BitmapData = new BitmapData(100, 100, true, 0xFFFF0000);
var bm2:Bitmap = new Bitmap(bmd2);
bm2.y = bm1.height;
addChild(bm2);
var bmd3:BitmapData = new BitmapData(100, 100, true, 0x7F0000FF);
var bm3:Bitmap = new Bitmap(bmd3);
bm3.x = bm1.width/2;
bm3.y = bm1.height/2;
addChild(bm3);

Building our project we should see somethin like this:
MergingLayeredBitmapDataStep1
As you can see we’ve created a super green bmd, a bloody red bmd and a half-hearted (half-opaque) blue bmd. When the half-opaque blue bitmap is placed over the green and red bitmaps it filters the green and red bitmaps, altering the percieved colors. Our goal is to be able to calculate those percieved colors given two 32 bit colors in order to merge them onto a new BitmapData.

Now let’s begin our pixel layering function. We can start by outlining what the function is going to do. Let’s give it two 32 bit colors, (like the red and blue or the green and blue) and let’s make it give us back a 32 bit color that represents the two colors layered on top of each other.

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
}

Now let’s extract each color’s ARGB channels. Traditionally the method would look like this:

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
    var aB:uint = bottomColor >>> 24;
    var rB:uint = bottomColor >>> 16 & 0xFF;
    var gB:uint = bottomColor >>> 8 & 0xFF;
    var bB:uint = bottomColor  & 0xFF;
    var aT:uint = topColor >>> 24;
    var rT:uint = topColor >>> 16 & 0xFF;
    var gT:uint = topColor >>> 8 & 0xFF;
    var bT:uint = topColor  & 0xFF;
}

But since we’re working with what the colors *should* look like when layered, we have to figure out how much each channel actually shows through, according to the alpha channel. To do this we’ll multiply each channel by the color’s alpha channel’s opacity. The opacity is simply the alpha channel value divided by 255. So 100% opacity (255 or 0xFF) would turn out as 1, and 1 times each channel means 100% of each channel will show. 50% opacity (127 or 0x7F-0×80) would turn out as .5 and .5 times each channel shows 50% of each channel. Thus:

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
    var aB:uint = bottomColor >>> 24;
    var rB:uint = (bottomColor >>> 16 & 0xFF)*(aB/0xFF);
    var gB:uint = (bottomColor >>> 8 & 0xFF)*(aB/0xFF);
    var bB:uint = (bottomColor  & 0xFF)*(aB/0xFF);
    var aT:uint = topColor >>> 24;
    var rT:uint = (topColor >>> 16 & 0xFF)*(aT/0xFF);
    var gT:uint = (topColor >>> 8 & 0xFF)*(aT/0xFF);
    var bT:uint = (topColor  & 0xFF)*(aT/0xFF);
}

Now that we have our individual channel values we can begin to calculate our new color. Remember that the maximum color value is 255 (0xFF) so we’ll be constraining our channel additions to 255.

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
    var aB:uint = bottomColor >>> 24;
    var rB:uint = (bottomColor >>> 16 & 0xFF)*(aB/0xFF);
    var gB:uint = (bottomColor >>> 8 & 0xFF)*(aB/0xFF);
    var bB:uint = (bottomColor  & 0xFF)*(aB/0xFF);

    var aT:uint = topColor >>> 24;
    var rT:uint = (topColor >>> 16 & 0xFF)*(aT/0xFF);
    var gT:uint = (topColor >>> 8 & 0xFF)*(aT/0xFF);
    var bT:uint = (topColor  & 0xFF)*(aT/0xFF);

    var aN:uint = Math.min(aB + aT, 0xFF);
    var rN:uint = Math.min(rB + rT, 0xFF);
    var gN:uint = Math.min(gB + gT, 0xFF);
    var bN:uint = Math.min(bB + bT, 0xFF);
}

If you combine our channels and return the new color you’ll get something much brighter than what we should see. We’re going to have to filter the bottom color through the top color to fix this. You might be saying “ah man, I’ll just use BitmapData.copyPixels,” but don’t worry, we’re almost done. It’ll be so much more satisfying to figure out this stuff in the end, believe me. You’ll be a color scientist. So, all we need to do is multiply each channel of the bottom color by a number that represents how much light penetrates the top layer. So, if our top layer alpha channel is 0x7F, our ‘show through’ variable is going to be 50% (0x7F) and if our top alpha channel is 0×40 (25%) then 75% of the bottom channel will show through. To do this we’ll create our ‘show through’ var and multiply the bottom channels by it.

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
    var aB:uint = bottomColor >>> 24;
    var rB:uint = (bottomColor >>> 16 & 0xFF)*(aB/0xFF);
    var gB:uint = (bottomColor >>> 8 & 0xFF)*(aB/0xFF);
    var bB:uint = (bottomColor  & 0xFF)*(aB/0xFF);
    var aT:uint = topColor >>> 24;
    var rT:uint = (topColor >>> 16 & 0xFF)*(aT/0xFF);
    var gT:uint = (topColor >>> 8 & 0xFF)*(aT/0xFF);
    var bT:uint = (topColor  & 0xFF)*(aT/0xFF);
    /*this determines how much of our lower layer shows through*/
    var showThru:Number = (0xFF - aT)/0xFF;
    trace("laryerColor32:showThru: (255 - "+aT+")/255 = "+showThru);
    /**********************************************************************/
    var aN:uint = Math.min(aB + aT, 0xFF);
    var rN:uint = Math.min(rB*showThru + rT, 0xFF);
    var gN:uint = Math.min(gB*showThru + gT, 0xFF);
    var bN:uint = Math.min(bB*showThru + bT, 0xFF);
}

Now for the last bit. Let’s combine all our new channels and return that value.

private function layerColor32(topColor:uint, bottomColor:uint):uint
{
    var aB:uint = bottomColor >>> 24;
    var rB:uint = (bottomColor >>> 16 & 0xFF)*(aB/0xFF);
    var gB:uint = (bottomColor >>> 8 & 0xFF)*(aB/0xFF);
    var bB:uint = (bottomColor  & 0xFF)*(aB/0xFF);
    var aT:uint = topColor >>> 24;
    var rT:uint = (topColor >>> 16 & 0xFF)*(aT/0xFF);
    var gT:uint = (topColor >>> 8 & 0xFF)*(aT/0xFF);
    var bT:uint = (topColor  & 0xFF)*(aT/0xFF);
    /*this determines how much of our lower layer shows through*/
    var showThru:Number = (0xFF - aT)/0xFF;
    trace("laryerColor32:showThru: (255 - "+aT+")/255 = "+showThru);
    /**********************************************************************/
    var aN:uint = Math.min(aB + aT, 0xFF);
    var rN:uint = Math.min(rB*showThru + rT, 0xFF);
    var gN:uint = Math.min(gB*showThru + gT, 0xFF);
    var bN:uint = Math.min(bB*showThru + bT, 0xFF);
   
    return(aN << 24 | rN << 16 | gN << 8 | bN);
}

Awesome! Were done. Using this function, for loops and some creativity, you can compile any number of 32 bit BitmapData on top of each other. Boom. Color. So, let’s use this function and compare it to the blending that happens within DisplayObjectContainers with transparency! Let’s alter our first bitmap generating/placing code to use our new function:

var bmd1:BitmapData = new BitmapData(100, 100, true, 0xFF00FF00);
var bm1:Bitmap = new Bitmap(bmd1);
addChild(bm1);
var bmd2:BitmapData = new BitmapData(100, 100, true, 0xFFFF0000);
var bm2:Bitmap = new Bitmap(bmd2);
    bm2.y = bm1.height;
addChild(bm2);
var bmd3:BitmapData = new BitmapData(100, 100, true, 0x7F0000FF);
var bm3:Bitmap = new Bitmap(bmd3);
    bm3.x = bm1.width/2;
    bm3.y = bm1.height/2;
addChild(bm3);
var color1:uint = bmd1.getPixel32(0, 0);
var color2:uint = bmd3.getPixel32(0, 0);
var color3:uint = layerColor32(color2, color1);
var color4:uint = bmd2.getPixel32(0, 0);
var color5:uint = layerColor32(color2, color4);
var bmd4:BitmapData = new BitmapData(50, 50, true, color3);
var bm4:Bitmap = new Bitmap(bmd4);
    bm4.x = bm1.width;
addChild(bm4);
var bmd5:BitmapData = new BitmapData(50, 50, true, color5);
var bm5:Bitmap = new Bitmap(bmd5);
    bm5.x = bm1.width;
    bm5.y = bm1.height*1.5;
addChild(bm5);

Let’s take a look at it.
MergingLayeredBitmapDataStep2
Wow, I don’t believe. Well, there you have it. Bust out your color schemer (or equivalent) and check those hex codes. Exact are they not? I hope you enjoyed this! Yay color.

AS3 Classes and Interfaces Quicktip

Saturday, January 19th, 2008

Interfaces in AS3 are super useful for requiring classes to support a set of public methods, enabling other classes to communicate through the interface thereby abstracting the class itself. AS3 doesn’t natively support abstract classes so using Interfaces is a good replacement in some cases, but if you’ve never used an Interface before you probably wouldn’t know how to implement it. The docs help a little, showing you how to instantiate a class using an interface with the “implements” keyword, but doesn’t go much further:

public class myClass implements myInterface{
}

But what if you’d like to implement a class while extending another? Well, it’s simple once you know the syntax…

public class myClass extends baseClass implements myInterface {
}

This is a very quick and simple tip that took me a while to find online due to the fact Adobe doesn’t mention it. Such a simple thing should be in the help docs, but I guess adobe thinks that anyone trying to use Interfaces would already know the syntax! Well now you do.


Follow me on GitHub
Follow me on Google+
Follow me on Twitter