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;
|
pub use crate::avm2::object::bitmap_data_allocator;
|
||||||
use crate::avm2::parameters::{null_parameter_error, ParametersExt};
|
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.
|
/// 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>(
|
pub fn hit_test<'gc>(
|
||||||
activation: &mut Activation<'_, 'gc>,
|
activation: &mut Activation<'_, 'gc>,
|
||||||
_this: Option<Object<'gc>>,
|
this: Option<Object<'gc>>,
|
||||||
_args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Value<'gc>, Error<'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())
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&mut self,
|
&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