Reply to comment
Last week, I wrote an article about using Flash 8 Perlin noise to generate a wood texture. Today, it's marble.
According to this link, the Perlin formula for marble is:
texture = cosine( x + perlin(x,y,z) )
Again, we don't want to do math on every pixel -- too slow. So, how do we do this with the existing Flash 8 API? First, let's pretend that the perlin term in the above equation is not there. Here's what our image would look like (this image is arbitrarily blue monochrome):
One pixel scan line of the above image was generated with Math.cos() and BitmapData.setPixel(). The bitmap was then stretched vertially -- no need to call setPixel on w * h pixels -- w x 1 is enough.
The perlin term randomly distorts the phase. In the context of the above image, this means pixels are randomly displaced horizontally. A DisplacementMapFilter driven by perlin is exactly what we need:
Then we can map the colors to realistic marble colors using ColorMatrixFilter:
I find the sinusoid basis for the marble to be too regular. We can use perlin noise instead of a sinusoid and our pre-DisplacementMapFilter image looks like this:
You can play with the settings here. BTW, I find it takes a lot of trial-and-error to find parameters that result in a decent marble.
Here's the code:
/** * @author jmay * 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.filters.DisplacementMapFilter; import flash.geom.ColorTransform; class com.connectedpixel.texture.Marble { //////////////////////////////////////////////////////////// // Properties. Settable via setter/getters below // Sinusoidal or Perlin floor private var _bSinusoidalFloor:Boolean = false; // Sinusoidal floor parameters private var _waveLength:Number = 100; private var _sinMidColorPt:Number = 128; private var _sinContrastMultiplier:Number = 1.0; // Perlin floor parameters private var _bPerlinFloorBaseX:Number = 50; private var _bPerlinFloorBaseY:Number = 150; private var _floorRandomSeed:Number = 317; private var _perlinMidColorPt:Number = 128; private var _perlinContrastMultiplier:Number = 2.0; // Perlin floor modifiers private var _contrastMultiplier:Number = 1.0; private var _colorMidLevel:Number = 128; // offset private var _veinAngleDeg:Number = 0; // Marble Distortion parameters private var _bEnableDistortion:Boolean = true; private var _baseX :Number = 20; private var _baseY :Number = 20; private var _nOctaves :Number = 2; private var _randomSeed :Number = 147; private var _bFractalNoise :Boolean = false; private var _blurX :Number = 5; private var _blurY :Number = 5; // Distortion strength private var _displacementScaleX = 150; // Color mapping private var _rgb0:Number = 0x1c2a1f; private var _rgb1:Number = 0xadc8b4; //////////////////////////////////////////////////////////// private var _identityMatrix:Matrix; private var _identityColorTrans:ColorTransform; private var _marbleColorFilter:ColorMatrixFilter; private function invalidateMarbleColorFilter():Void { delete _marbleColorFilter; _marbleColorFilter = undefined; } ////////////////////////////////////////////////////////////////////// // Properties. public function set sinusoidalFloor(bSin:Boolean):Void { _bSinusoidalFloor = bSin; } public function get sinusoidalFloor():Boolean { return _bSinusoidalFloor; } public function set waveLength(len:Number):Void { if (isNaN(len)) return; if (len < 2) len = 2; if (len > 1000) len = 1000; _waveLength = len; } public function get waveLength():Number { return _waveLength; } public function set sinMidColorPt(val:Number):Void { if (isNaN(val)) return; if (val < 10) val = 10; if (val > 246) val = 246; _sinMidColorPt = val; } public function get sinMidColorPt():Number { return _sinMidColorPt; } public function set sinContrast(contrast:Number):Void { if (contrast < 0.1 ) contrast = 0.1; if (contrast > 10.0) contrast = 10.0; _sinContrastMultiplier = contrast; } public function get sinContrast():Number { return _sinContrastMultiplier; } /////////////////////////////////////////////////////////////////////// public function set perlinMidColorPt(val:Number):Void { if (isNaN(val)) return; if (val < 10) val = 10; if (val > 246) val = 246; _perlinMidColorPt = val; } public function get perlinMidColorPt():Number { return _perlinMidColorPt; } public function set perlinContrast(contrast:Number):Void { if (contrast < 0.1 ) contrast = 0.1; if (contrast > 10.0) contrast = 10.0; _perlinContrastMultiplier = contrast; } public function get perlinContrast():Number { return _perlinContrastMultiplier; } public function set floorBaseX(bx:Number):Void { _bPerlinFloorBaseX = bx; } public function set floorBaseY(by:Number):Void { _bPerlinFloorBaseY = by; } public function get floorBaseX():Number { return _bPerlinFloorBaseX; } public function get floorBaseY():Number { return _bPerlinFloorBaseY; } public function set floorRandomSeed(seed:Number):Void { _floorRandomSeed = seed; } public function get floorRandomSeed():Number { return _floorRandomSeed; } //////////////////////////////////////////////////////////////////////////// public function set enableDistortion(bEnab:Boolean):Void { _bEnableDistortion = bEnab; } public function get enableDistortion():Boolean { return _bEnableDistortion; } 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 rgb0(rgb:Number):Void { _rgb0 = rgb; invalidateMarbleColorFilter(); } public function get rgb0():Number { return _rgb0; } public function set rgb1(rgb:Number):Void { _rgb1 = rgb; invalidateMarbleColorFilter(); } 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 fractalNoise(bFractal:Boolean):Void { _bFractalNoise = bFractal; } public function get fractalNoise():Boolean { return _bFractalNoise; } public function set displacementScaleX(dx:Number):Void { if (isNaN(dx)) return; if (dx < 1) dx = 1; if (dx > 256) dx = 256; _displacementScaleX = dx; } public function get displacementScaleX():Number { return _displacementScaleX; } //////////////////////////////////////////////////////////////////////// public function Marble() { _identityMatrix = new Matrix(); _identityColorTrans = new ColorTransform(); } //////////////////////////////////////////////////////////////////////// // Convenience function. Returns a bitmap of the desired // size using the current Marble settings. /////////////////////////////////////////////////////////////////////// public function createBitmap(w:Number,h:Number):BitmapData { var Marble_bmp:BitmapData = new BitmapData(w, h, false, 0x000000); render(Marble_bmp); return Marble_bmp; } ///////////////////////////////////////////////////////// // Render the Marble grain onto the bitmap using the // current property values. // buffer0_bmp and buffer1_bmp are optional. If they // are not suppliedtemporary bitmaps will be created. // They MUST have the same width and height as // the destination bmp. //////////////////////////////////////////////////////// public function render(bmp:BitmapData):Void { var w:Number = bmp.width; var h:Number = bmp.height; // 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); var mid:Number; var mult:Number; // The source bitmap needs to be larger than the destination bitmap because // DisplacementMapFilter will grab pixels from a larger area. var paddingX:Number = _displacementScaleX + 8; // Add 16 as slop var floor_bmp:BitmapData = new BitmapData(w+paddingX,h,false,0x000000); if (_bSinusoidalFloor){ drawSine(floor_bmp, _waveLength); mid = _sinMidColorPt; mult = _sinContrastMultiplier; } else{ floor_bmp.perlinNoise(_bPerlinFloorBaseX,_bPerlinFloorBaseY,1, _floorRandomSeed,false,true,4,false); mult = _perlinContrastMultiplier; mid = _perlinMidColorPt; } var offset:Number = mult * (mid-128); var ampColor:Array = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, mult, 0, offset, 0, 0, 0, 1, 0 ]; var ampColorFilter:ColorMatrixFilter = new ColorMatrixFilter(ampColor); floor_bmp.applyFilter(floor_bmp,floor_bmp.rectangle, origin, ampColorFilter); ///////////////////////////////////////////////////////// // Add the marble distortion. if (_bEnableDistortion){ // Will hold perlin noise. var srcNoise_bmp:BitmapData = new BitmapData(w+paddingX, h, false, 0xffffffff); // channelOptions - 4 - blue only // grayscale - false srcNoise_bmp.perlinNoise(_baseX,_baseY,_nOctaves,_randomSeed, false,_bFractalNoise,4,false); var filter:DisplacementMapFilter = new DisplacementMapFilter(srcNoise_bmp,origin,4,1, _displacementScaleX,0); var r:Rectangle = new Rectangle(paddingX/2,0,w,h); bmp.applyFilter(floor_bmp,r,new Point(0,0),filter); srcNoise_bmp.dispose(); } else{ bmp.copyPixels(floor_bmp,rect,origin); } floor_bmp.dispose(); // Change it from black and blue to the desired colors. bmp.applyFilter(bmp,rect, origin, getMarbleColorFilter()); } private function drawSine(floor_bmp:BitmapData, wavelength:Number):Void { var w:Number = floor_bmp.width; // 1-pixel high bitmap. var tmp_bmp:BitmapData = new BitmapData(w, 1); var phaseInc:Number = 2 * Math.PI / wavelength; var phase:Number = 0; for (var x:Number = 0 ; x < w ; x++){ phase += phaseInc; var colVal:Number = 127 * Math.cos(phase) + 128; colVal = Math.round(colVal); // We're dealing with blue only here. tmp_bmp.setPixel(x,0,colVal); } var stretchMatrix:Matrix = new Matrix(); stretchMatrix.scale(1,floor_bmp.height); // Now, draw the movieclip onto the floor_bmp var blend:Object = 1; // normal floor_bmp.draw(tmp_bmp,stretchMatrix,_identityColorTrans, blend); // Clean up. tmp_bmp.dispose(); } /////////////////////////////////////////////////////////////////////// // Map the black to blue colors to the desired Marble colors. /////////////////////////////////////////////////////////////////////// private function getMarbleColorFilter():ColorMatrixFilter { if (_marbleColorFilter != undefined){ return _marbleColorFilter; } // 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 marbleColor:Array = [0, 0, (r1-r0)/255, 0, r0, 0, 0, (g1-g0)/255, 0, g0, 0, 0, (b1-b0)/255, 0, b0, 0, 0, 0, 1, 0 ]; _marbleColorFilter= new ColorMatrixFilter(marbleColor); return _marbleColorFilter; } }
Here are a few generated marbles.



