diff --git a/lime/graphics/Image.hx b/lime/graphics/Image.hx index c450193d7..b5a506133 100644 --- a/lime/graphics/Image.hx +++ b/lime/graphics/Image.hx @@ -587,6 +587,30 @@ class Image { } + public function gaussianBlur (sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, blurX:Float = 4, blurY:Float = 4, quality:Int = 1, strength:Int = 1):Void { + + switch (type) { + + case CANVAS, DATA: + + #if (js && html5) + ImageCanvasUtil.convertToData (this); + ImageCanvasUtil.convertToData (sourceImage); + #end + + return ImageDataUtil.gaussianBlur (this, sourceImage, sourceRect, destPoint, blurX, blurY, quality, strength); + + case FLASH: + + // TODO + + default: + + } + + } + + public function getColorBoundsRect (mask:Int, color:Int, findColor:Bool = true, format:PixelFormat = null):Rectangle { if (buffer == null) return null; diff --git a/lime/graphics/utils/ImageDataUtil.hx b/lime/graphics/utils/ImageDataUtil.hx index dc838cd26..3e7bcacb3 100644 --- a/lime/graphics/utils/ImageDataUtil.hx +++ b/lime/graphics/utils/ImageDataUtil.hx @@ -503,6 +503,170 @@ class ImageDataUtil { } + public static function gaussianBlur (image:Image, sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, blurX:Float = 4, blurY:Float = 4, quality:Int = 1, strength:Int = 1):Void { + + // TODO: Support sourceRect better, do not modify sourceImage, create C++ implementation for native + + var fromPreMult = function (col:Float, alpha:Float):Int { + var col = Std.int (col / alpha * 255) ; + return col < 0 ? 0 : (col > 255 ? 255 : col); + } + + var boxesForGauss = function (sigma:Float, n:Int):Array { + var wIdeal = Math.sqrt((12*sigma*sigma/n)+1); // Ideal averaging filter width + var wl = Math.floor(wIdeal); + if (wl % 2 == 0) wl--; + var wu = wl+2; + + var mIdeal = (12*sigma*sigma - n*wl*wl - 4*n*wl - 3*n)/(-4*wl - 4); + var m = Math.round(mIdeal); + var sizes:Array = []; + for (i in 0...n) + sizes.push( i < m ? wl : wu); + + return sizes; + } + + var boxBlurH = function (imgA:UInt8Array, imgB:UInt8Array, w:Int, h:Int, r:Int, off:Int):Void { + var iarr = 1 / (r+r+1); + for (i in 0...h) { + var ti = i*w, li = ti, ri = ti+r; + var fv = imgA[ti * 4 + off], lv = imgA[(ti+w-1) * 4 + off], val = (r+1)*fv; + + for (j in 0...r) + val += imgA[(ti+j) * 4 + off]; + + for (j in 0...r+1) { + val += imgA[ri * 4 + off] - fv; + imgB[ti * 4 + off] = Math.round(val*iarr); + ri++; + ti++; + } + + for (j in r+1...w-r) { + val += imgA[ri * 4 + off] - imgA[li * 4 + off]; + imgB[ti * 4 + off] = Math.round(val*iarr); + ri++; + li++; + ti++; + } + + for (j in w-r...w) { + val += lv - imgA[li * 4 + off]; + imgB[ti * 4 + off] = Math.round(val*iarr); + li++; + ti++; + } + } + } + + var boxBlurT = function (imgA:UInt8Array, imgB:UInt8Array, w:Int, h:Int, r:Int, off:Int):Void { + var iarr = 1 / (r+r+1); + var ws = w * 4; + for (i in 0...w) { + var ti = i * 4 + off, li = ti, ri = ti+(r*ws); + var fv = imgA[ti], lv = imgA[ti+(ws*(h-1))], val = (r+1)*fv; + for (j in 0...r) + val += imgA[ti+j*ws]; + + for (j in 0...r+1) { + val += imgA[ri] - fv; + imgB[ti] = Math.round(val*iarr); + ri+=ws; ti+=ws; + } + + for (j in r+1...h-r) { + val += imgA[ri] - imgA[li]; + imgB[ti] = Math.round(val*iarr); + li+=ws; + ri+=ws; + ti+=ws; + } + + for (j in h-r...h) { + val += lv - imgA[li]; + imgB[ti] = Math.round(val*iarr); + li+=ws; + ti+=ws; + } + } + } + + var boxBlur = function (imgA:UInt8Array, imgB:UInt8Array, w:Int, h:Int, bx:Float, by:Float):Void { + for(i in 0...imgA.length) + imgB[i] = imgA[i]; + + boxBlurH(imgB, imgA, w, h, Std.int(bx), 0); + boxBlurH(imgB, imgA, w, h, Std.int(bx), 1); + boxBlurH(imgB, imgA, w, h, Std.int(bx), 2); + boxBlurH(imgB, imgA, w, h, Std.int(bx), 3); + + boxBlurT(imgA, imgB, w, h, Std.int(by), 0); + boxBlurT(imgA, imgB, w, h, Std.int(by), 1); + boxBlurT(imgA, imgB, w, h, Std.int(by), 2); + boxBlurT(imgA, imgB, w, h, Std.int(by), 3); + } + + var imgB = image.data; + var imgA = sourceImage.data; + var w = Std.int (sourceRect.width); + var h = Std.int (sourceRect.height); + var bx = Std.int (blurX); + var by = Std.int (blurY); + var oX = Std.int (destPoint.x); + var oY = Std.int (destPoint.y); + + var n = (quality * 2) - 1; + var rng = Math.pow(2, quality) * 0.125; + + var bxs = boxesForGauss(bx * rng, n); + var bys = boxesForGauss(by * rng, n); + var offset:Int = Std.int( (w * oY + oX) * 4 ); + + boxBlur (imgA, imgB, w, h, (bxs[0]-1)/2, (bys[0]-1)/2); + var bIndex:Int = 1; + for (i in 0...Std.int(n / 2)) { + boxBlur (imgB, imgA, w, h, (bxs[bIndex]-1)/2, (bys[bIndex]-1)/2); + boxBlur (imgA, imgB, w, h, (bxs[bIndex+1]-1)/2, (bys[bIndex+1]-1)/2); + + bIndex += 2; + } + + var i:Int = 0; + var a:Int; + if (offset < 0) { + while (i < imgA.length) { + a = Std.int(imgB[ i + 3 ] * strength ); + a = a < 0 ? 0 : (a > 255 ? 255 : a); + imgB[ i ] = fromPreMult( imgB[ i ], a ); + imgB[ i + 1 ] = fromPreMult( imgB[ i + 1 ], a ); + imgB[ i + 2 ] = fromPreMult( imgB[ i + 2 ], a ); + imgB[ i + 3 ] = a; + i += 4; + } + for (i in imgA.length - offset...imgA.length) + imgB[ i ] = 0; + } else { + i = imgA.length - 4; + while (i >= 0) { + a = Std.int(imgB[ i + 3 ] * strength ); + a = a < 0 ? 0 : (a > 255 ? 255 : a); + imgB[ i + offset] = fromPreMult( imgB[ i ], a ); + imgB[ i + 1 + offset] = fromPreMult( imgB[ i + 1 ], a ); + imgB[ i + 2 + offset] = fromPreMult( imgB[ i + 2 ], a ); + imgB[ i + 3 + offset] = a; + i -= 4; + } + for (i in 0...offset) + imgB[ i ] = 0; + } + + image.dirty = true; + image.version++; + + } + + public static function getColorBoundsRect (image:Image, mask:Int, color:Int, findColor:Bool = true, format:PixelFormat):Rectangle { var left = image.width + 1;