Skip to content

Commit f2080e6

Browse files
authored
avm2: implement get and set of displayobject filters (ruffle-rs#8623)
1 parent 281ca6a commit f2080e6

File tree

6 files changed

+128
-11
lines changed

6 files changed

+128
-11
lines changed

core/src/avm2/globals/flash/display/displayobject.rs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ use crate::avm2::class::Class;
55
use crate::avm2::method::{Method, NativeMethodImpl};
66
use crate::avm2::object::{stage_allocator, Object, TObject};
77
use crate::avm2::value::Value;
8-
use crate::avm2::ArrayObject;
98
use crate::avm2::Error;
109
use crate::avm2::Multiname;
1110
use crate::avm2::Namespace;
1211
use crate::avm2::QName;
12+
use crate::avm2::{ArrayObject, ArrayStorage};
1313
use crate::display_object::{DisplayObject, HitTestOptions, TDisplayObject};
1414
use crate::ecma_conversions::round_to_even;
1515
use crate::frame_lifecycle::catchup_display_object_to_frame;
@@ -239,23 +239,67 @@ pub fn set_scale_x<'gc>(
239239
Ok(Value::Undefined)
240240
}
241241

242-
/// Implements `filters`'s getter.
243242
pub fn filters<'gc>(
244243
activation: &mut Activation<'_, 'gc, '_>,
245-
_this: Option<Object<'gc>>,
244+
this: Option<Object<'gc>>,
246245
_args: &[Value<'gc>],
247246
) -> Result<Value<'gc>, Error<'gc>> {
248-
log::warn!("DisplayObject.filters getter - not yet implemented");
247+
if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
248+
return Ok(ArrayObject::from_storage(activation, dobj.filters())?.into());
249+
}
249250
Ok(ArrayObject::empty(activation)?.into())
250251
}
251252

252-
/// Implements `filters`'s setter.
253+
fn build_argument_type_error<'gc>(
254+
activation: &mut Activation<'_, 'gc, '_>,
255+
) -> Result<Value<'gc>, Error<'gc>> {
256+
Err(Error::AvmError(crate::avm2::error::argument_error(
257+
activation,
258+
"Error #2005: Parameter 0 is of the incorrect type. Should be type Filter.",
259+
2005,
260+
)?))
261+
}
262+
253263
pub fn set_filters<'gc>(
254-
_activation: &mut Activation<'_, 'gc, '_>,
255-
_this: Option<Object<'gc>>,
256-
_args: &[Value<'gc>],
264+
activation: &mut Activation<'_, 'gc, '_>,
265+
this: Option<Object<'gc>>,
266+
args: &[Value<'gc>],
257267
) -> Result<Value<'gc>, Error<'gc>> {
258-
log::warn!("DisplayObject.filters setter - not yet implemented");
268+
if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
269+
let new_filters = args.get(0).cloned().unwrap_or(Value::Undefined);
270+
271+
if matches!(new_filters, Value::Undefined | Value::Null) {
272+
let new_storage = ArrayStorage::new(0);
273+
dobj.set_filters(activation.context.gc_context, new_storage);
274+
} else {
275+
let new_filters = new_filters.coerce_to_object(activation)?;
276+
277+
if let Some(filters_array) = new_filters.as_array_object() {
278+
if let Some(filters_storage) = filters_array.as_array_storage() {
279+
let filter_class =
280+
Multiname::new(Namespace::Package("flash.filters".into()), "BitmapFilter");
281+
282+
let filter_class_object = activation.resolve_class(&filter_class)?;
283+
284+
for filter in filters_storage.iter().flatten() {
285+
if matches!(filter, Value::Undefined | Value::Null) {
286+
return build_argument_type_error(activation);
287+
} else {
288+
let filter_object = filter.coerce_to_object(activation)?;
289+
290+
if !filter_object.is_of_type(filter_class_object, activation) {
291+
return build_argument_type_error(activation);
292+
}
293+
}
294+
}
295+
let new_storage = ArrayStorage::from_storage(filters_storage.iter().collect());
296+
297+
dobj.set_filters(activation.context.gc_context, new_storage);
298+
}
299+
}
300+
}
301+
}
302+
259303
Ok(Value::Undefined)
260304
}
261305

core/src/display_object.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::avm1::{Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value};
22
use crate::avm2::{
3-
Activation as Avm2Activation, Avm2, Error as Avm2Error, EventObject as Avm2EventObject,
4-
Multiname as Avm2Multiname, Object as Avm2Object, TObject as Avm2TObject, Value as Avm2Value,
3+
Activation as Avm2Activation, ArrayStorage as Avm2ArrayStorage, Avm2, Error as Avm2Error,
4+
EventObject as Avm2EventObject, Multiname as Avm2Multiname, Object as Avm2Object,
5+
TObject as Avm2TObject, Value as Avm2Value,
56
};
67
use crate::context::{RenderContext, UpdateContext};
78
use crate::drawing::Drawing;
@@ -59,6 +60,7 @@ pub struct DisplayObjectBase<'gc> {
5960
#[collect(require_static)]
6061
transform: Transform,
6162
name: AvmString<'gc>,
63+
filters: Avm2ArrayStorage<'gc>,
6264
clip_depth: Depth,
6365

6466
// Cached transform properties `_xscale`, `_yscale`, `_rotation`.
@@ -122,6 +124,7 @@ impl<'gc> Default for DisplayObjectBase<'gc> {
122124
depth: Default::default(),
123125
transform: Default::default(),
124126
name: Default::default(),
127+
filters: Avm2ArrayStorage::new(0),
125128
clip_depth: Default::default(),
126129
rotation: Degrees::from_radians(0.0),
127130
scale_x: Percent::from_unit(1.0),
@@ -306,6 +309,14 @@ impl<'gc> DisplayObjectBase<'gc> {
306309
self.name = name;
307310
}
308311

312+
fn filters(&self) -> Avm2ArrayStorage<'gc> {
313+
Avm2ArrayStorage::from_storage(self.filters.iter().collect())
314+
}
315+
316+
fn set_filters(&mut self, filters: Avm2ArrayStorage<'gc>) {
317+
self.filters = filters;
318+
}
319+
309320
fn alpha(&self) -> f64 {
310321
f64::from(self.color_transform().a_mult)
311322
}
@@ -928,6 +939,14 @@ pub trait TDisplayObject<'gc>:
928939
self.base_mut(gc_context).set_name(name)
929940
}
930941

942+
fn filters(&self) -> Avm2ArrayStorage<'gc> {
943+
self.base().filters()
944+
}
945+
946+
fn set_filters(&self, gc_context: MutationContext<'gc, '_>, filters: Avm2ArrayStorage<'gc>) {
947+
self.base_mut(gc_context).set_filters(filters)
948+
}
949+
931950
/// Returns the dot-syntax path to this display object, e.g. `_level0.foo.clip`
932951
fn path(&self) -> WString {
933952
if let Some(parent) = self.avm1_parent() {

tests/tests/regression_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ swf_tests! {
248248
(as3_dictionary_in, "avm2/dictionary_in", 1),
249249
(as3_dictionary_namespaces, "avm2/dictionary_namespaces", 1),
250250
(as3_displayobject_alpha, "avm2/displayobject_alpha", 1),
251+
(as3_displayobject_filters, "avm2/displayobject_filters", 1),
251252
(as3_displayobject_blendmode, "avm2/displayobject_blendmode", 1, img = true),
252253
(as3_displayobject_hittestobject, "avm2/displayobject_hittestobject", 1),
253254
(as3_displayobject_hittestpoint, "avm2/displayobject_hittestpoint", 2),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package {
2+
import flash.display.Sprite;
3+
import flash.filters.BlurFilter;
4+
public class Test extends Sprite {
5+
public function Test() {
6+
trace("///this.filters.length == 0");
7+
trace(this.filters.length == 0)
8+
trace("///this.filters === this.filters");
9+
trace(this.filters === this.filters);
10+
trace("///this.filters = [new BlurFilter()]")
11+
this.filters = [new BlurFilter()];
12+
trace("///this.filters.length == 1");
13+
trace(this.filters.length == 1)
14+
trace("///this.filters = undefined")
15+
this.filters = undefined;
16+
trace("///this.filters.length == 0")
17+
trace(this.filters.length == 0)
18+
trace("///this.filters = null")
19+
this.filters = null;
20+
trace("///this.filters.length == 0")
21+
trace(this.filters.length == 0)
22+
try {
23+
trace("///this.filters = [1, 2, 3]")
24+
this.filters = [1, 2, 3];
25+
} catch (e: Error) {
26+
trace("Caught error: " + e);
27+
}
28+
try {
29+
trace("///this.filters = [new BlurFilter(), undefined]")
30+
this.filters = [new BlurFilter(), undefined];
31+
} catch (e: Error) {
32+
trace("Caught error: " + e);
33+
}
34+
}
35+
}
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
///this.filters.length == 0
2+
true
3+
///this.filters === this.filters
4+
false
5+
///this.filters = [new BlurFilter()]
6+
///this.filters.length == 1
7+
true
8+
///this.filters = undefined
9+
///this.filters.length == 0
10+
true
11+
///this.filters = null
12+
///this.filters.length == 0
13+
true
14+
///this.filters = [1, 2, 3]
15+
Caught error: ArgumentError: Error #2005: Parameter 0 is of the incorrect type. Should be type Filter.
16+
///this.filters = [new BlurFilter(), undefined]
17+
Caught error: ArgumentError: Error #2005: Parameter 0 is of the incorrect type. Should be type Filter.
920 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)