avm2: Implement Bitmap.pixelSnapping

This commit is contained in:
Nathan Adams 2023-07-05 23:49:19 +02:00
parent 289f73c85f
commit 4410d666ed
10 changed files with 120 additions and 10 deletions

View File

@ -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<Value<'gc>, 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<Value<'gc>, 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)
}

View File

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

View File

@ -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<PixelSnapping> 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<Self> {
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 => {

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

View File

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