Implementing BitmapData.threshold() natively for massive speedup - 95% reduction in time taken
This commit is contained in:
committed by
Joshua Granick
parent
91dd2b13cc
commit
3644008132
@@ -830,6 +830,12 @@ class Image {
|
||||
|
||||
}
|
||||
|
||||
public function threshold(sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, operation:String, threshold:Int, color:Int = 0x00000000, mask:Int = 0xFFFFFFFF, copySource:Bool = false):Int {
|
||||
|
||||
return ImageDataUtil.threshold(this, sourceImage, sourceRect, destPoint, operation, threshold, color, mask, copySource);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function setPixel32 (x:Int, y:Int, color:Int, format:PixelFormat = null):Void {
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import haxe.io.Bytes;
|
||||
import lime.graphics.Image;
|
||||
import lime.graphics.ImageBuffer;
|
||||
import lime.graphics.PixelFormat;
|
||||
import lime.graphics.utils.ImageDataUtil.Operation;
|
||||
import lime.math.color.ARGB;
|
||||
import lime.math.color.BGRA;
|
||||
import lime.math.color.RGBA;
|
||||
@@ -1057,6 +1058,186 @@ class ImageDataUtil {
|
||||
}
|
||||
|
||||
|
||||
public static function threshold (destImage:Image, sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, operation:String, threshold:Int, color:Int = 0x00000000, mask:Int = 0xFFFFFFFF, copySource:Bool = false):Int {
|
||||
|
||||
var thresholdMask:Int = threshold & mask;
|
||||
|
||||
var a = (thresholdMask >> 24) & 0xFF;
|
||||
var r = (thresholdMask >> 16) & 0xFF;
|
||||
var g = (thresholdMask >> 8) & 0xFF;
|
||||
var b = (thresholdMask ) & 0xFF;
|
||||
|
||||
var thresholdRGBA:RGBA = RGBA.create(r, g, b, a);
|
||||
|
||||
a = (color >> 24) & 0xFF;
|
||||
r = (color >> 16) & 0xFF;
|
||||
g = (color >> 8) & 0xFF;
|
||||
b = (color ) & 0xFF;
|
||||
|
||||
var colorRGBA:RGBA = RGBA.create(r, g, b, a);
|
||||
|
||||
var operationEnum:Operation = switch(operation) {
|
||||
|
||||
case "==": EQUALS;
|
||||
case "<" : LESS_THAN;
|
||||
case ">" : GREATER_THAN;
|
||||
case "<=": LESS_THAN_OR_EQUAL_TO;
|
||||
case ">=": GREATER_THAN_OR_EQUAL_TO;
|
||||
case "!=": NOT_EQUALS;
|
||||
default : EQUALS;
|
||||
|
||||
}
|
||||
|
||||
var hits:Int = 0;
|
||||
|
||||
if (sourceImage == destImage && sourceRect.equals (destImage.rect) && destPoint.x == 0 && destPoint.y == 0) {
|
||||
|
||||
var pixelMask:Int, i, test;
|
||||
|
||||
if (CFFI.enabled) {
|
||||
|
||||
hits = lime_image_data_util_threshold_inner_loop (destImage, sourceImage, sourceRect, mask, thresholdRGBA, operationEnum, colorRGBA, new Rectangle(0, 0, sourceRect.width, sourceRect.height));
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
hits = __threshold_inner_loop (destImage, sourceImage, sourceRect, mask, thresholdRGBA, operationEnum, colorRGBA, 0, 0, Std.int(sourceRect.width), Std.int(sourceRect.height));
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
var destData = destImage.buffer.data;
|
||||
var destFormat = destImage.buffer.format;
|
||||
var destPremultiplied = destImage.buffer.premultiplied;
|
||||
|
||||
sourceRect = sourceRect.clone ();
|
||||
|
||||
if (sourceRect.right > sourceImage.width) {
|
||||
|
||||
sourceRect.width = sourceImage.width - sourceRect.x;
|
||||
|
||||
}
|
||||
|
||||
if (sourceRect.bottom > sourceImage.height) {
|
||||
|
||||
sourceRect.height = sourceImage.height - sourceRect.y;
|
||||
|
||||
}
|
||||
|
||||
var targetRect = sourceRect.clone ();
|
||||
targetRect.offsetPoint (destPoint);
|
||||
|
||||
if (targetRect.right > destImage.width) {
|
||||
|
||||
targetRect.width = destImage.width - targetRect.x;
|
||||
|
||||
}
|
||||
|
||||
if (targetRect.bottom > destImage.height) {
|
||||
|
||||
targetRect.height = destImage.height - targetRect.y;
|
||||
|
||||
}
|
||||
|
||||
sourceRect.width = Math.min (sourceRect.width, targetRect.width);
|
||||
sourceRect.height = Math.min (sourceRect.height, targetRect.height);
|
||||
|
||||
var sx = Std.int (sourceRect.x);
|
||||
var sy = Std.int (sourceRect.y);
|
||||
var sw = Std.int (sourceRect.width);
|
||||
var sh = Std.int (sourceRect.height);
|
||||
|
||||
var dx = Std.int (destPoint.x);
|
||||
var dy = Std.int (destPoint.y);
|
||||
|
||||
var bw:Int = destImage.width - sw - dx;
|
||||
var bh:Int = destImage.height - sh - dy;
|
||||
|
||||
var dw:Int = (bw < 0) ? sw + (destImage.width - sw - dx) : sw;
|
||||
var dh:Int = (bw < 0) ? sh + (destImage.height - sh - dy) : sh;
|
||||
|
||||
if (copySource) {
|
||||
|
||||
destImage.copyPixels(sourceImage, sourceRect, destPoint);
|
||||
|
||||
}
|
||||
|
||||
var pixelMask:Int, i, test;
|
||||
|
||||
if (CFFI.enabled) {
|
||||
|
||||
hits = lime_image_data_util_threshold_inner_loop (destImage, sourceImage, sourceRect, mask, thresholdRGBA, operationEnum, cast colorRGBA, new Rectangle(sx, sy, dw, dh));
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
hits = __threshold_inner_loop (destImage, sourceImage, sourceRect, mask, thresholdRGBA, operationEnum, colorRGBA, sx, sy, dw, dh);
|
||||
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static inline function __threshold_inner_loop (image:Image, sourceImage:Image, sourceRect:Rectangle, mask:Int, threshold:Int, operation:Operation, color:Int, startX:Int, startY:Int, destWidth:Int, destHeight:Int):Int {
|
||||
|
||||
var srcView = new ImageDataView(sourceImage, sourceRect);
|
||||
var srcPixel:RGBA = 0;
|
||||
var srcPosition:Int = 0;
|
||||
var srcFormat = sourceImage.buffer.format;
|
||||
var srcPremultiplied = sourceImage.buffer.premultiplied;
|
||||
var srcData = sourceImage.buffer.data;
|
||||
|
||||
var colorRGBA:RGBA = color;
|
||||
|
||||
var pixelMask:Int = 0;
|
||||
var i = 0;
|
||||
var test = false;
|
||||
var hits = 0;
|
||||
|
||||
for (yy in 0...destHeight) {
|
||||
|
||||
srcPosition = srcView.row (yy + startY);
|
||||
srcPosition += (4 * startX);
|
||||
|
||||
for (xx in 0...destWidth) {
|
||||
|
||||
srcPixel.readUInt8 (srcData, srcPosition, srcFormat, srcPremultiplied);
|
||||
pixelMask = srcPixel & mask;
|
||||
|
||||
i = __ucompare (pixelMask, threshold);
|
||||
|
||||
test = switch(operation) {
|
||||
|
||||
case EQUALS: (i == 0);
|
||||
case LESS_THAN: (i == -1);
|
||||
case GREATER_THAN: (i == 1);
|
||||
case NOT_EQUALS: (i != 0);
|
||||
case LESS_THAN_OR_EQUAL_TO: (i == 0 || i == -1);
|
||||
case GREATER_THAN_OR_EQUAL_TO: (i == 0 || i == 1);
|
||||
default: false;
|
||||
|
||||
}
|
||||
|
||||
if (test) {
|
||||
|
||||
colorRGBA.writeUInt8(srcData, srcPosition, srcFormat, srcPremultiplied);
|
||||
hits++;
|
||||
|
||||
}
|
||||
|
||||
srcPosition += 4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
|
||||
public static function unmultiplyAlpha (image:Image):Void {
|
||||
|
||||
var data = image.buffer.data;
|
||||
@@ -1086,6 +1267,58 @@ class ImageDataUtil {
|
||||
}
|
||||
|
||||
|
||||
private static function __ucompare (n1:Int, n2:Int) : Int {
|
||||
|
||||
var tmp1 : Int;
|
||||
var tmp2 : Int;
|
||||
|
||||
tmp1 = (n1 >> 24) & 0xFF;
|
||||
tmp2 = (n2 >> 24) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = (n1 >> 16) & 0xFF;
|
||||
tmp2 = (n2 >> 16) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = (n1 >> 8) & 0xFF;
|
||||
tmp2 = (n2 >> 8) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = n1 & 0xFF;
|
||||
tmp2 = n2 & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Native Methods
|
||||
@@ -1105,6 +1338,7 @@ class ImageDataUtil {
|
||||
@:cffi private static function lime_image_data_util_resize (image:Dynamic, buffer:Dynamic, width:Int, height:Int):Void;
|
||||
@:cffi private static function lime_image_data_util_set_format (image:Dynamic, format:Int):Void;
|
||||
@:cffi private static function lime_image_data_util_set_pixels (image:Dynamic, rect:Dynamic, bytes:Dynamic, format:Int):Void;
|
||||
@:cffi private static function lime_image_data_util_threshold_inner_loop (image:Dynamic, sourceImage:Image, sourceRect:Dynamic, mask:Int, threshold:Int, operation:Int, color:Int, destRect:Dynamic):Int;
|
||||
@:cffi private static function lime_image_data_util_unmultiply_alpha (image:Dynamic):Void;
|
||||
#end
|
||||
|
||||
@@ -1177,4 +1411,14 @@ private class ImageDataView {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@:enum abstract Operation(Int) from Int to Int{
|
||||
var EQUALS = 0;
|
||||
var LESS_THAN = 1;
|
||||
var GREATER_THAN = 2;
|
||||
var LESS_THAN_OR_EQUAL_TO = 3;
|
||||
var GREATER_THAN_OR_EQUAL_TO = 4;
|
||||
var NOT_EQUALS = 5;
|
||||
}
|
||||
@@ -32,6 +32,8 @@ namespace lime {
|
||||
static void Resize (Image* image, ImageBuffer* buffer, int width, int height);
|
||||
static void SetFormat (Image* image, PixelFormat format);
|
||||
static void SetPixels (Image* image, Rectangle* rect, Bytes* bytes, PixelFormat format);
|
||||
static int ThresholdInnerLoop (Image* image, Image* sourceImage, Rectangle* sourceRect, int mask, int threshold, int operation, int color, Rectangle* destRect);
|
||||
static int Ucompare (int n1, int n2);
|
||||
static void UnmultiplyAlpha (Image* image);
|
||||
|
||||
|
||||
|
||||
@@ -769,6 +769,15 @@ namespace lime {
|
||||
|
||||
}
|
||||
|
||||
int lime_image_data_util_threshold_inner_loop (value image, value sourceImage, value sourceRect, int mask, int threshold, int operation, int color, value destRect) {
|
||||
|
||||
Image _image = Image (image);
|
||||
Image _sourceImage = Image (sourceImage);
|
||||
Rectangle _sourceRect = Rectangle (sourceRect);
|
||||
Rectangle _destRect = Rectangle (destRect);
|
||||
return ImageDataUtil::ThresholdInnerLoop (&_image, &_sourceImage, &_sourceRect, mask, threshold, operation, color, &_destRect);
|
||||
|
||||
}
|
||||
|
||||
void lime_image_data_util_unmultiply_alpha (value image) {
|
||||
|
||||
@@ -1411,6 +1420,7 @@ namespace lime {
|
||||
DEFINE_PRIME4v (lime_image_data_util_resize);
|
||||
DEFINE_PRIME2v (lime_image_data_util_set_format);
|
||||
DEFINE_PRIME4v (lime_image_data_util_set_pixels);
|
||||
DEFINE_PRIME8v (lime_image_data_util_threshold_inner_loop);
|
||||
DEFINE_PRIME1v (lime_image_data_util_unmultiply_alpha);
|
||||
DEFINE_PRIME3 (lime_image_encode);
|
||||
DEFINE_PRIME1 (lime_image_load);
|
||||
|
||||
@@ -606,6 +606,124 @@ namespace lime {
|
||||
}
|
||||
|
||||
|
||||
int ImageDataUtil::ThresholdInnerLoop (Image* image, Image* sourceImage, Rectangle* sourceRect, int mask, int threshold, int operation, int color, Rectangle* destRect) {
|
||||
|
||||
int startX = (int)destRect->x;
|
||||
int startY = (int)destRect->y;
|
||||
int destWidth = (int)destRect->width;
|
||||
int destHeight = (int)destRect->height;
|
||||
|
||||
ImageDataView srcView = ImageDataView (sourceImage, sourceRect);
|
||||
RGBA srcPixel;
|
||||
int srcPosition = 0;
|
||||
PixelFormat srcFormat = image->buffer->format;
|
||||
bool srcPremultiplied = image->buffer->premultiplied;
|
||||
uint8_t* srcData = (uint8_t*)sourceImage->buffer->data->Data ();
|
||||
|
||||
RGBA colorRGBA = RGBA (color);
|
||||
|
||||
int pixelMask = 0;
|
||||
int i = 0;
|
||||
bool test = false;
|
||||
int hits = 0;
|
||||
|
||||
for(int yy = 0; yy < destHeight; yy++) {
|
||||
|
||||
srcPosition = srcView.Row (yy + startY);
|
||||
srcPosition += (4 * startX);
|
||||
|
||||
for(int xx = 0; xx < destWidth; xx++) {
|
||||
|
||||
srcPixel.ReadUInt8 (srcData, srcPosition, srcFormat, srcPremultiplied);
|
||||
pixelMask = srcPixel.Get() & mask;
|
||||
|
||||
i = Ucompare (pixelMask, threshold);
|
||||
|
||||
switch(operation) {
|
||||
|
||||
case 0: test = (i == 0); //EQUALS
|
||||
break;
|
||||
case 1: test = (i == -1); //LESS_THAN
|
||||
break;
|
||||
case 2: test = (i == 1); //GREATER_THAN
|
||||
break;
|
||||
case 3: test = (i != 0); //NOT_EQUALS
|
||||
break;
|
||||
case 4: test = (i == 0 || i == -1); //LESS_THAN_OR_EQUAL_TO
|
||||
break;
|
||||
case 5: test = (i == 0 || i == 1); //GREATER_THAN_OR_EQUAL_TO
|
||||
break;
|
||||
}
|
||||
|
||||
if (test) {
|
||||
|
||||
colorRGBA.WriteUInt8 (srcData, srcPosition, srcFormat, srcPremultiplied);
|
||||
hits++;
|
||||
|
||||
}
|
||||
|
||||
srcPosition += 4;
|
||||
}
|
||||
}
|
||||
|
||||
return hits;
|
||||
}
|
||||
|
||||
int ImageDataUtil::Ucompare (int n1, int n2) {
|
||||
|
||||
int tmp1;
|
||||
int tmp2;
|
||||
|
||||
tmp1 = (n1 >> 24) & 0xFF;
|
||||
tmp2 = (n2 >> 24) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = (n1 >> 16) & 0xFF;
|
||||
tmp2 = (n2 >> 16) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = (n1 >> 8) & 0xFF;
|
||||
tmp2 = (n2 >> 8) & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
tmp1 = n1 & 0xFF;
|
||||
tmp2 = n2 & 0xFF;
|
||||
|
||||
if (tmp1 != tmp2) {
|
||||
|
||||
return (tmp1 > tmp2 ? 1 : -1);
|
||||
|
||||
} else {
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void ImageDataUtil::UnmultiplyAlpha (Image* image) {
|
||||
|
||||
PixelFormat format = image->buffer->format;
|
||||
|
||||
Reference in New Issue
Block a user