avm2: Implement DisplayObject.transform and most of Transform
This PR implements the 'DisplayObject.transform' getters/setters, and most of the getters/setters in the `Transform` class From testing in FP, it appears that each call to the 'DisplayObject.transform' property produces a new 'Transform' instance, which is permanently tied to the owner 'DisplayObject'. All of the getters/setters in `Transform` operate directly on owner `DisplayObject`. However, note that the `Matrix` and `ColorTransform` valuse *produced* the getter are plain ActionScript objects, and have no further tie to the `DisplayObject`. Using the `DisplayObject.transform` setter results in values being *copied* from the input `Transform` object. The input object retains its original owner `DisplayObject`. Not implemented: * Transform.concatenatedColorTransform * Transform.pixelBounds When a DisplayObject is not a descendant of the stage, the `concatenatedMatrix` property produces a bizarre matrix: a scale matrix that the depends on the global state quality. Any DisplayObject that *is* a descendant of the stage has a `concatenatedMatrix` that does not depend on the stage quality. I'm not sure why the behavior occurs - for now, I just manually mimic the values prdduced by FP. However, these values may indicate that we need to do some internal scaling based on stage quality values, and then 'undo' this in certain circumstances when constructing an ActionScript matrix. Unfortunately, some of the computed 'concatenatedMatrix' values are off by f32::EPSILON. This is likely due to us storing some internal values in pixels rather than twips (the rounding introduced by round-trip twips conversions could cause this slight difference0. For now, I've opted to mark these tests as 'approximate'. To support this, I've extended our test framework to support providing a regex that matches floating-point values in the output. This allows us to print out 'Matrix.toString()' and still perform approximate comparisons between strings of the format '(a=0, b=0, c=0, d=0, tx=0, ty=0)'
This commit is contained in:
parent
c4488fc883
commit
6f20e8882d
|
@ -3621,6 +3621,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"image",
|
"image",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"regex",
|
||||||
"ruffle_core",
|
"ruffle_core",
|
||||||
"ruffle_input_format",
|
"ruffle_input_format",
|
||||||
"ruffle_render_wgpu",
|
"ruffle_render_wgpu",
|
||||||
|
|
|
@ -93,6 +93,9 @@ pub struct SystemClasses<'gc> {
|
||||||
pub errorevent: ClassObject<'gc>,
|
pub errorevent: ClassObject<'gc>,
|
||||||
pub ioerrorevent: ClassObject<'gc>,
|
pub ioerrorevent: ClassObject<'gc>,
|
||||||
pub securityerrorevent: ClassObject<'gc>,
|
pub securityerrorevent: ClassObject<'gc>,
|
||||||
|
pub transform: ClassObject<'gc>,
|
||||||
|
pub colortransform: ClassObject<'gc>,
|
||||||
|
pub matrix: ClassObject<'gc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> SystemClasses<'gc> {
|
impl<'gc> SystemClasses<'gc> {
|
||||||
|
@ -155,6 +158,9 @@ impl<'gc> SystemClasses<'gc> {
|
||||||
errorevent: object,
|
errorevent: object,
|
||||||
ioerrorevent: object,
|
ioerrorevent: object,
|
||||||
securityerrorevent: object,
|
securityerrorevent: object,
|
||||||
|
transform: object,
|
||||||
|
colortransform: object,
|
||||||
|
matrix: object,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,6 +758,9 @@ fn load_playerglobal<'gc>(
|
||||||
("flash.events", "IOErrorEvent", ioerrorevent),
|
("flash.events", "IOErrorEvent", ioerrorevent),
|
||||||
("flash.events", "MouseEvent", mouseevent),
|
("flash.events", "MouseEvent", mouseevent),
|
||||||
("flash.events", "FullScreenEvent", fullscreenevent),
|
("flash.events", "FullScreenEvent", fullscreenevent),
|
||||||
|
("flash.geom", "Matrix", matrix),
|
||||||
|
("flash.geom", "Transform", transform),
|
||||||
|
("flash.geom", "ColorTransform", colortransform),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// This is a stub - the actual class is implemented in 'displayobject.rs'
|
||||||
|
package flash.display {
|
||||||
|
public class DisplayObject {
|
||||||
|
}
|
||||||
|
}
|
|
@ -594,6 +594,54 @@ pub fn loader_info<'gc>(
|
||||||
Ok(Value::Undefined)
|
Ok(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if let Some(this) = this {
|
||||||
|
return Ok(activation
|
||||||
|
.avm2()
|
||||||
|
.classes()
|
||||||
|
.transform
|
||||||
|
.construct(activation, &[this.into()])?
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_transform<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
if let Some(this) = this {
|
||||||
|
let transform = args[0].coerce_to_object(activation)?;
|
||||||
|
|
||||||
|
// FIXME - consider 3D matrix and pixel bounds
|
||||||
|
let matrix = transform
|
||||||
|
.get_property(&QName::dynamic_name("matrix").into(), activation)?
|
||||||
|
.coerce_to_object(activation)?;
|
||||||
|
let color_transform = transform
|
||||||
|
.get_property(&QName::dynamic_name("matrix").into(), activation)?
|
||||||
|
.coerce_to_object(activation)?;
|
||||||
|
|
||||||
|
let matrix =
|
||||||
|
crate::avm2::globals::flash::geom::transform::object_to_matrix(matrix, activation)?;
|
||||||
|
let color_transform =
|
||||||
|
crate::avm2::globals::flash::geom::transform::object_to_color_transform(
|
||||||
|
color_transform,
|
||||||
|
activation,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let dobj = this.as_display_object().unwrap();
|
||||||
|
let mut write = dobj.base_mut(activation.context.gc_context);
|
||||||
|
write.set_color_transform(&color_transform);
|
||||||
|
write.set_matrix(&matrix);
|
||||||
|
}
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct `DisplayObject`'s class.
|
/// Construct `DisplayObject`'s class.
|
||||||
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
|
||||||
let class = Class::new(
|
let class = Class::new(
|
||||||
|
@ -637,6 +685,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
|
||||||
("mouseY", Some(mouse_y), None),
|
("mouseY", Some(mouse_y), None),
|
||||||
("loaderInfo", Some(loader_info), None),
|
("loaderInfo", Some(loader_info), None),
|
||||||
("filters", Some(filters), Some(set_filters)),
|
("filters", Some(filters), Some(set_filters)),
|
||||||
|
("transform", Some(transform), Some(set_transform)),
|
||||||
];
|
];
|
||||||
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
//! `flash.geom` namespace
|
//! `flash.geom` namespace
|
||||||
|
|
||||||
|
pub mod transform;
|
||||||
|
|
|
@ -29,5 +29,9 @@ package flash.geom {
|
||||||
this.a *= sx;
|
this.a *= sx;
|
||||||
this.d *= sy;
|
this.d *= sy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toString():String {
|
||||||
|
return "(a=" + this.a + ", b=" + this.b + ", c=" + this.c + ", d=" + this.d + ", tx=" + this.tx + ", ty=" + this.ty + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package flash.geom {
|
||||||
|
import flash.display.DisplayObject;
|
||||||
|
public class Transform {
|
||||||
|
private var _displayObject:DisplayObject;
|
||||||
|
|
||||||
|
function Transform(object: DisplayObject) {
|
||||||
|
this.init(object);
|
||||||
|
}
|
||||||
|
native function init(object:DisplayObject):void;
|
||||||
|
|
||||||
|
public native function get colorTransform():ColorTransform;
|
||||||
|
public native function set colorTransform(value: ColorTransform):void;
|
||||||
|
public native function get matrix():Matrix;
|
||||||
|
public native function set matrix(value:Matrix):void;
|
||||||
|
|
||||||
|
public native function get concatenatedColorTransform():ColorTransform;
|
||||||
|
public native function get concatenatedMatrix():Matrix;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use crate::avm2::{Activation, Error, Namespace, Object, QName, TObject, Value};
|
||||||
|
use crate::display_object::{StageQuality, TDisplayObject};
|
||||||
|
use crate::prelude::{ColorTransform, DisplayObject, Matrix, Twips};
|
||||||
|
use swf::Fixed8;
|
||||||
|
|
||||||
|
fn get_display_object<'gc>(
|
||||||
|
this: Object<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<DisplayObject<'gc>, Error> {
|
||||||
|
Ok(this
|
||||||
|
.get_property(
|
||||||
|
&QName::new(Namespace::Private("".into()), "_displayObject").into(),
|
||||||
|
activation,
|
||||||
|
)?
|
||||||
|
.as_object()
|
||||||
|
.unwrap()
|
||||||
|
.as_display_object()
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
this.unwrap().set_property(
|
||||||
|
&QName::new(Namespace::Private("".into()), "_displayObject").into(),
|
||||||
|
args[0],
|
||||||
|
activation,
|
||||||
|
)?;
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_color_transform<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
let ct_obj = *get_display_object(this, activation)?
|
||||||
|
.base()
|
||||||
|
.color_transform();
|
||||||
|
color_transform_to_object(&ct_obj, activation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_color_transform<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
let ct = object_to_color_transform(args[0].coerce_to_object(activation)?, activation)?;
|
||||||
|
get_display_object(this, activation)?
|
||||||
|
.base_mut(activation.context.gc_context)
|
||||||
|
.set_color_transform(&ct);
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_matrix<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
let matrix = *get_display_object(this, activation)?.base().matrix();
|
||||||
|
matrix_to_object(matrix, activation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_matrix<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
let matrix = object_to_matrix(args[0].coerce_to_object(activation)?, activation)?;
|
||||||
|
get_display_object(this, activation)?
|
||||||
|
.base_mut(activation.context.gc_context)
|
||||||
|
.set_matrix(&matrix);
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_concatenated_matrix<'gc>(
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let this = this.unwrap();
|
||||||
|
|
||||||
|
let dobj = get_display_object(this, activation)?;
|
||||||
|
let mut node = Some(dobj);
|
||||||
|
while let Some(obj) = node {
|
||||||
|
if obj.as_stage().is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = obj.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're a child of the Stage, and not the stage itself
|
||||||
|
if node.is_some() && dobj.as_stage().is_none() {
|
||||||
|
let matrix = get_display_object(this, activation)?.local_to_global_matrix();
|
||||||
|
matrix_to_object(matrix, activation)
|
||||||
|
} else {
|
||||||
|
// If this object is the Stage itself, or an object
|
||||||
|
// that's not a child of the stage, then we need to mimic
|
||||||
|
// Flash's bizarre behavior.
|
||||||
|
let scale = match activation.context.stage.quality() {
|
||||||
|
StageQuality::Low => 20.0,
|
||||||
|
StageQuality::Medium => 10.0,
|
||||||
|
StageQuality::High | StageQuality::Best => 5.0,
|
||||||
|
StageQuality::High8x8 | StageQuality::High8x8Linear => 2.5,
|
||||||
|
StageQuality::High16x16 | StageQuality::High16x16Linear => 1.25,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mat = *dobj.base().matrix();
|
||||||
|
mat.a *= scale;
|
||||||
|
mat.d *= scale;
|
||||||
|
|
||||||
|
matrix_to_object(mat, activation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_concatenated_color_transform<'gc>(
|
||||||
|
_activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
_this: Option<Object<'gc>>,
|
||||||
|
_args: &[Value<'gc>],
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
log::warn!("Transform.concatenatedColorTransform: not yet implemented");
|
||||||
|
Ok(Value::Undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME - handle clamping. We're throwing away precision here in converting to an integer:
|
||||||
|
// is that what we should be doing?
|
||||||
|
pub fn object_to_color_transform<'gc>(
|
||||||
|
object: Object<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<ColorTransform, Error> {
|
||||||
|
let red_multiplier = object
|
||||||
|
.get_property(&QName::dynamic_name("redMultiplier").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let green_multiplier = object
|
||||||
|
.get_property(&QName::dynamic_name("greenMultiplier").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let blue_multiplier = object
|
||||||
|
.get_property(&QName::dynamic_name("blueMultiplier").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let alpha_multiplier = object
|
||||||
|
.get_property(&QName::dynamic_name("alphaMultiplier").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let red_offset = object
|
||||||
|
.get_property(&QName::dynamic_name("redOffset").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let green_offset = object
|
||||||
|
.get_property(&QName::dynamic_name("greenOffset").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let blue_offset = object
|
||||||
|
.get_property(&QName::dynamic_name("blueOffset").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
let alpha_offset = object
|
||||||
|
.get_property(&QName::dynamic_name("alphaOffset").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?;
|
||||||
|
Ok(ColorTransform {
|
||||||
|
r_mult: Fixed8::from_f64(red_multiplier),
|
||||||
|
g_mult: Fixed8::from_f64(green_multiplier),
|
||||||
|
b_mult: Fixed8::from_f64(blue_multiplier),
|
||||||
|
a_mult: Fixed8::from_f64(alpha_multiplier),
|
||||||
|
r_add: red_offset as i16,
|
||||||
|
g_add: green_offset as i16,
|
||||||
|
b_add: blue_offset as i16,
|
||||||
|
a_add: alpha_offset as i16,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_transform_to_object<'gc>(
|
||||||
|
color_transform: &ColorTransform,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let args = [
|
||||||
|
color_transform.r_mult.to_f64().into(),
|
||||||
|
color_transform.g_mult.to_f64().into(),
|
||||||
|
color_transform.b_mult.to_f64().into(),
|
||||||
|
color_transform.a_mult.to_f64().into(),
|
||||||
|
color_transform.r_add.into(),
|
||||||
|
color_transform.g_add.into(),
|
||||||
|
color_transform.b_add.into(),
|
||||||
|
color_transform.a_add.into(),
|
||||||
|
];
|
||||||
|
let ct_class = activation.avm2().classes().colortransform;
|
||||||
|
let object = ct_class.construct(activation, &args)?;
|
||||||
|
Ok(object.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matrix_to_object<'gc>(
|
||||||
|
matrix: Matrix,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Value<'gc>, Error> {
|
||||||
|
let args = [
|
||||||
|
matrix.a.into(),
|
||||||
|
matrix.b.into(),
|
||||||
|
matrix.c.into(),
|
||||||
|
matrix.d.into(),
|
||||||
|
matrix.tx.to_pixels().into(),
|
||||||
|
matrix.ty.to_pixels().into(),
|
||||||
|
];
|
||||||
|
let object = activation
|
||||||
|
.avm2()
|
||||||
|
.classes()
|
||||||
|
.matrix
|
||||||
|
.construct(activation, &args)?;
|
||||||
|
Ok(object.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_to_matrix<'gc>(
|
||||||
|
object: Object<'gc>,
|
||||||
|
activation: &mut Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Matrix, Error> {
|
||||||
|
let a = object
|
||||||
|
.get_property(&QName::dynamic_name("a").into(), activation)?
|
||||||
|
.coerce_to_number(activation)? as f32;
|
||||||
|
let b = object
|
||||||
|
.get_property(&QName::dynamic_name("b").into(), activation)?
|
||||||
|
.coerce_to_number(activation)? as f32;
|
||||||
|
let c = object
|
||||||
|
.get_property(&QName::dynamic_name("c").into(), activation)?
|
||||||
|
.coerce_to_number(activation)? as f32;
|
||||||
|
let d = object
|
||||||
|
.get_property(&QName::dynamic_name("d").into(), activation)?
|
||||||
|
.coerce_to_number(activation)? as f32;
|
||||||
|
let tx = Twips::from_pixels(
|
||||||
|
object
|
||||||
|
.get_property(&QName::dynamic_name("tx").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?,
|
||||||
|
);
|
||||||
|
let ty = Twips::from_pixels(
|
||||||
|
object
|
||||||
|
.get_property(&QName::dynamic_name("ty").into(), activation)?
|
||||||
|
.coerce_to_number(activation)?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Matrix { a, b, c, d, tx, ty })
|
||||||
|
}
|
|
@ -56,6 +56,7 @@ include "flash/geom/Orientation3D.as"
|
||||||
include "flash/geom/Matrix.as"
|
include "flash/geom/Matrix.as"
|
||||||
include "flash/geom/Point.as"
|
include "flash/geom/Point.as"
|
||||||
include "flash/geom/Rectangle.as"
|
include "flash/geom/Rectangle.as"
|
||||||
|
include "flash/geom/Transform.as"
|
||||||
include "flash/geom/Vector3D.as"
|
include "flash/geom/Vector3D.as"
|
||||||
include "flash/net/SharedObjectFlushStatus.as"
|
include "flash/net/SharedObjectFlushStatus.as"
|
||||||
include "flash/net/URLLoader.as"
|
include "flash/net/URLLoader.as"
|
||||||
|
|
|
@ -8,6 +8,7 @@ include "Array.as"
|
||||||
include "Boolean.as"
|
include "Boolean.as"
|
||||||
include "flash/display/DisplayObjectContainer.as"
|
include "flash/display/DisplayObjectContainer.as"
|
||||||
include "flash/display/InteractiveObject.as"
|
include "flash/display/InteractiveObject.as"
|
||||||
|
include "flash/display/DisplayObject.as"
|
||||||
include "flash/events/EventDispatcher.as"
|
include "flash/events/EventDispatcher.as"
|
||||||
include "flash/system/ApplicationDomain.as"
|
include "flash/system/ApplicationDomain.as"
|
||||||
include "Number.as"
|
include "Number.as"
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl<'gc> DisplayObjectBase<'gc> {
|
||||||
&mut self.transform.matrix
|
&mut self.transform.matrix
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_matrix(&mut self, matrix: &Matrix) {
|
pub fn set_matrix(&mut self, matrix: &Matrix) {
|
||||||
self.transform.matrix = *matrix;
|
self.transform.matrix = *matrix;
|
||||||
self.flags -= DisplayObjectFlags::SCALE_ROTATION_CACHED;
|
self.flags -= DisplayObjectFlags::SCALE_ROTATION_CACHED;
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ impl<'gc> DisplayObjectBase<'gc> {
|
||||||
&mut self.transform.color_transform
|
&mut self.transform.color_transform
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_color_transform(&mut self, color_transform: &ColorTransform) {
|
pub fn set_color_transform(&mut self, color_transform: &ColorTransform) {
|
||||||
self.transform.color_transform = *color_transform;
|
self.transform.color_transform = *color_transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ ruffle_core = { path = "../core", features = ["deterministic"] }
|
||||||
ruffle_render_wgpu = { path = "../render/wgpu" }
|
ruffle_render_wgpu = { path = "../render/wgpu" }
|
||||||
ruffle_input_format = { path = "input-format" }
|
ruffle_input_format = { path = "input-format" }
|
||||||
image = "0.24.2"
|
image = "0.24.2"
|
||||||
|
regex = "1.6.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable running image comparison tests. This is off by default,
|
# Enable running image comparison tests. This is off by default,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//! Trace output can be compared with correct output from the official Flash Player.
|
//! Trace output can be compared with correct output from the official Flash Player.
|
||||||
|
|
||||||
use approx::assert_relative_eq;
|
use approx::assert_relative_eq;
|
||||||
|
use regex::Regex;
|
||||||
use ruffle_core::backend::{
|
use ruffle_core::backend::{
|
||||||
log::LogBackend,
|
log::LogBackend,
|
||||||
navigator::{NullExecutor, NullNavigatorBackend},
|
navigator::{NullExecutor, NullNavigatorBackend},
|
||||||
|
@ -45,6 +46,15 @@ macro_rules! val_or_false {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! val_or_empty_slice {
|
||||||
|
($val:expr) => {
|
||||||
|
$val
|
||||||
|
};
|
||||||
|
() => {
|
||||||
|
&[]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// This macro generates test cases for a given list of SWFs.
|
// This macro generates test cases for a given list of SWFs.
|
||||||
// If 'img' is true, then we will render an image of the final frame
|
// If 'img' is true, then we will render an image of the final frame
|
||||||
// of the SWF, and compare it against a reference image on disk.
|
// of the SWF, and compare it against a reference image on disk.
|
||||||
|
@ -69,8 +79,12 @@ macro_rules! swf_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This macro generates test cases for a given list of SWFs using `test_swf_approx`.
|
// This macro generates test cases for a given list of SWFs using `test_swf_approx`.
|
||||||
|
// If provided, `@num_patterns` must be a `&[Regex]`. Each regex in the slice is
|
||||||
|
// tested against the expected and actual - if it matches, then each capture
|
||||||
|
// group is treated as a floating-point value to be compared approximately.
|
||||||
|
// The rest of the string (outside of the capture groups) is compared exactly.
|
||||||
macro_rules! swf_tests_approx {
|
macro_rules! swf_tests_approx {
|
||||||
($($(#[$attr:meta])* ($name:ident, $path:expr, $num_frames:literal $(, $opt:ident = $val:expr)*),)*) => {
|
($($(#[$attr:meta])* ($name:ident, $path:expr, $num_frames:literal $(, @num_patterns = $num_patterns:expr)? $(, $opt:ident = $val:expr)*),)*) => {
|
||||||
$(
|
$(
|
||||||
#[test]
|
#[test]
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
|
@ -81,6 +95,7 @@ macro_rules! swf_tests_approx {
|
||||||
$num_frames,
|
$num_frames,
|
||||||
concat!("tests/swfs/", $path, "/input.json"),
|
concat!("tests/swfs/", $path, "/input.json"),
|
||||||
concat!("tests/swfs/", $path, "/output.txt"),
|
concat!("tests/swfs/", $path, "/output.txt"),
|
||||||
|
val_or_empty_slice!($($num_patterns)?),
|
||||||
|actual, expected| assert_relative_eq!(actual, expected $(, $opt = $val)*),
|
|actual, expected| assert_relative_eq!(actual, expected $(, $opt = $val)*),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -809,6 +824,9 @@ swf_tests_approx! {
|
||||||
(as3_displayobject_height, "avm2/displayobject_height", 7, epsilon = 0.06), // TODO: height/width appears to be off by 1 twip sometimes
|
(as3_displayobject_height, "avm2/displayobject_height", 7, epsilon = 0.06), // TODO: height/width appears to be off by 1 twip sometimes
|
||||||
(as3_displayobject_rotation, "avm2/displayobject_rotation", 1, epsilon = 0.0000000001),
|
(as3_displayobject_rotation, "avm2/displayobject_rotation", 1, epsilon = 0.0000000001),
|
||||||
(as3_displayobject_width, "avm2/displayobject_width", 7, epsilon = 0.06),
|
(as3_displayobject_width, "avm2/displayobject_width", 7, epsilon = 0.06),
|
||||||
|
(as3_displayobject_transform, "avm2/displayobject_transform", 1, @num_patterns = &[
|
||||||
|
Regex::new(r"\(a=(.+), b=(.+), c=(.+), d=(.+), tx=(.+), ty=(.+)\)").unwrap()
|
||||||
|
], max_relative = f32::EPSILON as f64),
|
||||||
(as3_divide, "avm2/divide", 1, epsilon = 0.0), // TODO: Discrepancy in float formatting.
|
(as3_divide, "avm2/divide", 1, epsilon = 0.0), // TODO: Discrepancy in float formatting.
|
||||||
(as3_edittext_align, "avm2/edittext_align", 1, epsilon = 3.0),
|
(as3_edittext_align, "avm2/edittext_align", 1, epsilon = 3.0),
|
||||||
(as3_edittext_autosize, "avm2/edittext_autosize", 1, epsilon = 0.1),
|
(as3_edittext_autosize, "avm2/edittext_autosize", 1, epsilon = 0.1),
|
||||||
|
@ -1138,6 +1156,7 @@ fn test_swf_approx(
|
||||||
num_frames: u32,
|
num_frames: u32,
|
||||||
simulated_input_path: &str,
|
simulated_input_path: &str,
|
||||||
expected_output_path: &str,
|
expected_output_path: &str,
|
||||||
|
num_patterns: &[Regex],
|
||||||
approx_assert_fn: impl Fn(f64, f64),
|
approx_assert_fn: impl Fn(f64, f64),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let injector =
|
let injector =
|
||||||
|
@ -1185,9 +1204,48 @@ fn test_swf_approx(
|
||||||
// }
|
// }
|
||||||
approx_assert_fn(actual, expected);
|
approx_assert_fn(actual, expected);
|
||||||
} else {
|
} else {
|
||||||
|
let mut found = false;
|
||||||
|
// Check each of the user-provided regexes for a match
|
||||||
|
for pattern in num_patterns {
|
||||||
|
if let (Some(actual_captures), Some(expected_captures)) =
|
||||||
|
(pattern.captures(actual), pattern.captures(expected))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
std::assert_eq!(
|
||||||
|
actual_captures.len(),
|
||||||
|
expected_captures.len(),
|
||||||
|
"Differing numbers of regex captures"
|
||||||
|
);
|
||||||
|
// Each capture group (other than group 0, which is always the entire regex
|
||||||
|
// match) represents a floating-point value
|
||||||
|
for (actual_val, expected_val) in actual_captures
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.zip(expected_captures.iter().skip(1))
|
||||||
|
{
|
||||||
|
let actual_num = actual_val
|
||||||
|
.expect("Missing capture gruop value for 'actual'")
|
||||||
|
.as_str()
|
||||||
|
.parse::<f64>()
|
||||||
|
.expect("Failed to parse 'actual' capture group as float");
|
||||||
|
let expected_num = expected_val
|
||||||
|
.expect("Missing capture gruop value for 'expected'")
|
||||||
|
.as_str()
|
||||||
|
.parse::<f64>()
|
||||||
|
.expect("Failed to parse 'expected' capture group as float");
|
||||||
|
approx_assert_fn(actual_num, expected_num);
|
||||||
|
}
|
||||||
|
let modified_actual = pattern.replace(actual, "");
|
||||||
|
let modified_expected = pattern.replace(expected, "");
|
||||||
|
assert_eq!(modified_actual, modified_expected);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
Checking stage qualities with non-stage child
|
||||||
|
best TextField (a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
best stage(a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
high TextField (a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
high stage(a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
16x16 TextField (a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
16x16 stage(a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
16x16linear TextField (a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
16x16linear stage(a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
8x8 TextField (a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
8x8 stage(a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
8x8linear TextField (a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
8x8linear stage(a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
low TextField (a=20, b=0, c=0, d=20, tx=0, ty=0)
|
||||||
|
low stage(a=20, b=0, c=0, d=20, tx=0, ty=0)
|
||||||
|
medium TextField (a=10, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
medium stage(a=10, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
|
||||||
|
Checking stage qualities with child on stage
|
||||||
|
best TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
best stage(a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
high TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
high stage(a=5, b=0, c=0, d=5, tx=0, ty=0)
|
||||||
|
16x16 TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
16x16 stage(a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
16x16linear TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
16x16linear stage(a=1.25, b=0, c=0, d=1.25, tx=0, ty=0)
|
||||||
|
8x8 TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
8x8 stage(a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
8x8linear TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
8x8linear stage(a=2.5, b=0, c=0, d=2.5, tx=0, ty=0)
|
||||||
|
low TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
low stage(a=20, b=0, c=0, d=20, tx=0, ty=0)
|
||||||
|
medium TextField (a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
medium stage(a=10, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
// child.transform == child.transform
|
||||||
|
false
|
||||||
|
// firstTransform
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// secondTransform
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// firstTransform after no-op modifications
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=1, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// firstTransform after matrix modification
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=42.000003814697266, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// secondTransform
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=0, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=42.000003814697266, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// firstTransform after color modification
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=42.000003814697266, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// secondTransform
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=42.000003814697266, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
// firstTransform after setting parent
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=420, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
// secondTransform after setting parent
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=420, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
// firstTransform after indirectly added to stage
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=1764.0001220703125, b=1764.0001220703125, c=42.000003814697266, d=42.000003814697266, tx=0, ty=0)
|
||||||
|
// secondTransform after indirectly added to stage
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=1764.0001220703125, b=1764.0001220703125, c=42.000003814697266, d=42.000003814697266, tx=0, ty=0)
|
||||||
|
// firstTransform after indirectly removed from stage
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=420, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
// secondTransform after indirectly removed forrm stage
|
||||||
|
colorTransform=(redMultiplier=1, greenMultiplier=1, blueMultiplier=1, alphaMultiplier=1, redOffset=12, greenOffset=0, blueOffset=0, alphaOffset=0)
|
||||||
|
matrix=(a=42, b=0, c=0, d=1, tx=0, ty=0)
|
||||||
|
concatenatedMatrix=(a=420, b=0, c=0, d=10, tx=0, ty=0)
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue