Extracting positive and negative shapes from a BitmapData
Tuesday, March 3rd, 2009The first step in generating the "vector text effects" which I blogged about in my previous post is to grab a BitmapData of a character, then seperate the "characters components" into individual BitmapData instances.
So let's take a capital B :

The B only contains one "positive shape", that is, the "filled in" part. The B contains 3 "negative shapes" or the transparent bits. In this case we've got the surrounding area and the two "holes".
If we take a percent symbol "%"

We've got 3 positive shapes and 3 negative ones.
So how does one go about extracting these? I'm sure there are several approaches, I tried a few, this was the speediest of the ones I came up with.
Step 1 : Convert the captured BitmapData into one containing just two colors, a solid and a transparent. *Note:* this has the side effect that the extracted shapes have "pixelated" , "jagged" or "aliased" edges. For my purposes this is sufficient, a different approach would be needed to keep anti-aliasing.
-
public static function toMonoChrome(source:BitmapData,mono_color:uint=0xFF000000):BitmapData{
-
var bmd:BitmapData=source.clone();
-
bmd.threshold(bmd,bmd.rect,new Point(),">",0x00000000,mono_color);
-
return bmd;
-
}
Step 2 : Create an "inverse" of the "monochrome" image:
-
//create the monochrome for extracting "positive shapes"
-
var positive_two_color:BitmapData=new BitmapData(source_bmd.width,source_bmd.height,true,0x00);
-
positive_two_color.draw(source_bmd);
-
positive_two_color=BitmapDataUtil.toMonoChrome(positive_two_color,0xFFFF0000);
-
-
//create the monochrome for extracting "negative shapes"
-
var temp:BitmapData=new BitmapData(source_bmd.width,source_bmd.height,true,0xFF0000FF);
-
temp.draw(positive_two_color);
-
var negative_two_color:BitmapData=new BitmapData(source_bmd.width,source_bmd.height,true,0xFFFF0000);
-
negative_two_color.copyChannel(temp,positive_two_color.rect,new Point(), BitmapDataChannel.BLUE, BitmapDataChannel.ALPHA);
Again, I'm sure there are other ways to skin this cat. I created a BLUE "temp" BitmapData, then drew the "positive shape" on it. I then copy the BLUE Channel into the ALPHA channel of a RED BitmapData, which creates an image with inverted RED and ALPHA from the "positive shape".
Step 3 : Extract the "component" BitmapDatas from the positive and negative BitmapDatas. This requires a few steps:
Create a loop which:
- finds the first non transparent pixel more info
- Does a FloodFill with Blue on that pixel
- Create a new BitmapData, and copy the BLUE Channel into it, add this to the "found" items
- Remove the Blue from the original by using a transparent FloodFill
Do this for both the "positive" and inverse "negative" BitmapDatas and store the discovered "component shapes" in a vector or an array.
Here's my code for doing so:
-
protected static function extractShapesFromMonochrome(bmd:BitmapData):Vector.<BitmapData>{
-
var scan_y:uint=0;
-
var non_trans:Point;
-
var found:Vector.<BitmapData>=new Vector.<BitmapData>();
-
var copy_bmd:BitmapData;
-
while(scan_y<bmd.height){
-
non_trans=BitmapDataUtil.getFirstNonTransparentPixel(bmd,scan_y);
-
if(non_trans==null)return found;
-
bmd.floodFill(non_trans.x,non_trans.y,0xFF0000FF);//fill with blue
-
copy_bmd=new BitmapData(bmd.width,bmd.height,true,0xFFFF0000);
-
copy_bmd.copyChannel(bmd,bmd.rect,new Point(),BitmapDataChannel.BLUE , BitmapDataChannel.ALPHA);
-
bmd.floodFill(non_trans.x,non_trans.y,0x00000000);//fill with blue
-
found.push(copy_bmd);
-
scan_y=non_trans.y;
-
}
-
return found;
-
}
That pretty much does it. The next step is to analyze the discovered shapes and generate vector points to work with... but that's another blog post.

