From a010bd0f7a55dccade2d6368b8df3d9fb9b19a70 Mon Sep 17 00:00:00 2001 From: Nathan Adams Date: Thu, 16 Mar 2023 09:09:48 +0100 Subject: [PATCH] avm2: Implement BitmapData.hitTest --- .../avm2/globals/flash/display/bitmap_data.rs | 128 +++++++++++++++- core/src/bitmap/bitmap_data.rs | 66 +++++++++ .../swfs/avm2/bitmapdata_hittest/Test.as | 138 ++++++++++++++++++ .../swfs/avm2/bitmapdata_hittest/output.txt | 87 +++++++++++ .../swfs/avm2/bitmapdata_hittest/test.fla | Bin 0 -> 4424 bytes .../swfs/avm2/bitmapdata_hittest/test.swf | Bin 0 -> 1631 bytes .../swfs/avm2/bitmapdata_hittest/test.toml | 1 + 7 files changed, 417 insertions(+), 3 deletions(-) create mode 100644 tests/tests/swfs/avm2/bitmapdata_hittest/Test.as create mode 100644 tests/tests/swfs/avm2/bitmapdata_hittest/output.txt create mode 100644 tests/tests/swfs/avm2/bitmapdata_hittest/test.fla create mode 100644 tests/tests/swfs/avm2/bitmapdata_hittest/test.swf create mode 100644 tests/tests/swfs/avm2/bitmapdata_hittest/test.toml diff --git a/core/src/avm2/globals/flash/display/bitmap_data.rs b/core/src/avm2/globals/flash/display/bitmap_data.rs index 632324b98..37ebf7010 100644 --- a/core/src/avm2/globals/flash/display/bitmap_data.rs +++ b/core/src/avm2/globals/flash/display/bitmap_data.rs @@ -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>, - _args: &[Value<'gc>], + this: Option>, + args: &[Value<'gc>], ) -> Result, 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()) } diff --git a/core/src/bitmap/bitmap_data.rs b/core/src/bitmap/bitmap_data.rs index 05ee3e8b4..130875fde 100644 --- a/core/src/bitmap/bitmap_data.rs +++ b/core/src/bitmap/bitmap_data.rs @@ -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, diff --git a/tests/tests/swfs/avm2/bitmapdata_hittest/Test.as b/tests/tests/swfs/avm2/bitmapdata_hittest/Test.as new file mode 100644 index 000000000..51425e7f8 --- /dev/null +++ b/tests/tests/swfs/avm2/bitmapdata_hittest/Test.as @@ -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(""); + } + } + +} diff --git a/tests/tests/swfs/avm2/bitmapdata_hittest/output.txt b/tests/tests/swfs/avm2/bitmapdata_hittest/output.txt new file mode 100644 index 000000000..e4f6ee5b5 --- /dev/null +++ b/tests/tests/swfs/avm2/bitmapdata_hittest/output.txt @@ -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 diff --git a/tests/tests/swfs/avm2/bitmapdata_hittest/test.fla b/tests/tests/swfs/avm2/bitmapdata_hittest/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..195dbd997705a0b45a7c09b9e186989e1e567df1 GIT binary patch literal 4424 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 literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/bitmapdata_hittest/test.swf b/tests/tests/swfs/avm2/bitmapdata_hittest/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..b2ef1196942e82029ba066772e3ac2c7e8d19c07 GIT binary patch literal 1631 zcmV-l2B7&vS5prb4gdgnoTXMrq;t}lR1NK*?u z=klFz&SiGi+erEbp}}2*Mls3^k0OMAJToLAbk8glQ%^EkdAm}sS}D-(kC$w_mQs|h zt*zKrGG>??N_=*9R#6j5A`t~d)Y_@q+IF;RT_2y59fl0E@}^$1b)zbCUCSAp_Wkkk zChc~m)~2mmF|At73n@qC}bJZ})bMqV}XUm#Zk{_E!ky#ed zYh^k8^sXG0bDMg(AWzQ3?U1hOlbV{kqsCLJdMMUK zx}nR=)(V=fbp_9+CTCKUvxkF^V4LAaK`-unP;dt+-&2lt^22&t$hTWt+ceA0yb5`R zm05*VZ425PciLOXr;3JI(d;>`Rx9gyjT0){QLAL+zuVHDv1pOE`kvAOer!rm+4A!H z2VSWtEwp7nVa4~Ot<{|xQ=Tx(*fjGD;Mbc6W_dZaRJClanrBOyIWWX@y^y+_o=;}e zNi{Q_PA+8P@tK9`2kGS0!pwYfIysY0IK4j_+G5KX`Ay!pCR^b@upJ6*v8|Z;h7L!z z2WujcP?Pb=M0)D|tUV~&xiV9K#tK=}s5n!oX{N<^r|yrpTE*Ms^f#qimovy%H3WN{O$;wrOj-?H&cXX&zD3*?>4X{n-Zuz>qq zI>Cdk+@|Pr^(K)G93D3X$u_k-JF6&)T+(e$E^p~}NzPRY7rTsxU1BD8o$tmwZ0EY{ zChQmNveuj&xp~1f4O7l*7PGw3rbZcGmNJr~N{%MLu3l&V2bF@y-OXw%E^Sk z%C=;u@(H|6A~N2=x5esaxjeuf?KVtETYAARc}h&*DA|K9UwagRLpu5X2gr}t8f#`g zL7PIKJ1O>zU!a2ve96FAH9HT{{TX+{Zl{Nrsy2fL+i=2MOYn$4ZNRHiuQJo~v}Woy zqd^=Lg3_q}ybx5-HHrx(A{9JTppO(O3guuu@w61;` zexufx5X1GX4?V{f+?EK#YXc0I@uLC!e4rw%4Mxta4B|RBtqCC%ek70(I;ySods=?X zEjJptsS1v|iw_}42)njcL`dku2wfQB(jLN~@G%JLf*ebxr2+z~5}+zr&h{*hoK_q; zy*Tm##gWsCBc~TfPAcwP&*FsBiW5#RPWXW0gk!~Z@(Lc$3j)@}5DA9?E{ZGt;!$sd zdr=tJ#S`5m%u6`pB^|HW@pjTU-z$w~Sg%c*VsF>+DAI9>O;=~<(cX`gywWcXT|ua> z?x|m+@Y}F@r^4YE7%Ai8&@M!cdC9TBFJOvdI|`o^^^p8Tf2P0C;gQ$$fWCuOI0IJ$ zI?LaX=a4Xfh$Q+0pV!rEFW#-;kQBZNrw)tH@tY8S@pkAMLOu}Q4fTVtw|}7Em7s9& z>czW*kP!T<(b#|W;_W_&zc(6tP`pCGy|;*vK!`tZ333}5MksP^uXuhpa(#ENIJz6T zvAY<#2@sJ(Zga?0fZRF^;mZYgx!4Ji1i>FzsjK^Y7vUz1j4kf33%`!|pamej%=$4H z#FjyPnGJ9Q{&j?d!ypd0;+@5P*&(>O7>NL_hf;swv$_hsi`MsnhHrepzv_EAG22HR zF()-WIS