From d23ea90459effe78612eda1b8dd4576e13cdb41e Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Mon, 7 Jun 2021 22:33:37 -0700 Subject: [PATCH] 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`. --- core/src/avm1/globals/matrix.rs | 7 +- core/src/avm1/globals/movie_clip.rs | 2 +- core/src/backend/render.rs | 2 +- core/src/bounding_box.rs | 2 +- core/src/display_object.rs | 14 +- core/src/display_object/avm1_button.rs | 4 +- core/src/display_object/avm2_button.rs | 2 +- core/src/display_object/morph_shape.rs | 12 +- core/src/display_object/text.rs | 2 +- core/src/drawing.rs | 2 +- core/src/lib.rs | 1 + core/src/matrix.rs | 807 ++++++++++++++++++++++++ core/src/prelude.rs | 2 +- core/src/shape_utils.rs | 4 +- render/canvas/src/lib.rs | 16 +- render/common_tess/src/lib.rs | 12 +- render/webgl/src/lib.rs | 4 +- render/wgpu/src/lib.rs | 4 +- swf/src/read.rs | 28 +- swf/src/test_data.rs | 116 ++-- swf/src/types/matrix.rs | 813 ++++--------------------- swf/src/write.rs | 20 +- 22 files changed, 1045 insertions(+), 831 deletions(-) create mode 100644 core/src/matrix.rs diff --git a/core/src/avm1/globals/matrix.rs b/core/src/avm1/globals/matrix.rs index bcf82ec4b..48d4e8f2a 100644 --- a/core/src/avm1/globals/matrix.rs +++ b/core/src/avm1/globals/matrix.rs @@ -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, 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, Error<'gc>> { - apply_matrix_to_object(Matrix::identity(), this, activation)?; + apply_matrix_to_object(Matrix::IDENTITY, this, activation)?; Ok(Value::Undefined) } diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 1a0113ab8..cb2a626cb 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -391,7 +391,7 @@ fn begin_gradient_fill<'gc>( }; let gradient = Gradient { - matrix, + matrix: matrix.into(), spread, interpolation, records, diff --git a/core/src/backend/render.rs b/core/src/backend/render.rs index 25b149c01..eb6183ae8 100644 --- a/core/src/backend/render.rs +++ b/core/src/backend/render.rs @@ -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); diff --git a/core/src/bounding_box.rs b/core/src/bounding_box.rs index 71e0805aa..771af57b2 100644 --- a/core/src/bounding_box.rs +++ b/core/src/bounding_box.rs @@ -1,5 +1,5 @@ +use crate::matrix::Matrix; use gc_arena::Collect; -use swf::Matrix; use swf::Twips; #[derive(Clone, Debug, PartialEq, Collect)] diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 6cfccd4ad..8b21e418a 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -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 { std::cell::Ref::map(self.0.read(), |o| o.$field.transform()) } - fn matrix(&self) -> std::cell::Ref { + fn matrix(&self) -> std::cell::Ref { std::cell::Ref::map(self.0.read(), |o| o.$field.matrix()) } fn matrix_mut( &self, context: gc_arena::MutationContext<'gc, '_>, - ) -> std::cell::RefMut { + ) -> std::cell::RefMut { std::cell::RefMut::map(self.0.write(context), |o| o.$field.matrix_mut()) } fn color_transform(&self) -> std::cell::Ref { @@ -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) } }; diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index 0ce55792a..fe0953a94 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -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())); diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index fef08ad25..2500141d6 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -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 { diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index 56c499ca4..3eda8ecb6 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -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), } diff --git a/core/src/display_object/text.rs b/core/src/display_object/text.rs index 03323bb7f..e498ad92d 100644 --- a/core/src/display_object/text.rs +++ b/core/src/display_object/text.rs @@ -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(), }, ), diff --git a/core/src/drawing.rs b/core/src/drawing.rs index c57cd81c5..e6c7a01cb 100644 --- a/core/src/drawing.rs +++ b/core/src/drawing.rs @@ -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) { diff --git a/core/src/lib.rs b/core/src/lib.rs index cc6c1de97..b691cc530 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -38,6 +38,7 @@ mod font; mod html; mod library; pub mod loader; +pub mod matrix; mod player; mod prelude; pub mod shape_utils; diff --git a/core/src/matrix.rs b/core/src/matrix.rs new file mode 100644 index 000000000..5fb09bfad --- /dev/null +++ b/core/src/matrix.rs @@ -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 = (::Epsilon, ::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 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 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 + } +} diff --git a/core/src/prelude.rs b/core/src/prelude.rs index a58409805..be8b1239e 100644 --- a/core/src/prelude.rs +++ b/core/src/prelude.rs @@ -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. diff --git a/core/src/shape_utils.rs b/core/src/shape_utils.rs index 86f90044e..fad4530e5 100644 --- a/core/src/shape_utils.rs +++ b/core/src/shape_utils.rs @@ -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 { diff --git a/render/canvas/src/lib.rs b/render/canvas/src/lib.rs index b2917da7e..99cdf6bde 100644 --- a/render/canvas/src/lib.rs +++ b/render/canvas/src/lib.rs @@ -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); diff --git a/render/common_tess/src/lib.rs b/render/common_tess/src/lib.rs index 280fd8754..4f3937f14 100644 --- a/render/common_tess/src/lib.rs +++ b/render/common_tess/src/lib.rs @@ -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, diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index 5eba41f25..705db3cdc 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -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], diff --git a/render/wgpu/src/lib.rs b/render/wgpu/src/lib.rs index c79d2a5c0..f7ecde21b 100644 --- a/render/wgpu/src/lib.rs +++ b/render/wgpu/src/lib.rs @@ -887,7 +887,7 @@ impl RenderBackend for WgpuRenderBackend { 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 RenderBackend for WgpuRenderBackend { } } - 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 { diff --git a/swf/src/read.rs b/swf/src/read.rs index 503b5e973..0c97a5765 100644 --- a/swf/src/read.rs +++ b/swf/src/read.rs @@ -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 { - self.read_sbits(num_bits).map(|n| (n as f32) / 65536f32) + fn read_fbits(&mut self, num_bits: u32) -> io::Result { + self.read_sbits(num_bits).map(Fixed16::from_bits) } #[inline] @@ -693,7 +692,7 @@ impl<'a> Reader<'a> { fn read_matrix(&mut self) -> Result { 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, diff --git a/swf/src/test_data.rs b/swf/src/test_data.rs index 8da7efa96..8cdc67511 100644 --- a/swf/src/test_data.rs +++ b/swf/src/test_data.rs @@ -201,7 +201,7 @@ pub fn tag_tests() -> Vec { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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() diff --git a/swf/src/types/matrix.rs b/swf/src/types/matrix.rs index e3c1d2614..c2ca773a7 100644 --- a/swf/src/types/matrix.rs +++ b/swf/src/types/matrix.rs @@ -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, - ty: Twips::zero(), - } - } + /// 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 = (::Epsilon, ::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; } } diff --git a/swf/src/write.rs b/swf/src/write.rs index a8ba7e3f9..35d94500a 100644 --- a/swf/src/write.rs +++ b/swf/src/write.rs @@ -182,8 +182,8 @@ impl BitWriter { } #[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 Writer { 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 Writer { 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 Writer { 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]); }