Implementing BitmapData.threshold() natively for massive speedup - 95% reduction in time taken

This commit is contained in:
Lars A. Doucet
2015-12-28 17:35:54 -06:00
committed by Joshua Granick
parent 91dd2b13cc
commit 3644008132
5 changed files with 380 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;