avm2: Implement BitmapData.hitTest
This commit is contained in:
parent
a1176afcce
commit
a010bd0f7a
|
@ -21,6 +21,7 @@ use std::str::FromStr;
|
|||
|
||||
pub use crate::avm2::object::bitmap_data_allocator;
|
||||
use crate::avm2::parameters::{null_parameter_error, ParametersExt};
|
||||
use crate::display_object::TDisplayObject;
|
||||
|
||||
/// Copy the static data from a given Bitmap into a new BitmapData.
|
||||
///
|
||||
|
@ -667,10 +668,131 @@ pub fn unlock<'gc>(
|
|||
|
||||
pub fn hit_test<'gc>(
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
_this: Option<Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
this: Option<Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
avm2_stub_method!(activation, "flash.display.BitmapData", "hitTest");
|
||||
if let Some(bitmap_data) = this.and_then(|t| t.as_bitmap_data()) {
|
||||
if !bitmap_data.read().disposed() {
|
||||
let first_point = args.get_object(activation, 0, "firstPoint")?;
|
||||
let top_left = (
|
||||
first_point
|
||||
.get_public_property("x", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
first_point
|
||||
.get_public_property("y", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
);
|
||||
let source_threshold = args.get_u32(activation, 1)?;
|
||||
let compare_object = args.get_object(activation, 2, "secondObject")?;
|
||||
let point_class = activation.avm2().classes().point;
|
||||
let rectangle_class = activation.avm2().classes().rectangle;
|
||||
|
||||
if compare_object.is_of_type(point_class, activation) {
|
||||
let test_point = (
|
||||
compare_object
|
||||
.get_public_property("x", activation)?
|
||||
.coerce_to_i32(activation)?
|
||||
- top_left.0,
|
||||
compare_object
|
||||
.get_public_property("y", activation)?
|
||||
.coerce_to_i32(activation)?
|
||||
- top_left.1,
|
||||
);
|
||||
return Ok(Value::Bool(
|
||||
bitmap_data
|
||||
.read()
|
||||
.hit_test_point(source_threshold, test_point),
|
||||
));
|
||||
} else if compare_object.is_of_type(rectangle_class, activation) {
|
||||
let test_point = (
|
||||
compare_object
|
||||
.get_public_property("x", activation)?
|
||||
.coerce_to_i32(activation)?
|
||||
- top_left.0,
|
||||
compare_object
|
||||
.get_public_property("y", activation)?
|
||||
.coerce_to_i32(activation)?
|
||||
- top_left.1,
|
||||
);
|
||||
let size = (
|
||||
compare_object
|
||||
.get_public_property("width", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
compare_object
|
||||
.get_public_property("height", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
);
|
||||
return Ok(Value::Bool(bitmap_data.read().hit_test_rectangle(
|
||||
source_threshold,
|
||||
test_point,
|
||||
size,
|
||||
)));
|
||||
} else if let Some(other_bmd) = compare_object.as_bitmap_data() {
|
||||
other_bmd.read().check_valid(activation)?;
|
||||
let second_point = args.get_object(activation, 3, "secondBitmapDataPoint")?;
|
||||
let second_point = (
|
||||
second_point
|
||||
.get_public_property("x", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
second_point
|
||||
.get_public_property("y", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
);
|
||||
let second_threshold = args.get_u32(activation, 4)?;
|
||||
|
||||
let result = if GcCell::ptr_eq(bitmap_data, other_bmd) {
|
||||
bitmap_data.read().hit_test_bitmapdata(
|
||||
top_left,
|
||||
source_threshold,
|
||||
None,
|
||||
second_point,
|
||||
second_threshold,
|
||||
)
|
||||
} else {
|
||||
bitmap_data.read().hit_test_bitmapdata(
|
||||
top_left,
|
||||
source_threshold,
|
||||
Some(&other_bmd.read()),
|
||||
second_point,
|
||||
second_threshold,
|
||||
)
|
||||
};
|
||||
return Ok(Value::Bool(result));
|
||||
} else if let Some(bitmap) = compare_object
|
||||
.as_display_object()
|
||||
.and_then(|dobj| dobj.as_bitmap())
|
||||
{
|
||||
let other_bmd = bitmap.bitmap_data_wrapper().sync();
|
||||
other_bmd.read().check_valid(activation)?;
|
||||
let second_point = args.get_object(activation, 3, "secondBitmapDataPoint")?;
|
||||
let second_point = (
|
||||
second_point
|
||||
.get_public_property("x", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
second_point
|
||||
.get_public_property("y", activation)?
|
||||
.coerce_to_i32(activation)?,
|
||||
);
|
||||
let second_threshold = args.get_u32(activation, 4)?;
|
||||
|
||||
return Ok(Value::Bool(bitmap_data.read().hit_test_bitmapdata(
|
||||
top_left,
|
||||
source_threshold,
|
||||
Some(&other_bmd.read()),
|
||||
second_point,
|
||||
second_threshold,
|
||||
)));
|
||||
} else {
|
||||
// This is the error message Flash Player produces. Even though it's misleading.
|
||||
return Err(Error::AvmError(argument_error(
|
||||
activation,
|
||||
"Parameter 0 is of the incorrect type. Should be type BitmapData.",
|
||||
2005,
|
||||
)?));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false.into())
|
||||
}
|
||||
|
||||
|
|
|
@ -1362,6 +1362,72 @@ impl<'gc> BitmapData<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn hit_test_point(&self, alpha_threshold: u32, test_point: (i32, i32)) -> bool {
|
||||
self.get_pixel32(test_point.0, test_point.1).alpha() as u32 >= alpha_threshold
|
||||
}
|
||||
|
||||
pub fn hit_test_rectangle(
|
||||
&self,
|
||||
alpha_threshold: u32,
|
||||
top_left: (i32, i32),
|
||||
size: (i32, i32),
|
||||
) -> bool {
|
||||
for x in 0..size.0 {
|
||||
for y in 0..size.1 {
|
||||
if self.get_pixel32(top_left.0 + x, top_left.1 + y).alpha() as u32
|
||||
>= alpha_threshold
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn hit_test_bitmapdata(
|
||||
&self,
|
||||
self_point: (i32, i32),
|
||||
self_threshold: u32,
|
||||
test: Option<&BitmapData>,
|
||||
test_point: (i32, i32),
|
||||
test_threshold: u32,
|
||||
) -> bool {
|
||||
let xd = test_point.0 - self_point.0;
|
||||
let yd = test_point.1 - self_point.1;
|
||||
let self_width = self.width as i32;
|
||||
let self_height = self.height as i32;
|
||||
let (test_width, test_height) = if let Some(test) = test {
|
||||
(test.width as i32, test.height as i32)
|
||||
} else {
|
||||
(self_width, self_height)
|
||||
};
|
||||
let (self_x0, test_x0, width) = if xd < 0 {
|
||||
(0, -xd, self_width.min(test_width + xd))
|
||||
} else {
|
||||
(xd, 0, test_width.min(self_width - xd))
|
||||
};
|
||||
let (self_y0, test_y0, height) = if yd < 0 {
|
||||
(0, -yd, self_height.min(test_height + yd))
|
||||
} else {
|
||||
(yd, 0, test_height.min(self_height - yd))
|
||||
};
|
||||
for x in 0..width {
|
||||
for y in 0..height {
|
||||
let self_is_opaque =
|
||||
self.hit_test_point(self_threshold, (self_x0 + x, self_y0 + y));
|
||||
let test_is_opaque = if let Some(test) = test {
|
||||
test.hit_test_point(test_threshold, (test_x0 + x, test_y0 + y))
|
||||
} else {
|
||||
self.hit_test_point(test_threshold, (test_x0 + x, test_y0 + y))
|
||||
};
|
||||
if self_is_opaque && test_is_opaque {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
package {
|
||||
|
||||
import flash.display.MovieClip;
|
||||
import flash.display.Bitmap;
|
||||
import flash.display.BitmapData;
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
|
||||
public class Test extends MovieClip {
|
||||
|
||||
|
||||
public function Test() {
|
||||
var bmd: BitmapData = createImage();
|
||||
var otherBmd: BitmapData = createImage();
|
||||
var bitmap: Bitmap = new Bitmap(bmd);
|
||||
|
||||
|
||||
|
||||
// Testing bmd against bmd, aligns both images so both points overlap and checks for any opaque overlap
|
||||
trace("/// hitTest with bmd");
|
||||
test(bmd, new Point(0, 0), 0, bmd, new Point(0, 0), 0);
|
||||
test(bmd, new Point(1, 1), 0xFF, bmd, new Point(3, 3), 0xA0);
|
||||
test(bmd, new Point(2, 1), 0xA0, bmd, new Point(1, 3), 0xA0);
|
||||
test(bmd, new Point(3, 1), 0xA0, bmd, new Point(1, 2), 0xFF);
|
||||
test(bmd, new Point(0, 0), 0xA0, bmd, new Point(1, 0), 0xFF);
|
||||
test(bmd, new Point(1, 1), 0xFF, bmd, new Point(1, 1), 0xFF);
|
||||
trace("");
|
||||
|
||||
trace("/// hitTest with other bmd");
|
||||
test(bmd, new Point(0, 0), 0, otherBmd, new Point(0, 0), 0);
|
||||
test(bmd, new Point(1, 1), 0xFF, otherBmd, new Point(3, 3), 0xA0);
|
||||
test(bmd, new Point(2, 1), 0xA0, otherBmd, new Point(1, 3), 0xA0);
|
||||
test(bmd, new Point(3, 1), 0xA0, otherBmd, new Point(1, 2), 0xFF);
|
||||
test(bmd, new Point(0, 0), 0xA0, otherBmd, new Point(1, 0), 0xFF);
|
||||
test(bmd, new Point(1, 1), 0xFF, otherBmd, new Point(1, 1), 0xFF);
|
||||
trace("");
|
||||
|
||||
// Testing bmd against bitmap, same as above
|
||||
trace("/// hitTest with bitmap");
|
||||
test(bmd, new Point(0, 0), 0, bitmap, new Point(0, 0), 0);
|
||||
test(bmd, new Point(1, 1), 0xFF, bitmap, new Point(3, 3), 0xA0);
|
||||
test(bmd, new Point(2, 1), 0xA0, bitmap, new Point(1, 3), 0xA0);
|
||||
test(bmd, new Point(3, 1), 0xA0, bitmap, new Point(1, 2), 0xFF);
|
||||
trace("");
|
||||
|
||||
// Testing bmd against rect, offsets the rect by -firstPoint and then looks for any opaque pixel inside rect
|
||||
trace("/// hitTest with rect");
|
||||
test(bmd, new Point(0, 0), 0xA0, new Rectangle(2, 2, 2, 2));
|
||||
test(bmd, new Point(0, 0), 0xFF, new Rectangle(0, 0, 3, 4));
|
||||
test(bmd, new Point(0, 0), 0xFF, new Rectangle(2, 2, 1, 1));
|
||||
test(bmd, new Point(2, 2), 0xFF, new Rectangle(4, 4, 1, 1));
|
||||
trace("");
|
||||
|
||||
// Testing bmd against point, offsets the point by -firstPoint and then checks if that pixel is opaque
|
||||
trace("/// hitTest with point");
|
||||
test(bmd, new Point(0, 0), 0xA0, new Point(2, 2));
|
||||
test(bmd, new Point(0, 0), 0xFF, new Point(0, 0));
|
||||
test(bmd, new Point(0, 0), 0xFF, new Point(2, 2));
|
||||
test(bmd, new Point(2, 2), 0xFF, new Point(4, 4));
|
||||
trace("");
|
||||
|
||||
trace("/// Error cases")
|
||||
|
||||
try {
|
||||
test(bmd, new Point(0, 0), 0x00, bmd, null);
|
||||
} catch (error: Error) {
|
||||
trace("- Error " + error.errorID);
|
||||
}
|
||||
|
||||
try {
|
||||
test(bmd, new Point(0, 0), 0x00, {});
|
||||
} catch (error: Error) {
|
||||
trace("- Error " + error.errorID);
|
||||
}
|
||||
}
|
||||
|
||||
// BMD looks like: ('-' is no alpha, 'x' is 0xA0, 'X' is 0xFF)
|
||||
/* 0 1 2 3 4
|
||||
* 0 - - - - -
|
||||
* 1 - x x x -
|
||||
* 2 - x X x -
|
||||
* 3 - x x x -
|
||||
* 4 - - - - -
|
||||
*/
|
||||
function createImage():BitmapData {
|
||||
var bmd: BitmapData = new BitmapData(5, 5, true, 0);
|
||||
for (var x = 1; x <= 3; x++) {
|
||||
for (var y = 1; y <= 3; y++) {
|
||||
bmd.setPixel32(x, y, 0xA0FFFFFF);
|
||||
}
|
||||
}
|
||||
bmd.setPixel32(2, 2, 0xFFFFFFFF);
|
||||
return bmd;
|
||||
}
|
||||
|
||||
function formatPoint(point: Point): String {
|
||||
if (point) {
|
||||
return "new Point(" + point.x + ", " + point.y + ")";
|
||||
} else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
function formatRectangle(rect: Rectangle): String {
|
||||
if (rect) {
|
||||
return "new Rectangle(" + rect.x + ", " + rect.y + ", " + rect.width + ", " + rect.height + ")";
|
||||
} else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
function formatObject(bmd: BitmapData, object: Object): String {
|
||||
if (object === bmd) {
|
||||
return "bmd";
|
||||
} else if (object is Point) {
|
||||
return formatPoint(object as Point);
|
||||
} else if (object is Rectangle) {
|
||||
return formatRectangle(object as Rectangle);
|
||||
} else if (object is BitmapData) {
|
||||
return "otherBitmapData";
|
||||
} else if (object is Bitmap) {
|
||||
return "otherBitmap";
|
||||
} else if (object === null) {
|
||||
return "null";
|
||||
} else {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
function test(bmd: BitmapData, firstPoint:Point, firstAlphaThreshold:uint, secondObject:Object, secondBitmapDataPoint:Point = null, secondAlphaThreshold:uint = 1) {
|
||||
trace("// bmd.hitTest(" + formatPoint(firstPoint) + ", " + firstAlphaThreshold + ", " + formatObject(bmd, secondObject) + ", " + formatPoint(secondBitmapDataPoint) + ", " + secondAlphaThreshold + ")");
|
||||
trace(bmd.hitTest(firstPoint, firstAlphaThreshold, secondObject, secondBitmapDataPoint, secondAlphaThreshold));
|
||||
trace("");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/// hitTest with bmd
|
||||
// bmd.hitTest(new Point(0, 0), 0, bmd, new Point(0, 0), 0)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(1, 1), 255, bmd, new Point(3, 3), 160)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(2, 1), 160, bmd, new Point(1, 3), 160)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(3, 1), 160, bmd, new Point(1, 2), 255)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 160, bmd, new Point(1, 0), 255)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(1, 1), 255, bmd, new Point(1, 1), 255)
|
||||
true
|
||||
|
||||
|
||||
/// hitTest with other bmd
|
||||
// bmd.hitTest(new Point(0, 0), 0, otherBitmapData, new Point(0, 0), 0)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(1, 1), 255, otherBitmapData, new Point(3, 3), 160)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(2, 1), 160, otherBitmapData, new Point(1, 3), 160)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(3, 1), 160, otherBitmapData, new Point(1, 2), 255)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 160, otherBitmapData, new Point(1, 0), 255)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(1, 1), 255, otherBitmapData, new Point(1, 1), 255)
|
||||
true
|
||||
|
||||
|
||||
/// hitTest with bitmap
|
||||
// bmd.hitTest(new Point(0, 0), 0, otherBitmap, new Point(0, 0), 0)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(1, 1), 255, otherBitmap, new Point(3, 3), 160)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(2, 1), 160, otherBitmap, new Point(1, 3), 160)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(3, 1), 160, otherBitmap, new Point(1, 2), 255)
|
||||
false
|
||||
|
||||
|
||||
/// hitTest with rect
|
||||
// bmd.hitTest(new Point(0, 0), 160, new Rectangle(2, 2, 2, 2), null, 1)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 255, new Rectangle(0, 0, 3, 4), null, 1)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 255, new Rectangle(2, 2, 1, 1), null, 1)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(2, 2), 255, new Rectangle(4, 4, 1, 1), null, 1)
|
||||
true
|
||||
|
||||
|
||||
/// hitTest with point
|
||||
// bmd.hitTest(new Point(0, 0), 160, new Point(2, 2), null, 1)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 255, new Point(0, 0), null, 1)
|
||||
false
|
||||
|
||||
// bmd.hitTest(new Point(0, 0), 255, new Point(2, 2), null, 1)
|
||||
true
|
||||
|
||||
// bmd.hitTest(new Point(2, 2), 255, new Point(4, 4), null, 1)
|
||||
true
|
||||
|
||||
|
||||
/// Error cases
|
||||
// bmd.hitTest(new Point(0, 0), 0, bmd, null, 1)
|
||||
- Error 2007
|
||||
// bmd.hitTest(new Point(0, 0), 0, {}, null, 1)
|
||||
- Error 2005
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
num_frames = 1
|
Loading…
Reference in New Issue