swf: Change Matrix to use Fixed16 for a, b, c, d

Matrices in an SWF file store their scale/skew components in
in 16.16 format (fbits).

Split `ruffle_core::Matrix` and `swf::Matrix`. `swf::Matrix` now
stores its data as `Fixed16` instead of immediately converting to
`f32`.
This commit is contained in:
Mike Welsh 2021-06-07 22:33:37 -07:00
parent 39decde5bc
commit d23ea90459
22 changed files with 1045 additions and 831 deletions

View File

@ -6,8 +6,9 @@ use crate::avm1::function::{Executable, FunctionObject};
use crate::avm1::globals::point::{point_to_object, value_to_point};
use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
use crate::matrix::Matrix;
use gc_arena::MutationContext;
use swf::{Matrix, Twips};
use swf::Twips;
const PROTO_DECLS: &[Declaration] = declare_properties! {
"toString" => method(to_string);
@ -138,7 +139,7 @@ fn constructor<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if args.is_empty() {
apply_matrix_to_object(Matrix::identity(), this, activation)?;
apply_matrix_to_object(Matrix::IDENTITY, this, activation)?;
} else {
if let Some(a) = args.get(0) {
this.set("a", *a, activation)?;
@ -168,7 +169,7 @@ fn identity<'gc>(
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
apply_matrix_to_object(Matrix::identity(), this, activation)?;
apply_matrix_to_object(Matrix::IDENTITY, this, activation)?;
Ok(Value::Undefined)
}

View File

@ -391,7 +391,7 @@ fn begin_gradient_fill<'gc>(
};
let gradient = Gradient {
matrix,
matrix: matrix.into(),
spread,
interpolation,
records,

View File

@ -1,10 +1,10 @@
use crate::matrix::Matrix;
use crate::shape_utils::DistilledShape;
pub use crate::{library::MovieLibrary, transform::Transform, Color};
use downcast_rs::Downcast;
use gc_arena::Collect;
use std::io::Read;
pub use swf;
use swf::Matrix;
pub trait RenderBackend: Downcast {
fn set_viewport_dimensions(&mut self, width: u32, height: u32);

View File

@ -1,5 +1,5 @@
use crate::matrix::Matrix;
use gc_arena::Collect;
use swf::Matrix;
use swf::Twips;
#[derive(Clone, Debug, PartialEq, Collect)]

View File

@ -1061,8 +1061,8 @@ pub trait TDisplayObject<'gc>:
) {
// PlaceObject tags only apply if this object has not been dynamically moved by AS code.
if !self.transformed_by_script() {
if let Some(matrix) = &place_object.matrix {
self.set_matrix(context.gc_context, matrix);
if let Some(matrix) = place_object.matrix {
self.set_matrix(context.gc_context, &matrix.into());
}
if let Some(color_transform) = &place_object.color_transform {
self.set_color_transform(context.gc_context, &color_transform.clone().into());
@ -1384,13 +1384,13 @@ macro_rules! impl_display_object_sansbounds {
fn transform(&self) -> std::cell::Ref<crate::transform::Transform> {
std::cell::Ref::map(self.0.read(), |o| o.$field.transform())
}
fn matrix(&self) -> std::cell::Ref<swf::Matrix> {
fn matrix(&self) -> std::cell::Ref<crate::matrix::Matrix> {
std::cell::Ref::map(self.0.read(), |o| o.$field.matrix())
}
fn matrix_mut(
&self,
context: gc_arena::MutationContext<'gc, '_>,
) -> std::cell::RefMut<swf::Matrix> {
) -> std::cell::RefMut<crate::matrix::Matrix> {
std::cell::RefMut::map(self.0.write(context), |o| o.$field.matrix_mut())
}
fn color_transform(&self) -> std::cell::Ref<crate::color_transform::ColorTransform> {
@ -1618,7 +1618,11 @@ macro_rules! impl_display_object {
fn set_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) {
self.0.write(gc_context).$field.set_y(value)
}
fn set_matrix(&self, context: gc_arena::MutationContext<'gc, '_>, matrix: &swf::Matrix) {
fn set_matrix(
&self,
context: gc_arena::MutationContext<'gc, '_>,
matrix: &crate::matrix::Matrix,
) {
self.0.write(context).$field.set_matrix(matrix)
}
};

View File

@ -177,7 +177,7 @@ impl<'gc> Avm1Button<'gc> {
};
// Set transform of child (and modify previous child if it already existed)
child.set_matrix(context.gc_context, &record.matrix);
child.set_matrix(context.gc_context, &record.matrix.into());
child.set_color_transform(
context.gc_context,
&record.color_transform.clone().into(),
@ -288,7 +288,7 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
.instantiate_by_id(record.id, context.gc_context)
{
Ok(child) => {
child.set_matrix(context.gc_context, &record.matrix);
child.set_matrix(context.gc_context, &record.matrix.into());
child.set_parent(context.gc_context, Some(self_display_object));
child.set_depth(context.gc_context, record.depth.into());
new_children.push((child, record.depth.into()));

View File

@ -203,7 +203,7 @@ impl<'gc> Avm2Button<'gc> {
.instantiate_by_id(record.id, context.gc_context)
{
Ok(child) => {
child.set_matrix(context.gc_context, &record.matrix);
child.set_matrix(context.gc_context, &record.matrix.into());
child.set_depth(context.gc_context, record.depth.into());
if swf_state != swf::ButtonState::HIT_TEST {

View File

@ -6,7 +6,7 @@ use crate::tag_utils::SwfMovie;
use crate::types::{Degrees, Percent};
use gc_arena::{Collect, Gc, GcCell, MutationContext};
use std::sync::Arc;
use swf::{Fixed8, Twips};
use swf::{Fixed16, Fixed8, Twips};
#[derive(Clone, Debug, Collect, Copy)]
#[collect(no_drop)]
@ -482,11 +482,13 @@ fn lerp_edges(
fn lerp_matrix(start: &swf::Matrix, end: &swf::Matrix, a: f32, b: f32) -> swf::Matrix {
// TODO: Lerping a matrix element-wise is geometrically wrong,
// but I doubt Flash is decomposing the matrix into scale-rotate-translate?
let af = Fixed16::from_f32(a);
let bf = Fixed16::from_f32(b);
swf::Matrix {
a: start.a * a + end.a * b,
b: start.b * a + end.b * b,
c: start.c * a + end.c * b,
d: start.d * a + end.d * b,
a: start.a * af + end.a * bf,
b: start.b * af + end.b * bf,
c: start.c * af + end.c * bf,
d: start.d * af + end.d * bf,
tx: lerp_twips(start.tx, end.tx, a, b),
ty: lerp_twips(start.ty, end.ty, a, b),
}

View File

@ -36,7 +36,7 @@ impl<'gc> Text<'gc> {
swf,
id: tag.id,
bounds: tag.bounds.clone().into(),
text_transform: tag.matrix,
text_transform: tag.matrix.into(),
text_blocks: tag.records.clone(),
},
),

View File

@ -252,7 +252,7 @@ impl Drawing {
self.shape_bounds.clone()
}
pub fn hit_test(&self, point: (Twips, Twips), local_matrix: &swf::Matrix) -> bool {
pub fn hit_test(&self, point: (Twips, Twips), local_matrix: &crate::matrix::Matrix) -> bool {
use crate::shape_utils;
for path in &self.fills {
if shape_utils::draw_command_fill_hit_test(&path.1, point) {

View File

@ -38,6 +38,7 @@ mod font;
mod html;
mod library;
pub mod loader;
pub mod matrix;
mod player;
mod prelude;
pub mod shape_utils;

807
core/src/matrix.rs Normal file
View File

@ -0,0 +1,807 @@
#![allow(
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::suspicious_operation_groupings
)]
use swf::{Fixed16, Twips};
/// The transformation matrix used by Flash display objects.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Matrix {
/// Serialized as `scale_x` in SWF files
pub a: f32,
/// Serialized as `rotate_skew_0` in SWF files
pub b: f32,
/// Serialized as `rotate_skew_1` in SWF files
pub c: f32,
/// Serialized as `scale_y` in SWF files
pub d: f32,
/// Serialized as `transform_x` in SWF files
pub tx: Twips,
/// Serialized as `transform_y` in SWF files
pub ty: Twips,
}
impl Matrix {
pub const IDENTITY: Self = Self {
a: 1.0,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: 1.0,
ty: Twips::ZERO,
};
pub fn scale(scale_x: f32, scale_y: f32) -> Self {
Self {
a: scale_x,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: scale_y,
ty: Twips::ZERO,
}
}
pub fn rotate(angle: f32) -> Self {
Self {
a: angle.cos(),
c: -angle.sin(),
tx: Twips::ZERO,
b: angle.sin(),
d: angle.cos(),
ty: Twips::ZERO,
}
}
pub fn translate(x: Twips, y: Twips) -> Self {
Self {
a: 1.0,
c: 0.0,
tx: x,
b: 0.0,
d: 1.0,
ty: y,
}
}
pub fn create_box(
scale_x: f32,
scale_y: f32,
rotation: f32,
translate_x: Twips,
translate_y: Twips,
) -> Self {
if rotation != 0.0 {
Self {
a: rotation.cos() * scale_x,
c: -rotation.sin() * scale_x,
tx: translate_x,
b: rotation.sin() * scale_y,
d: rotation.cos() * scale_y,
ty: translate_y,
}
} else {
Self {
a: scale_x,
c: 0.0,
tx: translate_x,
b: 0.0,
d: scale_y,
ty: translate_y,
}
}
}
pub fn create_gradient_box(
width: f32,
height: f32,
rotation: f32,
translate_x: Twips,
translate_y: Twips,
) -> Self {
Self::create_box(
width / 1638.4,
height / 1638.4,
rotation,
translate_x + Twips::from_pixels((width / 2.0) as f64),
translate_y + Twips::from_pixels((height / 2.0) as f64),
)
}
pub fn invert(&mut self) {
let (tx, ty) = (self.tx.get() as f32, self.ty.get() as f32);
let det = self.a * self.d - self.b * self.c;
let a = self.d / det;
let b = self.b / -det;
let c = self.c / -det;
let d = self.a / det;
let (out_tx, out_ty) = (
round_to_i32((self.d * tx - self.c * ty) / -det),
round_to_i32((self.b * tx - self.a * ty) / det),
);
*self = Matrix {
a,
b,
c,
d,
tx: Twips::new(out_tx),
ty: Twips::new(out_ty),
};
}
}
impl std::ops::Mul for Matrix {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let (rhs_tx, rhs_ty) = (rhs.tx.get() as f32, rhs.ty.get() as f32);
let (out_tx, out_ty) = (
round_to_i32(self.a * rhs_tx + self.c * rhs_ty).wrapping_add(self.tx.get()),
round_to_i32(self.b * rhs_tx + self.d * rhs_ty).wrapping_add(self.ty.get()),
);
Matrix {
a: self.a * rhs.a + self.c * rhs.b,
b: self.b * rhs.a + self.d * rhs.b,
c: self.a * rhs.c + self.c * rhs.d,
d: self.b * rhs.c + self.d * rhs.d,
tx: Twips::new(out_tx),
ty: Twips::new(out_ty),
}
}
}
impl std::ops::Mul<(Twips, Twips)> for Matrix {
type Output = (Twips, Twips);
fn mul(self, (x, y): (Twips, Twips)) -> (Twips, Twips) {
let (x, y) = (x.get() as f32, y.get() as f32);
let out_x = round_to_i32(self.a * x + self.c * y).wrapping_add(self.tx.get());
let out_y = round_to_i32(self.b * x + self.d * y).wrapping_add(self.ty.get());
(Twips::new(out_x), Twips::new(out_y))
}
}
impl std::default::Default for Matrix {
fn default() -> Matrix {
Matrix::IDENTITY
}
}
impl std::ops::MulAssign for Matrix {
fn mul_assign(&mut self, rhs: Self) {
let (rhs_tx, rhs_ty) = (rhs.tx.get() as f32, rhs.ty.get() as f32);
let (out_tx, out_ty) = (
round_to_i32(self.a * rhs_tx + self.c * rhs_ty) + self.tx.get(),
round_to_i32(self.b * rhs_tx + self.d * rhs_ty) + self.ty.get(),
);
*self = Matrix {
a: self.a * rhs.a + self.c * rhs.b,
b: self.b * rhs.a + self.d * rhs.b,
c: self.a * rhs.c + self.c * rhs.d,
d: self.b * rhs.c + self.d * rhs.d,
tx: Twips::new(out_tx),
ty: Twips::new(out_ty),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::{assert_ulps_eq, AbsDiffEq, UlpsEq};
macro_rules! test_invert {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (mut input, output) = $args;
input.invert();
assert_ulps_eq!(input, output);
)*
}
};
}
macro_rules! test_multiply {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (input1, input2, output) = $args;
assert_ulps_eq!(input1 * input2, output);
)*
}
};
}
macro_rules! test_multiply_twips {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (input1, input2, output) = $args;
assert_eq!(input1 * input2, output);
)*
}
};
}
impl AbsDiffEq for Matrix {
type Epsilon = (<f32 as AbsDiffEq>::Epsilon, <i32 as AbsDiffEq>::Epsilon);
fn default_epsilon() -> Self::Epsilon {
(f32::default_epsilon(), i32::default_epsilon())
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.a.abs_diff_eq(&other.a, epsilon.0)
&& self.b.abs_diff_eq(&other.b, epsilon.0)
&& self.c.abs_diff_eq(&other.c, epsilon.0)
&& self.d.abs_diff_eq(&other.d, epsilon.0)
&& self.tx.get().abs_diff_eq(&other.tx.get(), epsilon.1)
&& self.ty.get().abs_diff_eq(&other.ty.get(), epsilon.1)
}
}
impl UlpsEq for Matrix {
fn default_max_ulps() -> u32 {
f32::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.a.ulps_eq(&other.a, epsilon.0, max_ulps)
&& self.b.ulps_eq(&other.b, epsilon.0, max_ulps)
&& self.c.ulps_eq(&other.c, epsilon.0, max_ulps)
&& self.d.ulps_eq(&other.d, epsilon.0, max_ulps)
&& self.tx == other.tx
&& self.ty == other.ty
}
}
// Identity matrix inverted should be unchanged
test_invert!(
invert_identity_matrix,
(Matrix::default(), Matrix::default())
);
// Standard test cases; there's nothing special about these matrices
test_invert!(
invert_matrices,
(
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix {
a: -1.666_666_6,
c: 1.333_333_3,
tx: Twips::from_pixels(9.0),
b: 0.666_666_6,
d: -0.333_333_3,
ty: Twips::from_pixels(-4.0)
}
),
(
Matrix {
a: -1.0,
c: -4.0,
tx: Twips::from_pixels(-7.0),
b: -2.0,
d: -5.0,
ty: Twips::from_pixels(-2.0)
},
Matrix {
a: 1.666_666_6,
c: -1.333_333_3,
tx: Twips::from_pixels(9.0),
b: -0.666_666_6,
d: 0.333_333_3,
ty: Twips::from_pixels(-4.0)
}
),
(
Matrix {
a: 1.5,
c: 1.2,
tx: Twips::from_pixels(1.0),
b: -2.7,
d: 3.4,
ty: Twips::from_pixels(-2.4)
},
Matrix {
a: 0.407_673_9,
c: -0.143_884_9,
tx: Twips::from_pixels(-0.752_997_6),
b: 0.323_741,
d: 0.179_856_1,
ty: Twips::from_pixels(0.107_913_67)
}
),
(
Matrix {
a: -2.0,
c: 0.0,
tx: Twips::from_pixels(10.0),
b: 0.0,
d: -1.0,
ty: Twips::from_pixels(5.0)
},
Matrix {
a: -0.5,
c: 0.0,
tx: Twips::from_pixels(5.0),
b: 0.0,
d: -1.0,
ty: Twips::from_pixels(5.0)
}
)
);
// Anything multiplied by the identity matrix should be unchanged
test_multiply!(
multiply_identity_matrix,
(Matrix::default(), Matrix::default(), Matrix::default()),
(
Matrix::default(),
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
}
),
(
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix::default(),
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
}
)
);
// General test cases for matrix multiplication
test_multiply!(
multiply_matrices,
(
Matrix {
a: 6.0,
c: 4.0,
tx: Twips::new(2),
b: 5.0,
d: 3.0,
ty: Twips::new(1)
},
Matrix {
a: 1.0,
c: 3.0,
tx: Twips::new(5),
b: 2.0,
d: 4.0,
ty: Twips::new(6)
},
Matrix {
a: 14.0,
c: 34.0,
tx: Twips::new(56),
b: 11.0,
d: 27.0,
ty: Twips::new(44)
}
),
(
Matrix {
a: 1.0,
c: 3.0,
tx: Twips::new(5),
b: 2.0,
d: 4.0,
ty: Twips::new(6)
},
Matrix {
a: 6.0,
c: 4.0,
tx: Twips::new(2),
b: 5.0,
d: 3.0,
ty: Twips::new(1)
},
Matrix {
a: 21.0,
c: 13.0,
tx: Twips::new(10),
b: 32.0,
d: 20.0,
ty: Twips::new(14)
}
),
(
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 6.0,
c: 5.0,
tx: Twips::new(4),
b: 3.0,
d: 2.0,
ty: Twips::new(1)
},
Matrix {
a: 12.0,
c: 9.0,
tx: Twips::new(9),
b: 39.0,
d: 30.0,
ty: Twips::new(27)
}
),
(
Matrix {
a: 6.0,
c: 5.0,
tx: Twips::new(4),
b: 3.0,
d: 2.0,
ty: Twips::new(1)
},
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 26.0,
c: 37.0,
tx: Twips::new(52),
b: 11.0,
d: 16.0,
ty: Twips::new(22)
}
),
(
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 9.0,
c: 12.0,
tx: Twips::new(18),
b: 24.0,
d: 33.0,
ty: Twips::new(48)
}
)
);
// Twips multiplied by the identity/default matrix should be unchanged
test_multiply_twips!(
multiply_twips_identity_matrix,
(
Matrix::default(),
(Twips::ZERO, Twips::ZERO),
(Twips::ZERO, Twips::ZERO)
),
(
Matrix::default(),
(Twips::ZERO, Twips::new(10)),
(Twips::ZERO, Twips::new(10))
),
(
Matrix::default(),
(Twips::new(10), Twips::ZERO),
(Twips::new(10), Twips::ZERO)
),
(
Matrix::default(),
(Twips::new(-251), Twips::new(152)),
(Twips::new(-251), Twips::new(152))
)
);
// multiply by translate matrices; values should be shifted
test_multiply_twips!(
multiply_twips_translate,
(
Matrix {
a: 1.0,
c: 0.0,
tx: Twips::new(10),
b: 0.0,
d: 1.0,
ty: Twips::new(5)
},
(Twips::ZERO, Twips::ZERO),
(Twips::new(10), Twips::new(5))
),
(
Matrix {
a: 1.0,
c: 0.0,
tx: Twips::new(-200),
b: 0.0,
d: 1.0,
ty: Twips::ZERO
},
(Twips::new(50), Twips::new(20)),
(Twips::new(-150), Twips::new(20))
)
);
// multiply by scalar matrices; values should be scaled up/down
test_multiply_twips!(
multiply_twips_scale,
(
Matrix {
a: 3.0,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: 3.0,
ty: Twips::ZERO
},
(Twips::ZERO, Twips::ZERO),
(Twips::ZERO, Twips::ZERO)
),
(
Matrix {
a: 3.0,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: 3.0,
ty: Twips::ZERO
},
(Twips::new(10), Twips::new(10)),
(Twips::new(30), Twips::new(30))
),
(
Matrix {
a: 0.6,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: 0.2,
ty: Twips::ZERO
},
(Twips::new(5), Twips::new(10)),
(Twips::new(3), Twips::new(2))
),
(
Matrix {
a: 0.5,
c: 0.0,
tx: Twips::ZERO,
b: 0.0,
d: 0.5,
ty: Twips::ZERO
},
(Twips::new(5), Twips::new(5)),
(Twips::new(2), Twips::new(2))
)
);
// multiply by rotation matrices; values should be rotated around origin
test_multiply_twips!(
multiply_twips_rotation,
(
Matrix {
a: 0.0,
c: -1.0,
tx: Twips::ZERO,
b: 1.0,
d: 0.0,
ty: Twips::ZERO
},
(Twips::new(10), Twips::ZERO),
(Twips::ZERO, Twips::new(10))
),
(
Matrix {
a: 0.0,
c: -1.0,
tx: Twips::ZERO,
b: 1.0,
d: 0.0,
ty: Twips::ZERO
},
(Twips::ZERO, Twips::new(10)),
(Twips::new(-10), Twips::ZERO)
),
(
Matrix {
a: 0.0,
c: 1.0,
tx: Twips::ZERO,
b: -1.0,
d: 0.0,
ty: Twips::ZERO
},
(Twips::new(10), Twips::new(10)),
(Twips::new(10), Twips::new(-10))
),
(
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::ZERO,
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::ZERO
},
(Twips::new(100), Twips::ZERO),
(Twips::new(71), Twips::new(-71))
),
(
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::ZERO,
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::ZERO
},
(Twips::new(100), Twips::new(100)),
(Twips::new(141), Twips::ZERO)
)
);
// Testing transformation matrices that have more than 1 translation applied
test_multiply_twips!(
multiply_twips_complex,
(
// result of scaling by 3 * rotation by 45 degrees
Matrix {
a: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
c: 3.0 * f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::ZERO,
b: 3.0 * -f32::sin(std::f32::consts::FRAC_PI_4),
d: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::ZERO
},
(Twips::new(100), Twips::new(100)),
(Twips::new(424), Twips::ZERO)
),
(
// result of translating by (-5, 5) * rotation by 45 degrees
Matrix {
a: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
c: 3.0 * f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::new(-5),
b: 3.0 * -f32::sin(std::f32::consts::FRAC_PI_4),
d: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new(5)
},
(Twips::new(100), Twips::new(100)),
(Twips::new(419), Twips::new(5))
),
(
// result of rotation by 45 degrees * translating by (-5, 5)
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::new(-5),
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new(5)
},
(Twips::new(100), Twips::new(100)),
(Twips::new(136), Twips::new(5))
),
(
// result of translating by (-5, 5) * rotation by 45 degrees
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::ZERO,
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new((10.0 * f32::sin(std::f32::consts::FRAC_PI_4)) as i32)
},
(Twips::new(105), Twips::new(95)),
(Twips::new(141), Twips::ZERO)
)
);
}
impl From<swf::Matrix> for Matrix {
fn from(matrix: swf::Matrix) -> Self {
Self {
a: matrix.a.to_f32(),
b: matrix.b.to_f32(),
c: matrix.c.to_f32(),
d: matrix.d.to_f32(),
tx: matrix.tx,
ty: matrix.ty,
}
}
}
impl From<Matrix> for swf::Matrix {
fn from(matrix: Matrix) -> Self {
Self {
a: Fixed16::from_f32(matrix.a),
b: Fixed16::from_f32(matrix.b),
c: Fixed16::from_f32(matrix.c),
d: Fixed16::from_f32(matrix.d),
tx: matrix.tx,
ty: matrix.ty,
}
}
}
/// Implements the IEEE-754 "Round to nearest, ties to even" rounding rule.
/// (e.g., both 1.5 and 2.5 will round to 2).
/// This is the rounding method used by Flash for the above transforms.
/// Although this is easy to do on most architectures, Rust provides no standard
/// way to round in this manner (`f32::round` always rounds away from zero).
/// For more info and the below code snippet, see: https://github.com/rust-lang/rust/issues/55107
/// This also clamps out-of-range values and NaN to `i32::MIN`.
/// TODO: Investigate using SSE/wasm intrinsics for this.
fn round_to_i32(f: f32) -> i32 {
if f.is_finite() {
let a = f.abs();
if f < 2_147_483_648.0_f32 {
let k = 1.0 / f32::EPSILON;
let out = if a < k { ((a + k) - k).copysign(f) } else { f };
out as i32
} else {
// Out-of-range clamps to MIN.
i32::MIN
}
} else {
// NaN/Infinity goes to 0.
0
}
}

View File

@ -5,12 +5,12 @@ pub use crate::display_object::{
DisplayObject, DisplayObjectContainer, HitTestOptions, Lists, TDisplayObject,
TDisplayObjectContainer,
};
pub use crate::matrix::Matrix;
pub use crate::{
impl_display_object, impl_display_object_container, impl_display_object_sansbounds,
};
pub use log::{error, info, trace, warn};
pub use std::ops::{Bound, RangeBounds};
pub use swf::Matrix;
pub use swf::{CharacterId, Color, Twips};
/// A depth for a Flash display object in AVM1.

View File

@ -1,8 +1,8 @@
use crate::bounding_box::BoundingBox;
use crate::{bounding_box::BoundingBox, matrix::Matrix};
use fnv::FnvHashMap;
use smallvec::SmallVec;
use std::num::NonZeroU32;
use swf::{CharacterId, FillStyle, LineStyle, Matrix, Shape, ShapeRecord, Twips};
use swf::{CharacterId, FillStyle, LineStyle, Shape, ShapeRecord, Twips};
pub fn calculate_shape_bounds(shape_records: &[swf::ShapeRecord]) -> swf::Rectangle {
let mut bounds = swf::Rectangle {

View File

@ -4,8 +4,8 @@ use ruffle_core::backend::render::{
RenderBackend, ShapeHandle, Transform,
};
use ruffle_core::color_transform::ColorTransform;
use ruffle_core::matrix::Matrix;
use ruffle_core::shape_utils::{DistilledShape, DrawCommand};
use ruffle_core::swf::Matrix;
use ruffle_web_common::JsResult;
use std::convert::TryInto;
use wasm_bindgen::{JsCast, JsValue};
@ -845,7 +845,7 @@ fn swf_shape_to_svg(
ty: swf::Twips::new(-16384),
..Default::default()
};
let gradient_matrix = gradient.matrix * shift;
let gradient_matrix = Matrix::from(gradient.matrix) * shift;
let mut svg_gradient = LinearGradient::new()
.set("id", format!("f{}", num_defs))
@ -904,7 +904,7 @@ fn swf_shape_to_svg(
d: 32768.0,
..Default::default()
};
let gradient_matrix = gradient.matrix * shift;
let gradient_matrix = Matrix::from(gradient.matrix) * shift;
let mut svg_gradient = RadialGradient::new()
.set("id", format!("f{}", num_defs))
@ -969,7 +969,7 @@ fn swf_shape_to_svg(
d: 32768.0,
..Default::default()
};
let gradient_matrix = gradient.matrix * shift;
let gradient_matrix = Matrix::from(gradient.matrix) * shift;
let mut svg_gradient = RadialGradient::new()
.set("id", format!("f{}", num_defs))
@ -1346,10 +1346,10 @@ fn swf_shape_to_canvas_commands(
let matrix = matrix_factory.create_svg_matrix();
matrix.set_a(a.a);
matrix.set_b(a.b);
matrix.set_c(a.c);
matrix.set_d(a.d);
matrix.set_a(a.a.to_f32());
matrix.set_b(a.b.to_f32());
matrix.set_c(a.c.to_f32());
matrix.set_d(a.d.to_f32());
matrix.set_e(a.tx.get() as f32);
matrix.set_f(a.ty.get() as f32);

View File

@ -185,7 +185,7 @@ impl ShapeTessellator {
flush_draw(
DrawType::Bitmap(Bitmap {
matrix: swf_bitmap_to_gl_matrix(
*matrix,
(*matrix).into(),
bitmap_width,
bitmap_height,
),
@ -320,7 +320,7 @@ pub struct Bitmap {
}
#[allow(clippy::many_single_char_names)]
fn swf_to_gl_matrix(m: swf::Matrix) -> [[f32; 3]; 3] {
fn swf_to_gl_matrix(m: ruffle_core::matrix::Matrix) -> [[f32; 3]; 3] {
let tx = m.tx.get() as f32;
let ty = m.ty.get() as f32;
let det = m.a * m.d - m.c * m.b;
@ -344,7 +344,11 @@ fn swf_to_gl_matrix(m: swf::Matrix) -> [[f32; 3]; 3] {
}
#[allow(clippy::many_single_char_names)]
fn swf_bitmap_to_gl_matrix(m: swf::Matrix, bitmap_width: u32, bitmap_height: u32) -> [[f32; 3]; 3] {
fn swf_bitmap_to_gl_matrix(
m: ruffle_core::matrix::Matrix,
bitmap_width: u32,
bitmap_height: u32,
) -> [[f32; 3]; 3] {
let bitmap_width = bitmap_width as f32;
let bitmap_height = bitmap_height as f32;
@ -440,7 +444,7 @@ fn swf_gradient_to_uniforms(
}
Gradient {
matrix: swf_to_gl_matrix(gradient.matrix),
matrix: swf_to_gl_matrix(gradient.matrix.into()),
gradient_type,
ratios,
colors,

View File

@ -933,7 +933,7 @@ impl RenderBackend for WebGlRenderBackend {
// Scale the quad to the bitmap's dimensions.
let matrix = transform.matrix
* swf::Matrix {
* ruffle_core::matrix::Matrix {
a: width,
d: height,
..Default::default()
@ -1167,7 +1167,7 @@ impl RenderBackend for WebGlRenderBackend {
}
}
fn draw_rect(&mut self, color: Color, matrix: &swf::Matrix) {
fn draw_rect(&mut self, color: Color, matrix: &ruffle_core::matrix::Matrix) {
let world_matrix = [
[matrix.a, matrix.b, 0.0, 0.0],
[matrix.c, matrix.d, 0.0, 0.0],

View File

@ -887,7 +887,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
let transform = Transform {
matrix: transform.matrix
* swf::Matrix {
* ruffle_core::matrix::Matrix {
a: texture.width as f32,
d: texture.height as f32,
..Default::default()
@ -1059,7 +1059,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
}
}
fn draw_rect(&mut self, color: Color, matrix: &swf::Matrix) {
fn draw_rect(&mut self, color: Color, matrix: &ruffle_core::matrix::Matrix) {
let frame = if let Some(frame) = &mut self.current_frame {
frame.get()
} else {

View File

@ -1,7 +1,6 @@
#![allow(
renamed_and_removed_lints,
clippy::unknown_clippy_lints,
clippy::float_cmp,
clippy::inconsistent_digit_grouping,
clippy::unreadable_literal
)]
@ -266,8 +265,8 @@ impl<'a, 'b> BitReader<'a, 'b> {
}
#[inline]
fn read_fbits(&mut self, num_bits: u32) -> io::Result<f32> {
self.read_sbits(num_bits).map(|n| (n as f32) / 65536f32)
fn read_fbits(&mut self, num_bits: u32) -> io::Result<Fixed16> {
self.read_sbits(num_bits).map(Fixed16::from_bits)
}
#[inline]
@ -693,7 +692,7 @@ impl<'a> Reader<'a> {
fn read_matrix(&mut self) -> Result<Matrix> {
let mut bits = self.bits();
let mut m = Matrix::identity();
let mut m = Matrix::IDENTITY;
// Scale
if bits.read_bit()? {
let num_bits = bits.read_ubits(5)?;
@ -2777,20 +2776,23 @@ pub mod tests {
#[test]
fn read_fbits() {
assert_eq!(Reader::new(&[0][..], 1).bits().read_fbits(5).unwrap(), 0f32);
assert_eq!(
Reader::new(&[0][..], 1).bits().read_fbits(5).unwrap(),
Fixed16::ZERO
);
assert_eq!(
Reader::new(&[0b01000000, 0b00000000, 0b0_0000000][..], 1)
.bits()
.read_fbits(17)
.unwrap(),
0.5f32
Fixed16::from_f32(0.5)
);
assert_eq!(
Reader::new(&[0b10000000, 0b00000000][..], 1)
.bits()
.read_fbits(16)
.unwrap(),
-0.5f32
Fixed16::from_f32(-0.5)
);
}
@ -2874,10 +2876,10 @@ pub mod tests {
Matrix {
tx: Twips::from_pixels(0.0),
ty: Twips::from_pixels(0.0),
a: 1f32,
d: 1f32,
b: 0f32,
c: 0f32,
a: Fixed16::ONE,
b: Fixed16::ZERO,
c: Fixed16::ZERO,
d: Fixed16::ONE,
}
);
}
@ -2965,7 +2967,7 @@ pub mod tests {
let fill_style = FillStyle::Bitmap {
id: 20,
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
is_smoothed: false,
is_repeating: true,
};
@ -2974,7 +2976,7 @@ pub mod tests {
fill_style
);
let mut matrix = Matrix::identity();
let mut matrix = Matrix::IDENTITY;
matrix.tx = Twips::from_pixels(1.0);
let fill_style = FillStyle::Bitmap {
id: 33,

View File

@ -201,7 +201,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
id: 1,
states: ButtonState::UP | ButtonState::OVER,
depth: 1,
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
color_transform: ColorTransform::new(),
filters: vec![],
blend_mode: BlendMode::Normal,
@ -210,7 +210,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
id: 2,
states: ButtonState::DOWN | ButtonState::HIT_TEST,
depth: 1,
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
color_transform: ColorTransform::new(),
filters: vec![],
blend_mode: BlendMode::Normal,
@ -234,7 +234,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
id: 2,
states: ButtonState::UP | ButtonState::OVER,
depth: 1,
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
color_transform: ColorTransform {
r_add: 200,
g_add: 0,
@ -253,7 +253,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
id: 3,
states: ButtonState::DOWN | ButtonState::HIT_TEST,
depth: 1,
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
color_transform: ColorTransform {
r_multiply: 0.into(),
g_multiply: 1.into(),
@ -726,10 +726,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(40.0),
ty: Twips::from_pixels(40.0),
a: 0.024429321,
d: 0.024429321,
b: 0.024429321,
c: -0.024429321,
a: Fixed16::from_f32(0.024429321),
d: Fixed16::from_f32(0.024429321),
b: Fixed16::from_f32(0.024429321),
c: Fixed16::from_f32(-0.024429321),
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -829,10 +829,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(48.4),
ty: Twips::from_pixels(34.65),
a: 0.0058898926,
d: 0.030914307,
b: 0.0,
c: 0.0,
a: Fixed16::from_f32(0.0058898926),
d: Fixed16::from_f32(0.030914307),
b: Fixed16::from_f32(0.0),
c: Fixed16::from_f32(0.0),
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -947,10 +947,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(116.05),
ty: Twips::from_pixels(135.05),
a: 0.11468506,
d: 0.18927002,
b: 0.0,
c: 0.0,
a: Fixed16::from_f32(0.11468506),
d: Fixed16::from_f32(0.18927002),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -1070,10 +1070,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(164.0),
ty: Twips::from_pixels(150.05),
a: 0.036087036,
d: 0.041992188,
b: 0.1347351,
c: -0.15675354,
a: Fixed16::from_f32(0.036087036),
d: Fixed16::from_f32(0.041992188),
b: Fixed16::from_f32(0.1347351),
c: Fixed16::from_f32(-0.15675354),
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -1206,10 +1206,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(100.00),
ty: Twips::from_pixels(100.00),
a: 0.1725769,
d: 0.1725769,
b: 0.0,
c: 0.0,
a: Fixed16::from_f32(0.1725769),
d: Fixed16::from_f32(0.1725769),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
},
spread: GradientSpread::Reflect,
interpolation: GradientInterpolation::LinearRgb,
@ -1294,10 +1294,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(100.00),
ty: Twips::from_pixels(100.00),
a: 0.000015258789,
d: 0.000015258789,
b: 0.084503174,
c: -0.084503174,
a: Fixed16::from_f32(0.000015258789),
d: Fixed16::from_f32(0.000015258789),
b: Fixed16::from_f32(0.084503174),
c: Fixed16::from_f32(-0.084503174),
},
spread: GradientSpread::Reflect,
interpolation: GradientInterpolation::LinearRgb,
@ -1503,10 +1503,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(24.95),
ty: Twips::from_pixels(24.95),
a: 0.030731201f32,
d: 0.030731201f32,
b: 0f32,
c: 0f32,
a: Fixed16::from_f32(0.030731201),
d: Fixed16::from_f32(0.030731201),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -1626,10 +1626,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(49.55),
ty: Twips::from_pixels(46.55),
a: 0.06199646f32,
d: 0.06199646f32,
b: 0f32,
c: 0f32,
a: Fixed16::from_f32(0.06199646),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
d: Fixed16::from_f32(0.06199646),
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::LinearRgb,
@ -1690,10 +1690,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Matrix {
tx: Twips::from_pixels(50.0),
ty: Twips::from_pixels(50.0),
a: 0.07324219f32,
d: 0.07324219f32,
b: 0f32,
c: 0f32,
a: Fixed16::from_f32(0.07324219),
d: Fixed16::from_f32(0.07324219),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::Rgb,
@ -1875,7 +1875,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
y_min: Twips::from_pixels(4.1),
y_max: Twips::from_pixels(18.45),
},
matrix: Matrix::identity(),
matrix: Matrix::IDENTITY,
records: vec![TextRecord {
font_id: Some(1),
color: Some(Color {
@ -2069,7 +2069,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(1),
depth: 1,
matrix: Some(Matrix::identity()),
matrix: Some(Matrix::IDENTITY),
color_transform: None,
ratio: None,
name: None,
@ -2092,7 +2092,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix::identity()),
matrix: Some(Matrix::IDENTITY),
color_transform: None,
ratio: None,
name: None,
@ -2122,7 +2122,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix::identity()),
matrix: Some(Matrix::IDENTITY),
color_transform: None,
ratio: None,
name: None,
@ -2167,10 +2167,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Some(Matrix {
tx: Twips::from_pixels(0.0),
ty: Twips::from_pixels(0.0),
b: 0f32,
c: 0f32,
a: 1.0f32,
d: 1.0f32,
a: Fixed16::ONE,
b: Fixed16::ZERO,
c: Fixed16::ZERO,
d: Fixed16::ONE,
}),
color_transform: None,
ratio: None,
@ -2197,10 +2197,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Some(Matrix {
tx: Twips::from_pixels(10.0),
ty: Twips::from_pixels(10.0),
b: 0f32,
c: 0f32,
a: 2.0f32,
d: 2.0f32,
a: Fixed16::from_f32(2.0),
b: Fixed16::ZERO,
c: Fixed16::ZERO,
d: Fixed16::from_f32(2.0),
}),
color_transform: Some(ColorTransform {
a_multiply: Fixed8::from_f32(1.0),
@ -2333,10 +2333,10 @@ pub fn tag_tests() -> Vec<TagTestData> {
matrix: Some(Matrix {
tx: Twips::from_pixels(10.0),
ty: Twips::from_pixels(10.0),
b: 0.0,
c: 0.0,
a: 1.0,
d: 1.0,
b: Fixed16::ZERO,
c: Fixed16::ZERO,
a: Fixed16::ONE,
d: Fixed16::ONE,
}),
color_transform: None,
ratio: None,
@ -2603,8 +2603,8 @@ pub fn tag_tests() -> Vec<TagTestData> {
fill_style: Some(FillStyle::Bitmap {
id: 1,
matrix: Matrix {
a: 20.0,
d: 20.0,
a: Fixed16::from_f32(20.0),
d: Fixed16::from_f32(20.0),
tx: Twips::from_pixels(10.0),
ty: Twips::from_pixels(10.0),
..Default::default()

View File

@ -4,147 +4,131 @@
clippy::suspicious_operation_groupings
)]
use crate::Twips;
use crate::{Fixed16, Twips};
use std::ops;
#[derive(Copy, Clone, Debug, PartialEq)]
/// The transformation matrix used by Flash display objects.
///
/// The matrix is a 2x3 affine transformation matrix. A point (x, y) is transformed by the matrix
/// in the following way:
/// ```text
/// [a c tx] * [x] = [a*x + c*y + tx]
/// [b d ty] [y] [b*x + d*y + ty]
/// [0 0 1 ] [1] [1 ]
/// ```
///
/// The SWF format uses 16.16 format for `a`, `b`, `c`, `d`. Twips are used for `tx` and `ty`.
/// This means that objects in Flash can only move in units of twips, or 1/20 pixels.
///
/// [SWF19 pp.22-24](https://www.adobe.com/content/dam/acom/en/devnet/pdf/swf-file-format-spec.pdf#page=22)
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Matrix {
/// Serialized as `scale_x` in SWF files
pub a: f32,
/// The matrix element at `[0, 0]`. Labeled `ScaleX` in SWF19.
pub a: Fixed16,
/// Serialized as `rotate_skew_0` in SWF files
pub b: f32,
/// The matrix element at `[1, 0]`. Labeled `RotateSkew0` in SWF19.
pub b: Fixed16,
/// Serialized as `rotate_skew_1` in SWF files
pub c: f32,
/// The matrix element at `[0, 1]`. Labeled `RotateSkew1` in SWF19.
pub c: Fixed16,
/// Serialized as `scale_y` in SWF files
pub d: f32,
/// The matrix element at `[1, 1]`. Labeled `ScaleY` in SWF19.
pub d: Fixed16,
/// Serialized as `transform_x` in SWF files
/// The X translation in twips. Labeled `TranslateX` in SWF19.
pub tx: Twips,
/// Serialized as `transform_y` in SWF files
/// The Y translation in twips. Labeled `TranslateX` in SWF19.
pub ty: Twips,
}
impl Matrix {
pub fn identity() -> Self {
Self {
a: 1.0,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: 1.0,
/// The identity matrix.
///
/// Transforming an object by this matrix has no effect.
pub const IDENTITY: Self = Self {
a: Fixed16::ONE,
c: Fixed16::ZERO,
b: Fixed16::ZERO,
d: Fixed16::ONE,
ty: Twips::zero(),
}
}
tx: Twips::zero(),
};
pub fn scale(scale_x: f32, scale_y: f32) -> Self {
/// Returns a scale matrix.
#[inline]
pub fn scale(scale_x: Fixed16, scale_y: Fixed16) -> Self {
Self {
a: scale_x,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: scale_y,
ty: Twips::zero(),
..Default::default()
}
}
/// Returns a rotation matrix that rotates by `angle` radians.
#[inline]
pub fn rotate(angle: f32) -> Self {
Self {
a: angle.cos(),
c: -angle.sin(),
tx: Twips::zero(),
b: angle.sin(),
d: angle.cos(),
ty: Twips::zero(),
a: Fixed16::from_f32(angle.cos()),
c: Fixed16::from_f32(-angle.sin()),
b: Fixed16::from_f32(angle.sin()),
d: Fixed16::from_f32(angle.cos()),
..Default::default()
}
}
/// Returns a translation matrix.
#[inline]
pub fn translate(x: Twips, y: Twips) -> Self {
Self {
a: 1.0,
c: 0.0,
tx: x,
b: 0.0,
d: 1.0,
ty: y,
..Default::default()
}
}
pub fn create_box(
scale_x: f32,
scale_y: f32,
rotation: f32,
translate_x: Twips,
translate_y: Twips,
) -> Self {
if rotation != 0.0 {
Self {
a: rotation.cos() * scale_x,
c: -rotation.sin() * scale_x,
tx: translate_x,
b: rotation.sin() * scale_y,
d: rotation.cos() * scale_y,
ty: translate_y,
}
} else {
Self {
a: scale_x,
c: 0.0,
tx: translate_x,
b: 0.0,
d: scale_y,
ty: translate_y,
}
}
}
pub fn create_gradient_box(
width: f32,
height: f32,
rotation: f32,
translate_x: Twips,
translate_y: Twips,
) -> Self {
Self::create_box(
width / 1638.4,
height / 1638.4,
rotation,
translate_x + Twips::from_pixels((width / 2.0) as f64),
translate_y + Twips::from_pixels((height / 2.0) as f64),
)
}
/// Inverts the matrix.
///
/// If the matrix is not invertible, the resulting matrix will be invalid.
#[inline]
pub fn invert(&mut self) {
// If we actually use this, may want to do this directly in fixed point instead of casting to f32.
let (tx, ty) = (self.tx.get() as f32, self.ty.get() as f32);
let det = self.a * self.d - self.b * self.c;
let a = self.d / det;
let b = self.b / -det;
let c = self.c / -det;
let d = self.a / det;
let (out_tx, out_ty) = (
round_to_i32((self.d * tx - self.c * ty) / -det),
round_to_i32((self.b * tx - self.a * ty) / det),
);
let a = self.a.to_f32();
let b = self.b.to_f32();
let c = self.c.to_f32();
let d = self.d.to_f32();
let det = a * d - b * c;
let a = d / det;
let b = b / -det;
let c = c / -det;
let d = a / det;
let (out_tx, out_ty) = ((d * tx - c * ty) / -det, (b * tx - a * ty) / det);
*self = Matrix {
a,
b,
c,
d,
tx: Twips::new(out_tx),
ty: Twips::new(out_ty),
a: Fixed16::from_f32(a),
b: Fixed16::from_f32(b),
c: Fixed16::from_f32(c),
d: Fixed16::from_f32(d),
tx: Twips::new(out_tx as i32),
ty: Twips::new(out_ty as i32),
};
}
}
impl std::ops::Mul for Matrix {
impl ops::Mul for Matrix {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
let (rhs_tx, rhs_ty) = (rhs.tx.get() as f32, rhs.ty.get() as f32);
let (rhs_tx, rhs_ty) = (rhs.tx.get(), rhs.ty.get());
let (out_tx, out_ty) = (
round_to_i32(self.a * rhs_tx + self.c * rhs_ty).wrapping_add(self.tx.get()),
round_to_i32(self.b * rhs_tx + self.d * rhs_ty).wrapping_add(self.ty.get()),
self.a
.wrapping_mul_int(rhs_tx)
.wrapping_add(self.c.mul_int(rhs_ty))
.wrapping_add(self.tx.get()),
self.b
.wrapping_mul_int(rhs_tx)
.wrapping_add(self.d.mul_int(rhs_ty))
.wrapping_add(self.ty.get()),
);
Matrix {
a: self.a * rhs.a + self.c * rhs.b,
@ -157,626 +141,35 @@ impl std::ops::Mul for Matrix {
}
}
impl std::ops::Mul<(Twips, Twips)> for Matrix {
impl ops::Mul<(Twips, Twips)> for Matrix {
type Output = (Twips, Twips);
#[inline]
fn mul(self, (x, y): (Twips, Twips)) -> (Twips, Twips) {
let (x, y) = (x.get() as f32, y.get() as f32);
let out_x = round_to_i32(self.a * x + self.c * y).wrapping_add(self.tx.get());
let out_y = round_to_i32(self.b * x + self.d * y).wrapping_add(self.ty.get());
let (x, y) = (x.get(), y.get());
let out_x = (self
.a
.wrapping_mul_int(x)
.wrapping_add(self.c.wrapping_mul_int(y)))
.wrapping_add(self.tx.get());
let out_y = (self
.b
.wrapping_mul_int(x)
.wrapping_add(self.d.wrapping_mul_int(y)))
.wrapping_add(self.ty.get());
(Twips::new(out_x), Twips::new(out_y))
}
}
impl std::default::Default for Matrix {
impl Default for Matrix {
#[inline]
fn default() -> Matrix {
Matrix::identity()
Matrix::IDENTITY
}
}
impl std::ops::MulAssign for Matrix {
impl ops::MulAssign for Matrix {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
let (rhs_tx, rhs_ty) = (rhs.tx.get() as f32, rhs.ty.get() as f32);
let (out_tx, out_ty) = (
round_to_i32(self.a * rhs_tx + self.c * rhs_ty) + self.tx.get(),
round_to_i32(self.b * rhs_tx + self.d * rhs_ty) + self.ty.get(),
);
*self = Matrix {
a: self.a * rhs.a + self.c * rhs.b,
b: self.b * rhs.a + self.d * rhs.b,
c: self.a * rhs.c + self.c * rhs.d,
d: self.b * rhs.c + self.d * rhs.d,
tx: Twips::new(out_tx),
ty: Twips::new(out_ty),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::{assert_ulps_eq, AbsDiffEq, UlpsEq};
macro_rules! test_invert {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (mut input, output) = $args;
input.invert();
assert_ulps_eq!(input, output);
)*
}
};
}
macro_rules! test_multiply {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (input1, input2, output) = $args;
assert_ulps_eq!(input1 * input2, output);
)*
}
};
}
macro_rules! test_multiply_twips {
( $test: ident, $($args: expr),* ) => {
#[test]
fn $test() {
$(
let (input1, input2, output) = $args;
assert_eq!(input1 * input2, output);
)*
}
};
}
impl AbsDiffEq for Matrix {
type Epsilon = (<f32 as AbsDiffEq>::Epsilon, <i32 as AbsDiffEq>::Epsilon);
fn default_epsilon() -> Self::Epsilon {
(f32::default_epsilon(), i32::default_epsilon())
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.a.abs_diff_eq(&other.a, epsilon.0)
&& self.b.abs_diff_eq(&other.b, epsilon.0)
&& self.c.abs_diff_eq(&other.c, epsilon.0)
&& self.d.abs_diff_eq(&other.d, epsilon.0)
&& self.tx.get().abs_diff_eq(&other.tx.get(), epsilon.1)
&& self.ty.get().abs_diff_eq(&other.ty.get(), epsilon.1)
}
}
impl UlpsEq for Matrix {
fn default_max_ulps() -> u32 {
f32::default_max_ulps()
}
fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
self.a.ulps_eq(&other.a, epsilon.0, max_ulps)
&& self.b.ulps_eq(&other.b, epsilon.0, max_ulps)
&& self.c.ulps_eq(&other.c, epsilon.0, max_ulps)
&& self.d.ulps_eq(&other.d, epsilon.0, max_ulps)
&& self.tx == other.tx
&& self.ty == other.ty
}
}
// Identity matrix inverted should be unchanged
test_invert!(
invert_identity_matrix,
(Matrix::default(), Matrix::default())
);
// Standard test cases; there's nothing special about these matrices
test_invert!(
invert_matrices,
(
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix {
a: -1.666_666_6,
c: 1.333_333_3,
tx: Twips::from_pixels(9.0),
b: 0.666_666_6,
d: -0.333_333_3,
ty: Twips::from_pixels(-4.0)
}
),
(
Matrix {
a: -1.0,
c: -4.0,
tx: Twips::from_pixels(-7.0),
b: -2.0,
d: -5.0,
ty: Twips::from_pixels(-2.0)
},
Matrix {
a: 1.666_666_6,
c: -1.333_333_3,
tx: Twips::from_pixels(9.0),
b: -0.666_666_6,
d: 0.333_333_3,
ty: Twips::from_pixels(-4.0)
}
),
(
Matrix {
a: 1.5,
c: 1.2,
tx: Twips::from_pixels(1.0),
b: -2.7,
d: 3.4,
ty: Twips::from_pixels(-2.4)
},
Matrix {
a: 0.407_673_9,
c: -0.143_884_9,
tx: Twips::from_pixels(-0.752_997_6),
b: 0.323_741,
d: 0.179_856_1,
ty: Twips::from_pixels(0.107_913_67)
}
),
(
Matrix {
a: -2.0,
c: 0.0,
tx: Twips::from_pixels(10.0),
b: 0.0,
d: -1.0,
ty: Twips::from_pixels(5.0)
},
Matrix {
a: -0.5,
c: 0.0,
tx: Twips::from_pixels(5.0),
b: 0.0,
d: -1.0,
ty: Twips::from_pixels(5.0)
}
)
);
// Anything multiplied by the identity matrix should be unchanged
test_multiply!(
multiply_identity_matrix,
(Matrix::default(), Matrix::default(), Matrix::default()),
(
Matrix::default(),
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
}
),
(
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
},
Matrix::default(),
Matrix {
a: 1.0,
c: 4.0,
tx: Twips::from_pixels(7.0),
b: 2.0,
d: 5.0,
ty: Twips::from_pixels(2.0)
}
)
);
// General test cases for matrix multiplication
test_multiply!(
multiply_matrices,
(
Matrix {
a: 6.0,
c: 4.0,
tx: Twips::new(2),
b: 5.0,
d: 3.0,
ty: Twips::new(1)
},
Matrix {
a: 1.0,
c: 3.0,
tx: Twips::new(5),
b: 2.0,
d: 4.0,
ty: Twips::new(6)
},
Matrix {
a: 14.0,
c: 34.0,
tx: Twips::new(56),
b: 11.0,
d: 27.0,
ty: Twips::new(44)
}
),
(
Matrix {
a: 1.0,
c: 3.0,
tx: Twips::new(5),
b: 2.0,
d: 4.0,
ty: Twips::new(6)
},
Matrix {
a: 6.0,
c: 4.0,
tx: Twips::new(2),
b: 5.0,
d: 3.0,
ty: Twips::new(1)
},
Matrix {
a: 21.0,
c: 13.0,
tx: Twips::new(10),
b: 32.0,
d: 20.0,
ty: Twips::new(14)
}
),
(
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 6.0,
c: 5.0,
tx: Twips::new(4),
b: 3.0,
d: 2.0,
ty: Twips::new(1)
},
Matrix {
a: 12.0,
c: 9.0,
tx: Twips::new(9),
b: 39.0,
d: 30.0,
ty: Twips::new(27)
}
),
(
Matrix {
a: 6.0,
c: 5.0,
tx: Twips::new(4),
b: 3.0,
d: 2.0,
ty: Twips::new(1)
},
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 26.0,
c: 37.0,
tx: Twips::new(52),
b: 11.0,
d: 16.0,
ty: Twips::new(22)
}
),
(
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 1.0,
c: 2.0,
tx: Twips::new(3),
b: 4.0,
d: 5.0,
ty: Twips::new(6)
},
Matrix {
a: 9.0,
c: 12.0,
tx: Twips::new(18),
b: 24.0,
d: 33.0,
ty: Twips::new(48)
}
)
);
// Twips multiplied by the identity/default matrix should be unchanged
test_multiply_twips!(
multiply_twips_identity_matrix,
(
Matrix::default(),
(Twips::zero(), Twips::zero()),
(Twips::zero(), Twips::zero())
),
(
Matrix::default(),
(Twips::zero(), Twips::new(10)),
(Twips::zero(), Twips::new(10))
),
(
Matrix::default(),
(Twips::new(10), Twips::zero()),
(Twips::new(10), Twips::zero())
),
(
Matrix::default(),
(Twips::new(-251), Twips::new(152)),
(Twips::new(-251), Twips::new(152))
)
);
// multiply by translate matrices; values should be shifted
test_multiply_twips!(
multiply_twips_translate,
(
Matrix {
a: 1.0,
c: 0.0,
tx: Twips::new(10),
b: 0.0,
d: 1.0,
ty: Twips::new(5)
},
(Twips::zero(), Twips::zero()),
(Twips::new(10), Twips::new(5))
),
(
Matrix {
a: 1.0,
c: 0.0,
tx: Twips::new(-200),
b: 0.0,
d: 1.0,
ty: Twips::zero()
},
(Twips::new(50), Twips::new(20)),
(Twips::new(-150), Twips::new(20))
)
);
// multiply by scalar matrices; values should be scaled up/down
test_multiply_twips!(
multiply_twips_scale,
(
Matrix {
a: 3.0,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: 3.0,
ty: Twips::zero()
},
(Twips::zero(), Twips::zero()),
(Twips::zero(), Twips::zero())
),
(
Matrix {
a: 3.0,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: 3.0,
ty: Twips::zero()
},
(Twips::new(10), Twips::new(10)),
(Twips::new(30), Twips::new(30))
),
(
Matrix {
a: 0.6,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: 0.2,
ty: Twips::zero()
},
(Twips::new(5), Twips::new(10)),
(Twips::new(3), Twips::new(2))
),
(
Matrix {
a: 0.5,
c: 0.0,
tx: Twips::zero(),
b: 0.0,
d: 0.5,
ty: Twips::zero()
},
(Twips::new(5), Twips::new(5)),
(Twips::new(2), Twips::new(2))
)
);
// multiply by rotation matrices; values should be rotated around origin
test_multiply_twips!(
multiply_twips_rotation,
(
Matrix {
a: 0.0,
c: -1.0,
tx: Twips::zero(),
b: 1.0,
d: 0.0,
ty: Twips::zero()
},
(Twips::new(10), Twips::zero()),
(Twips::zero(), Twips::new(10))
),
(
Matrix {
a: 0.0,
c: -1.0,
tx: Twips::zero(),
b: 1.0,
d: 0.0,
ty: Twips::zero()
},
(Twips::zero(), Twips::new(10)),
(Twips::new(-10), Twips::zero())
),
(
Matrix {
a: 0.0,
c: 1.0,
tx: Twips::zero(),
b: -1.0,
d: 0.0,
ty: Twips::zero()
},
(Twips::new(10), Twips::new(10)),
(Twips::new(10), Twips::new(-10))
),
(
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::zero(),
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::zero()
},
(Twips::new(100), Twips::zero()),
(Twips::new(71), Twips::new(-71))
),
(
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::zero(),
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::zero()
},
(Twips::new(100), Twips::new(100)),
(Twips::new(141), Twips::zero())
)
);
// Testing transformation matrices that have more than 1 translation applied
test_multiply_twips!(
multiply_twips_complex,
(
// result of scaling by 3 * rotation by 45 degrees
Matrix {
a: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
c: 3.0 * f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::zero(),
b: 3.0 * -f32::sin(std::f32::consts::FRAC_PI_4),
d: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::zero()
},
(Twips::new(100), Twips::new(100)),
(Twips::new(424), Twips::zero())
),
(
// result of translating by (-5, 5) * rotation by 45 degrees
Matrix {
a: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
c: 3.0 * f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::new(-5),
b: 3.0 * -f32::sin(std::f32::consts::FRAC_PI_4),
d: 3.0 * f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new(5)
},
(Twips::new(100), Twips::new(100)),
(Twips::new(419), Twips::new(5))
),
(
// result of rotation by 45 degrees * translating by (-5, 5)
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::new(-5),
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new(5)
},
(Twips::new(100), Twips::new(100)),
(Twips::new(136), Twips::new(5))
),
(
// result of translating by (-5, 5) * rotation by 45 degrees
Matrix {
a: f32::cos(std::f32::consts::FRAC_PI_4),
c: f32::sin(std::f32::consts::FRAC_PI_4),
tx: Twips::zero(),
b: -f32::sin(std::f32::consts::FRAC_PI_4),
d: f32::cos(std::f32::consts::FRAC_PI_4),
ty: Twips::new((10.0 * f32::sin(std::f32::consts::FRAC_PI_4)) as i32)
},
(Twips::new(105), Twips::new(95)),
(Twips::new(141), Twips::zero())
)
);
}
/// Implements the IEEE-754 "Round to nearest, ties to even" rounding rule.
/// (e.g., both 1.5 and 2.5 will round to 2).
/// This is the rounding method used by Flash for the above transforms.
/// Although this is easy to do on most architectures, Rust provides no standard
/// way to round in this manner (`f32::round` always rounds away from zero).
/// For more info and the below code snippet, see: https://github.com/rust-lang/rust/issues/55107
/// This also clamps out-of-range values and NaN to `i32::MIN`.
/// TODO: Investigate using SSE/wasm intrinsics for this.
fn round_to_i32(f: f32) -> i32 {
if f.is_finite() {
let a = f.abs();
if f < 2_147_483_648.0_f32 {
let k = 1.0 / f32::EPSILON;
let out = if a < k { ((a + k) - k).copysign(f) } else { f };
out as i32
} else {
// Out-of-range clamps to MIN.
i32::MIN
}
} else {
// NaN/Infinity goes to 0.
0
*self = *self * rhs;
}
}

View File

@ -182,8 +182,8 @@ impl<W: Write> BitWriter<W> {
}
#[inline]
fn write_fbits(&mut self, num_bits: u32, n: f32) -> io::Result<()> {
self.write_sbits(num_bits, (n * 65536f32) as i32)
fn write_fbits(&mut self, num_bits: u32, n: Fixed16) -> io::Result<()> {
self.write_sbits(num_bits, n.get())
}
#[inline]
@ -458,7 +458,7 @@ impl<W: Write> Writer<W> {
fn write_matrix(&mut self, m: &Matrix) -> Result<()> {
let mut bits = self.bits();
// Scale
let has_scale = m.a != 1f32 || m.d != 1f32;
let has_scale = m.a != Fixed16::ONE || m.d != Fixed16::ONE;
bits.write_bit(has_scale)?;
if has_scale {
let num_bits = max(count_fbits(m.a), count_fbits(m.d));
@ -467,7 +467,7 @@ impl<W: Write> Writer<W> {
bits.write_fbits(num_bits, m.d)?;
}
// Rotate/Skew
let has_rotate_skew = m.b != 0f32 || m.c != 0f32;
let has_rotate_skew = m.b != Fixed16::ZERO || m.c != Fixed16::ZERO;
bits.write_bit(has_rotate_skew)?;
if has_rotate_skew {
let num_bits = max(count_fbits(m.b), count_fbits(m.c));
@ -1721,7 +1721,7 @@ impl<W: Write> Writer<W> {
if let Some(ref matrix) = place_object.matrix {
writer.write_matrix(matrix)?;
} else {
writer.write_matrix(&Matrix::identity())?;
writer.write_matrix(&Matrix::IDENTITY)?;
}
if let Some(ref color_transform) = place_object.color_transform {
writer.write_color_transform_no_alpha(color_transform)?;
@ -2533,8 +2533,8 @@ fn count_sbits_twips(n: Twips) -> u32 {
}
}
fn count_fbits(n: f32) -> u32 {
count_sbits((n * 65536f32) as i32)
fn count_fbits(n: Fixed16) -> u32 {
count_sbits(n.get() as i32)
}
#[cfg(test)]
@ -2677,13 +2677,13 @@ mod tests {
#[test]
fn write_fbits() {
let num_bits = 18;
let nums = [1f32, -1f32];
let nums = [1.0, -1.0];
let mut buf = Vec::new();
{
let mut writer = Writer::new(&mut buf, 1);
let mut bits = writer.bits();
for n in &nums {
bits.write_fbits(num_bits, *n).unwrap();
bits.write_fbits(num_bits, Fixed16::from_f32(*n)).unwrap();
}
}
assert_eq!(
@ -2811,7 +2811,7 @@ mod tests {
buf
}
let m = Matrix::identity();
let m = Matrix::IDENTITY;
assert_eq!(write_to_buf(&m), [0]);
}