diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index c33b21bfe..61311c80a 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -6,12 +6,14 @@ use crate::avm2::globals::flash::display::display_object::initialize_for_allocat use crate::avm2::object::{BitmapDataObject, ClassObject, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; +use ruffle_render::bitmap::PixelSnapping; +use ruffle_wstr::WStr; +use crate::avm2::error::make_error_2008; use crate::avm2::parameters::ParametersExt; use crate::bitmap::bitmap_data::BitmapDataWrapper; use crate::character::Character; use crate::display_object::{Bitmap, TDisplayObject}; -use crate::{avm2_stub_getter, avm2_stub_setter}; pub fn bitmap_allocator<'gc>( class: ClassObject<'gc>, @@ -79,8 +81,9 @@ pub fn init<'gc>( let bitmap_data = args .try_get_object(activation, 0) .and_then(|o| o.as_bitmap_data()); - //TODO: Pixel snapping is not supported - let _pixel_snapping = args.get_string(activation, 1); + let Some(pixel_snapping) = PixelSnapping::from_wstr(&args.get_string(activation, 1)?) else { + return Err(make_error_2008(activation, "pixelSnapping")); + }; let smoothing = args.get_bool(2); if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { @@ -88,6 +91,7 @@ pub fn init<'gc>( bitmap.set_bitmap_data(&mut activation.context, bitmap_data); } bitmap.set_smoothing(activation.context.gc_context, smoothing); + bitmap.set_pixel_snapping(activation.context.gc_context, pixel_snapping); } else { unreachable!(); } @@ -138,21 +142,29 @@ pub fn set_bitmap_data<'gc>( /// Stub `Bitmap.pixelSnapping`'s getter pub fn get_pixel_snapping<'gc>( - activation: &mut Activation<'_, 'gc>, - _this: Object<'gc>, + _activation: &mut Activation<'_, 'gc>, + this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - avm2_stub_getter!(activation, "flash.display.Bitmap", "pixelSnapping"); - Ok("auto".into()) + if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { + let value: &WStr = bitmap.pixel_snapping().into(); + return Ok(Value::String(value.into())); + } + Ok(Value::Undefined) } /// Stub `Bitmap.pixelSnapping`'s setter pub fn set_pixel_snapping<'gc>( activation: &mut Activation<'_, 'gc>, - _this: Object<'gc>, - _args: &[Value<'gc>], + this: Object<'gc>, + args: &[Value<'gc>], ) -> Result, Error<'gc>> { - avm2_stub_setter!(activation, "flash.display.Bitmap", "pixelSnapping"); + if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { + let Some(value) = PixelSnapping::from_wstr(&args.get_string(activation, 0)?) else { + return Err(make_error_2008(activation, "pixelSnapping")); + }; + bitmap.set_pixel_snapping(activation.context.gc_context, value); + } Ok(Value::Undefined) } diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index 5fd9b1c44..f8f59edb3 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -174,6 +174,14 @@ impl<'gc> Bitmap<'gc> { self.0.read().height as u16 } + pub fn pixel_snapping(self) -> PixelSnapping { + self.0.read().pixel_snapping + } + + pub fn set_pixel_snapping(self, mc: MutationContext<'gc, '_>, value: PixelSnapping) { + self.0.write(mc).pixel_snapping = value; + } + pub fn bitmap_data_wrapper(self) -> BitmapDataWrapper<'gc> { self.0.read().bitmap_data } diff --git a/render/src/bitmap.rs b/render/src/bitmap.rs index 8a7b40c81..19b4a2dd1 100644 --- a/render/src/bitmap.rs +++ b/render/src/bitmap.rs @@ -3,6 +3,7 @@ use std::fmt::Debug; use std::sync::Arc; use downcast_rs::{impl_downcast, Downcast}; +use ruffle_wstr::WStr; use swf::{Rectangle, Twips}; use crate::backend::RenderBackend; @@ -67,7 +68,29 @@ pub enum PixelSnapping { Never, } +impl From for &'static WStr { + fn from(value: PixelSnapping) -> &'static WStr { + match value { + PixelSnapping::Always => WStr::from_units(b"always"), + PixelSnapping::Auto => WStr::from_units(b"auto"), + PixelSnapping::Never => WStr::from_units(b"never"), + } + } +} + impl PixelSnapping { + pub fn from_wstr(str: &WStr) -> Option { + if str == b"always" { + Some(PixelSnapping::Always) + } else if str == b"auto" { + Some(PixelSnapping::Auto) + } else if str == b"never" { + Some(PixelSnapping::Never) + } else { + None + } + } + pub fn apply(&self, matrix: &mut Matrix) { match self { PixelSnapping::Always => { diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/Test.as b/tests/tests/swfs/avm2/bitmap_pixelsnapping/Test.as new file mode 100644 index 000000000..9a38c0be6 --- /dev/null +++ b/tests/tests/swfs/avm2/bitmap_pixelsnapping/Test.as @@ -0,0 +1,57 @@ +package { + + import flash.display.MovieClip; + import flash.display.Bitmap; + import flash.display.PixelSnapping; + + + public class Test extends MovieClip { + + + public function Test() { + addTest(PixelSnapping.NEVER, 1.0); + addTest(PixelSnapping.ALWAYS, 1.0); + addTest(PixelSnapping.AUTO, 1.0); + + addTest(PixelSnapping.NEVER, 0.99); + addTest(PixelSnapping.ALWAYS, 0.99); + addTest(PixelSnapping.AUTO, 0.99); + + addTest(PixelSnapping.NEVER, 1.01); + addTest(PixelSnapping.ALWAYS, 1.01); + addTest(PixelSnapping.AUTO, 1.01); + + addTest(PixelSnapping.NEVER, 2.0); + addTest(PixelSnapping.ALWAYS, 2.0); + addTest(PixelSnapping.AUTO, 2.0); + + addTest(PixelSnapping.NEVER, 2.5); + addTest(PixelSnapping.ALWAYS, 2.5); + addTest(PixelSnapping.AUTO, 2.5); + + try { + var bitmap = new Bitmap(new TestImage()); + bitmap.pixelSnapping = "test"; + } catch (err) { + trace(err); + } + + try { + var bitmap = new Bitmap(new TestImage(), "test"); + } catch (err) { + trace(err); + } + } + + function addTest(snapping: String, scale: Number) { + var bitmap = new Bitmap(new TestImage()); + bitmap.pixelSnapping = snapping; + bitmap.x = (numChildren % 3 * 100) + 10.5; + bitmap.y = (Math.floor(numChildren / 3) * 100) + 10.5; + bitmap.width *= scale; + bitmap.height *= scale; + addChild(bitmap); + } + } + +} diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/expected.png b/tests/tests/swfs/avm2/bitmap_pixelsnapping/expected.png new file mode 100644 index 000000000..63af2c22e Binary files /dev/null and b/tests/tests/swfs/avm2/bitmap_pixelsnapping/expected.png differ diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/output.txt b/tests/tests/swfs/avm2/bitmap_pixelsnapping/output.txt new file mode 100644 index 000000000..20c563eaf --- /dev/null +++ b/tests/tests/swfs/avm2/bitmap_pixelsnapping/output.txt @@ -0,0 +1,2 @@ +ArgumentError: Error #2008: Parameter pixelSnapping must be one of the accepted values. +ArgumentError: Error #2008: Parameter pixelSnapping must be one of the accepted values. diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.fla b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.fla new file mode 100644 index 000000000..04368f5a0 Binary files /dev/null and b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.fla differ diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.png b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.png new file mode 100644 index 000000000..6376d4e72 Binary files /dev/null and b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.png differ diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.swf b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.swf new file mode 100644 index 000000000..e9bea7817 Binary files /dev/null and b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.swf differ diff --git a/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.toml b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.toml new file mode 100644 index 000000000..64f6c1f0e --- /dev/null +++ b/tests/tests/swfs/avm2/bitmap_pixelsnapping/test.toml @@ -0,0 +1,8 @@ +num_frames = 1 +ignore = true # big differences across rendering backends :( + +[image_comparison] +tolerance = 0 + +[player_options] +with_renderer = { optional = false, sample_count = 1 } \ No newline at end of file