Flash8 Perlin Wood Texture


The Flash 8 documentation for perlinNoise() has this intriguing statement:

You can use Perlin noise functions to simulate natural phenomena and landscapes, such as wood grain, clouds, and mountain ranges.

OK. That sounds like fun. But how is this done? It's not immediately obvious, at least not to me. So I googled .

According to these links (here and here), the formula for wood is:

g = perlin(x,y) * 20;
grain = g - int(g);

It looks like we have to do the math on every pixel.

After calling perlinNoise, we use getPixel(), mulitply by 20, get the fractional part, then stuff the pixel back in the bitmap with setPixel(). And do that with ever pixel.

That would take forever.

Maybe in AS3 we'll be able to process each pixel individually, but not in AS2. It's too slow. Instead, we need to use the available BitmapData functions to get the same result.

This article describes a way to do that.

Here's an imaginary scan line from a perlin noise bitmap -- just one rgb channel:
Initial Perlin Noise

Notice that the signal is bounded by 0 and 255 -- the limits of the r, g and b channels in a pixel.

The multiplier of 20 in the above formula is arbitrary. It determines how many tree rings you have in your wood. Let's use 4 instead for diagram purposes:
Multiplied Perlin Noise

The g - int(g) part is the same as fraction(g). You can get this by chopping each band and collapsing then together like so:

Chopped Perlin Noise

Believe it or not, if you do this in two dimensions with the amplitude controlling the color, here's what you get:

Replacement image.

The key Flash api functions and classes are:

  • BitmapData.perlinNoise() (of course)
  • ColorMatrixFilter
  • BitmapData.threshold()
  • BitmapData.draw() with blend modes.

Here's the code:

 * @author Joel May 
 * www.connectedpixel.com 
 * All original source code listed here is licensed under a Creative Commons License. 
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.geom.Matrix;
import flash.filters.BlurFilter;
import flash.filters.BitmapFilter;
import flash.filters.ColorMatrixFilter;
import flash.geom.ColorTransform;
// Renders a wood texture to a bitmap.  Uses perlin noise and the folumula
// pixval = fraction(nTreeRings * perlin(x,y))

class com.connectedpixel.texture.Wood {
    private var _baseX          :Number = 400;
    private var _baseY          :Number = 120;	
    private var _nOctaves       :Number = 1; 
    private var _randomSeed     :Number = 128;
    private var _bTile          :Boolean = false;	
    private var _bFractalNoise  :Boolean = false;
    private var _nGrainLayers   :Number = 15;
    private var _blurX          :Number = 5;
    private var _blurY          :Number = 5;
    private var _rgb0:Number = 0xaa5522;
    private var _rgb1:Number = 0xee9922;
    private var _identityMatrix:Matrix;
    private var _identityColorTrans:ColorTransform;
    private var _woodColorFilter:ColorMatrixFilter;
    private var _blurFilter:BlurFilter;
    private function invalidateWoodColorFilter():Void
        delete _woodColorFilter;
        _woodColorFilter = undefined;	
    private function invalidateBlurFilter():Void
        delete _blurFilter;
        _blurFilter = undefined;	
    // Properties.
    public function set perlinBaseX(bx:Number):Void { 
        if (isNaN(bx)) return;
        if (bx < 3) bx = 3;
        if (bx > 1000) bx = 1000;
        _baseX = bx; 
    public function get perlinBaseX():Number { return _baseX; }
    public function set perlinBaseY(by:Number):Void { 
        if (isNaN(by)) return;
        if (by < 3) by = 3;
        if (by > 1000) by = 1000;
        _baseY = by; 
    public function get perlinBaseY():Number { return _baseY; }
    public function set octaves(nOct:Number):Void { _nOctaves = nOct; }
    public function get octaves():Number { return _nOctaves; }
    public function set grainLayers(nLayers:Number):Void { _nGrainLayers = nLayers; }
    public function get grainLayers():Number { return _nGrainLayers; }
    public function set rgb0(rgb:Number):Void { 
        _rgb0 = rgb; invalidateWoodColorFilter();
    public function get rgb0():Number { return _rgb0; }
    public function set rgb1(rgb:Number):Void { 
        _rgb1 = rgb; invalidateWoodColorFilter();
    public function get rgb1():Number { return _rgb1; }
    public function set seed(s:Number):Void { _randomSeed = s; }
    public function get seed():Number { return _randomSeed; }
    public function set tileable(bTile:Boolean):Void { _bTile = bTile; }
    public function get tileable():Boolean { return _bTile; }
    public function set fractalNoise(bFractal:Boolean):Void { _bFractalNoise = bFractal; }
    public function get fractalNoise():Boolean { return _bFractalNoise; }
    public function Wood()
        _identityMatrix		= new Matrix();
        _identityColorTrans = new ColorTransform();
    // Convenience function.  Returns a bitmap of the desired
    // size using the current wood settings.
    public function createBitmap(w:Number,h:Number):BitmapData
        var wood_bmp:BitmapData = new BitmapData(w, h, false, 0x000000);
        return wood_bmp;
    //  Render the wood grain onto the bitmap using the current property 
    //  values.  
    //  buffer0_bmp and buffer1_bmp are optional.  If they are not supplied
    //  temporary bitmaps will be created.  They MUST have the same 
    //  width and height as the destination bmp.
    public function render(bmp:BitmapData, buffer0_bmp:BitmapData,  
        var w:Number = bmp.width;
        var h:Number = bmp.height;
        // Will hold perlin noise.
        var srcNoise_bmp:BitmapData = (buffer0_bmp != undefined) ?  buffer0_bmp : 
            							new BitmapData(w, h, false, 0xffffffff);
        var tmp_bmp:BitmapData = (buffer1_bmp != undefined) ?  buffer1_bmp : 
            					    new BitmapData(w, h, false, 0xffffffff);
        // channelOptions - 1 - Red only
        // grayscale - false
        // Needed in some of the following flash api calls.		
        var rect:Rectangle = new Rectangle(0,0,w,h);	
        var origin:Point = new Point(0,0);
        // For each tree ring.
        for (var iLayer:Number = 0 ; iLayer < _nGrainLayers ; iLayer++){
            // After multiplying, the signal needs to be shifted into the
            // 0 to 255 range.	
            var offset:Number = - iLayer * 256;	
            // Amplify and shift the pixels.
            var matrix:Array = [_nGrainLayers, 0, 0, 0, offset,
            				               0, 0, 0, 0,      0, 
            				               0, 0, 0, 0,      0, 
            				               0, 0, 0, 1,      0 ];
            var filter:BitmapFilter = new ColorMatrixFilter(matrix);
            // Set the brightest to be black. Following layers will write
            // only on the black.
            tmp_bmp.threshold(tmp_bmp, rect, origin, "==", 
                                         0x00ff0000, 0xff000000, 0x00ff0000, false);
            // Copy the tmp on to the dest bitmap.
            var blend:Object = 5; //lighten
            bmp.draw(tmp_bmp, _identityMatrix, _identityColorTrans, blend);
        // Don't need the temporary bitmaps anymore
        if (buffer1_bmp == undefined){
        if (buffer0_bmp == undefined){
        // Change it from black and red to the desired colors.
        bmp.applyFilter(bmp,rect, origin, getWoodColorFilter());		
        // Blur it to remove the jaggies.	
    // Create a filter that will lessen the jaggies.  The threshold() call
    // creates jaggies that are pretty bad.  This basically solves the 
    // problem.
    private function getBlurFilter():BlurFilter
        if (_blurFilter != undefined){
            return _blurFilter;
        _blurFilter = new BlurFilter(_blurX,_blurY,1);
        return _blurFilter;
    // Map the black to red colors to the desired wood colors.
    private function getWoodColorFilter():ColorMatrixFilter
        if (_woodColorFilter != undefined){
            return _woodColorFilter; 
        // Apply the desired colors to the bitmap.
        var r0:Number = (_rgb0 >> 16) & 0xff;
        var g0:Number = (_rgb0 >> 8 ) & 0xff;
        var b0:Number = _rgb0 & 0xff;
        var r1:Number = (_rgb1 >> 16) & 0xff;
        var g1:Number = (_rgb1 >> 8 ) & 0xff;
        var b1:Number = _rgb1 & 0xff;
        var woodColor:Array = [(r1-r0)/255, 0, 0, 0, r0,
                               (g1-g0)/255, 0, 0, 0, g0,
                               (b1-b0)/255, 0, 0, 0, b0,
                               0   , 0, 0, 1,    0 ];
        _woodColorFilter= new ColorMatrixFilter(woodColor);
        return _woodColorFilter;

The above code will render a wood texture to a bitmap.

I made a component to add a wood texture to a movieclip by dragging and dropping the component onto the movieclip. I used it to quickly add the wood texture to the shapes in the Flash movie below.

Replacement image.



It seems that the first time the perlin noise is used in a movie, it takes a long time. But subsquent uses are quick. I'm not sure about this. I need to dig in and figure out whether the problem really is the perlinNoise function, or one of the other bitmap api functions.

Perlin Persistance

Perlin noise is supposed to have a parameter called persistance that is used to control how the amplitude decreases with each perlin octave. The Flash version does not have this. Presumably, the Flash perlin function uses a persistance of 0.5, which seems to be the default in the perlin documentation I've read.

If you want more detail in your wood texture, you need to use more than one octave. But wood is supposed to use a low persistance (< 0.5). If you use more than one octave in your wood with too high a persistance, it looks swirly.

I tried to implement persistance other than 0.5 by layering perlin noise bitmaps with different baseX and baseY values (lots of shifting with ColorMatrix and blending with add and subtract). But I couldn't get it too work and was spending too much time on it. Maybe I'll go back to it later.


Of course, if you want a wood texture, you can simply use a jpeg. It's up to you whether this method is worthwhile.

It was a chance for me to get a handle on perlin noise. I probably will use it, however. I want to make a game with Scrabble-like tiles, and I'd like them to not look identical.

Now, how about those clouds and mountain ranges...


I had done a quick sample with clouds and landscape generation using the perlin noise before flash 8 was released (IE, no actual documentation). Could probably be improved now... :)


Very cool terrain. I like the idea of using a threshold in the noise and assigning blue to colors below that and orange to higher colors. The BitmapData.threshold function would also be useful here.

I wonder if there is a way to use the DisplacementMapFilter to do generate the 3D terrain from the perlin noise instead of doing trig on every pixel. Maybe not, I don't know. It would be tricky.

Hej, great work! Was actually doing generative textures just the other day, hadn't thought of would at all. You can do alot of stuff with perlin noise indeed.

Did this in the pre-release days (and as opposed to mike, had documentation) Perlin Clouds! :


Very subtle effect (sometimes it goes wrong, and shows no clouds, just refresh), but it works realistic, and the clouds move over time....


Cloud Source Code?

It looks like the distant clouds are smaller. Are they really scaled? Or is it random and my preconceived notions of clouds are skewing my perception?