package { import flash.events.EventDispatcher; import flash.events.ProgressEvent; import flash.events.Event; import flash.display.BitmapData; import flash.utils.setTimeout; import flash.utils.clearTimeout; /** * ... * @author nicoptere */ public class MeanShift extends EventDispatcher { private var _distance2D:Number; private var _distance3D:Number; private var bd:BitmapData; private var pixelsf:Array = []; private var width:int; private var height:int; private var interval:uint; private var _running:Boolean = false; private var iteration:int = -1; private var pct:Number; /** * * @param radius the 2D threshold around each pixel * @param distance the colorspace threshold */ public function MeanShift( radius:int = 3, distance:int = 12 ):void { this._distance2D = radius; this._distance3D = distance; } /** * throws the mean shift rendering loop * @param bd the source BitmapData, can be assigned through the getter/setters or here. */ public function process( bd:BitmapData = null ):void { if ( bd != null ) bitmapData = bd; if ( bitmapData == null ) return; if ( iteration == -1 ) { iteration = 0; percent = 0; _running = true; } clearTimeout(interval); interval = setTimeout( loop, 1 ); } /** * recursive loop called internally by pprocess */ private function loop():void { if ( !_running ) return; percent = ( iteration / height ) * 100; run( iteration ); dispatchEvent( new ProgressEvent( ProgressEvent.PROGRESS, false, false, iteration, height ) ); iteration++; if ( iteration < height ) { return process( null ); } else { stop(); } } /** * pprevents the next render to occur */ public function stop():void { _running = false; percent = 0; iteration = -1; clearTimeout(interval); dispatchEvent( new Event( Event.COMPLETE ) ); } /** * processes the given iteration * @param iteration an int between 0 and the bitmapData height */ private function run( iteration:int = 0 ):void { var _distance2D2:int = _distance2D * _distance2D; var _distance3D2:int = _distance3D * _distance3D; var x:int, y:int; var shift:Number = 0, Yc:Number, Ic:Number, Qc:Number, Y2:Number, I2:Number, Q2:Number, dY:Number, dI:Number, dQ:Number, YcOld:Number, IcOld:Number, QcOld:Number, num_:Number; var mx:Number = 0, my:Number = 0, mY:Number = 0, mI:Number = 0, mQ:Number = 0,dx:int, dy:int; var iters:int = 0, xc:int, yc:int, xcOld:int, ycOld:int, pos:int, num:int = 0, x2:int, y2:int, r_:int, g_:int, b_:int, rx:int, ry:int; var yiq:Array; y = iteration; for ( x = 0; x < width; x++) { xc = x; yc = y; pos = y * width + x; yiq = pixelsf[pos]; Yc = yiq[ 0 ]; Ic = yiq[ 1 ]; Qc = yiq[ 2 ]; iters = 0; do{ xcOld = xc; ycOld = yc; YcOld = Yc; IcOld = Ic; QcOld = Qc; mx = my = mY = mI = mQ = num = 0; //for each pixel 2D within the radius 2D around current pixel for ( ry = -_distance2D; ry <= _distance2D; ry++) { y2 = yc + ry; if ( y2 >= 0 && y2 < height) { for ( rx = -_distance2D; rx <= _distance2D; rx++) { x2 = xc + rx; if (x2 >= 0 && x2 < width) { if (ry * ry + rx * rx <= _distance2D2) { yiq = pixelsf[y2 * width + x2]; Y2 = yiq[ 0 ]; I2 = yiq[ 1 ]; Q2 = yiq[ 2 ]; dY = Yc - Y2; dI = Ic - I2; dQ = Qc - Q2; if (dY * dY + dI * dI + dQ * dQ <= _distance3D2) { mx += x2; my += y2; mY += Y2; mI += I2; mQ += Q2; num++; } } } } } } num_ = 1 / num; Yc = mY * num_; Ic = mI * num_; Qc = mQ * num_; xc = mx * num_ + .5; yc = my * num_ + .5; dx = xc - xcOld; dy = yc - ycOld; dY = Yc - YcOld; dI = Ic - IcOld; dQ = Qc - QcOld; shift = dx * dx + dy * dy + dY * dY + dI * dI + dQ * dQ; iters++; }while ( shift > 3 && iters < 100 ); r_ = int(Yc + 0.9563 * Ic + 0.6210 * Qc); g_ = int(Yc - 0.2721 * Ic - 0.6473 * Qc); b_ = int(Yc - 1.1070 * Ic + 1.7046 * Qc); bd.setPixel( x, y, (r_ << 16) | (g_ << 8) | b_ ); } } /** * assigns the bitmapdata to apply the filter to */ public function set bitmapData( src:BitmapData ):void { this.bd = src.clone(); width = bd.width; height = bd.height; var i:int, size:int; var argb:uint, r:uint, g:uint, b:uint; size = bd.width * bd.height; pixelsf = []; bd.lock(); for ( i = 0; i < size; i++) { argb = bd.getPixel( i % width, int( i / width ) ); r = (argb >> 16) & 0xff; g = (argb >> 8) & 0xff; b = (argb) & 0xff; pixelsf.push( [ 0.299 * r + 0.587 * g + 0.114 * b, // Y, 0.5957 * r - 0.2744 * g - 0.3212 * b, // I, 0.2114 * r - 0.5226 * g + 0.3111 * b // Q ] ); } bd.unlock(); } public function get bitmapData():BitmapData { return bd; }; /** * max distance to search in the physical(2d) space around the pixel */ public function set radius(value:Number):void { _distance2D = value; } public function get radius():Number { return _distance2D; } /** * max distance to search in the color space of the pixel */ public function set distance(value:Number):void { _distance3D = value; } public function get distance():Number { return _distance3D; } /** * percentage of the process currently achieved */ public function get percent():Number { return pct; } public function set percent(v:Number):void { pct = v; } /** * specifies if the filter is running */ public function get running():Boolean { return _running; } } }