Flex, Curved Text, DisplacementMapFilter, Why I Don't Swing That Way

I'm working on a project that involves writing text on circle. Much to the cat's horror, there are indeed several ways to skin it. Tonight I am giving up on what I thought was the most promising, and perhaps the coolest: the DisplacementMapFilter.

Displacement maps are really cool and I am getting the hang of them, though the real PITA is working out the logic to build your bitmaps. At f(x) / coverthesky, there is a tantalizing example that shows a working curved text demo (and you have to go to the front page for a few moments of amusement).

Of course, the only problem is that the example is not Flex based and seems to be written in Flash 8 and the code snippets are not exactly complete ... and his trig example would be easier to follow if he spent a bit more time setting the scene. That is, maybe it is obvious to others that "A" effectively describes the total rectangle of interest and the origin of the coordinate system. Anyway, beggars can't be choosers, but we can spend some time figuring out specifically what the heck is going on.

So, here's the skinny:

  1. create a container to hold your text object. We'll be applying the filter to this object.
  2. create and add a text containing object to your container.
  3. create a bitmap using the dimensions of the containing object, iterate over every single pixel, setting the color with respect to the desired final effect.
  4. to the containing objects filter array, add a new DisplacementMapFilter, using the created bitmap.
  5. voila, your container is likely now, depending on your color calculations, very screwed up looking

In an attempt to understand what I'm doing, I've broken the steps into handy little functions. I'll work a bit backwards, though maybe its forwards ... after several hours of this, I'm not really sure. Anyway, let's start with the black magic of the color determination:


private function getDisplacementColor(h:uint,w:uint,x:uint,y:uint):Number {
var xK:Number = w / 2;
var yK:Number = K * h;            
var DG:Number = Math.sqrt(yK * yK - xK * xK);
var CGD:Number = Math.acos(DG / yK);
var EG:Number = yK * (yK - h) / DG;

var LG:Number = Math.sqrt((x - xK) * (x - xK) + (y - yK) * (y - yK));
var NGL:Number = CGD + Math.asin((x - xK) / LG);
var xS:Number = Math.round(w * NGL / (2 * CGD));
var yS:Number = Math.round(h * (yK - LG) / (yK - EG));

return bf * (128 + xS - x) + gf * (128 + yS - y);
}    
Okidoki, that should be horribly confusing. Working from the diagram that is associated with the demo, these values almost start to make sense. Essentially we are determing for a coordinate system of h height and w width what should the new rotated coordinates be for any given x & y. At this point, I would love to be more profound, but my efforts thus far have been mostly a combination of head scratching, conversion and empirical experimentation.

[If you are paying attention to the code above, and your eyes didn't glass over, you'll notice that we refer to three variables that aren't declared; K, gf & bf. I made those global variables in order to allow for configuration by external controls. The center point is bound to the horizontal center of the containing object, however the vertical center is set by the multiplier K. As for gf & bf, you'll need to refer to the diagram on f(x) site.]

This function is called by a Bitmap creation function that builds our filter pixel by pixel:


private function createDisplacementBitMap(tWidth:uint,tHeight:uint):BitmapData {            

var b:BitmapData = new BitmapData(tWidth,tHeight,true);            

for (var x:uint=0; x < tWidth; x++) {
for (var y:uint=0;y<tHeight;y++) {                
b.setPixel(x,y,getDisplacementColor(tHeight,tWidth,x,y));
}
}    

return b;
}
I'm happy to say that at this level, everything is a lot simpler. We're creating a raw bitmap of the given dimensions. We then inspect the Bitmap by iterating over the columns (we could have just as easily done rows) and for every X,Y coordinate pair, we go and request the appropriate color to use.

If you just want to see interesting patterns, and not so interesting patterns, you could always replace the getDisplacementColor with a random color generator:


private function getRandomColor():Number {
return Math.random() * 0xFFFF;
}
You'll notice that I used 0xFFFF as my base. As we will see in just a second, only the blue and green color channels are used to determine displacement. So, limiting the color range limits color values, noise in this case, that won't affect the image.

Adding the filter is just a matter of adding an Array literal containing the desired filter, along with any other additional filters you might need. Convolution filters are fun to play with and I'll make some more mention of them as I start to utilize them in my projects (or if it is a slow day and I have nothing else to talk about). So, adding the filter goes like this:


private function applyFilter(myUIComponent:UIComponent):void {
myUIComponent.filters = [
new DisplacementMapFilter(createDisplacementBitMap(myUIComponent.width,myUIComponent.height),
new Point(),
BitmapDataChannel.BLUE,
BitmapDataChannel.GREEN,
0xFF,
0xFF,
DisplacementMapFilterMode.CLAMP)];    
}

Click here for an example that includes some controls for experimenting with the constraints as well as a view of the Bitmap used as the filter. Setting K to 0.52 gets you to a somewhat circular shape.

Of course, the whole reason for writing about all of this is that I came to the conclusion that it will not work well for my needs. Or better stated perhaps, that I have run out of patience with my own grasping of what it might take to make it work (I need to adjust the radius of the circular path on the fly and then reconstitute it in scaled coordinate system). And, more importantly, I have begun to fear the resultant quality of the deformed text. The in-place editing is really nice, but I can live without it.

Of course, that said, it is nifty enough that I expect to revisit it if only just to play.

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
makc's Gravatar hey, that was my article. I discovered this link through referrers list today. the arc was one transformation of dozen I did for now defunct site, so when I outlined the math for that I thought it was already too lengthy for simple example. to get circular text, you should have it started from scratch, not from my snippet. today, pixel bender allows us much better quality of the result, so it could be fun to try this again, indeed.
# Posted By makc | 11/8/09 7:21 AM
jason olmsted's Gravatar I probably would have dived deeper and started from the beginning, but, from what I could tell at the time, the net effect was going to be some very distorted text. Lately, I've been doing a more thorough investigation of actionscript image manipulation so maybe I'll revisit it - though, as you mentioned, PixelBender seems to be the wave of the future. Also, the other alternative, as I have mentioned in other blog posts was to position the text about a circle to get curved text. Not exactly the same effect, but interesting unto itself.

I tried adding a comment to your blog post back when I was investigating it, but it was, as it is now, not accepting them. So, a belated thank you for the helpful article. And, looking at all the interesting 3D & AR posts you have at your new site, I have a lot of reading to do and probably more reasons to be appreciative.
# Posted By jason olmsted | 11/9/09 12:04 PM
Sweety's Gravatar Hey!

i integrate code to draw circle

but how can i attach system font combo with this text?
# Posted By Sweety | 11/30/09 8:42 AM
jason olmsted's Gravatar @Sweety - not sure of your application that actually gives you a desirable effect, but changing the font is relatively straightforward. In the example (w/source) that gave for this, the manipulated text is in a programmatically added TextInput (in the init() function). To change the font, use the setStyle function:

myText.setStyle("fontFamily","Arial");

Have fun with it
# Posted By jason olmsted | 12/1/09 12:41 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.001. Contact Blog Owner