Archive for February, 2008

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.

First Software Purchased Evar.

Monday, February 4th, 2008

The first piece of [non-game] software I ever purchased was TextMate, just now. I buckled down and bought it. It is flippin sweet. I hope my move to linux finds me an equivalent editor. DORKSAUCE. Ah, I also bought 4GB of RAM for my MacBook for $80 at newegg. That’s almost 10x less than buying 2GB from Apple. They’re not “fully buffered EEC” but 2x2GB @ $36 each? Bomb! > 2x1GB @ $150 each, that’s for sure. I’ll let you know how it goes after installation.

*update*
Macbook Pros (at least my model) only support a max of 3gb of RAM. So I’m full up and have 2gb left over. I’m giving it to my friend Matthew. My mbp’s been running great with the new memory, so it’s all good.


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