core: First pass of drawing API (merge #611)

This commit is contained in:
Mike Welsh 2020-05-22 08:54:55 -07:00 committed by GitHub
commit 97db518fb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1602 additions and 258 deletions

1
Cargo.lock generated
View File

@ -2172,6 +2172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "swf"
version = "0.1.2"
dependencies = [
"approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"enumset 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -17,6 +17,7 @@ pub(crate) mod display_object;
mod function;
mod key;
mod math;
mod matrix;
pub(crate) mod mouse;
pub(crate) mod movie_clip;
mod movie_clip_loader;
@ -127,6 +128,7 @@ pub struct SystemPrototypes<'gc> {
pub string: Object<'gc>,
pub number: Object<'gc>,
pub boolean: Object<'gc>,
pub matrix: Object<'gc>,
}
unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
@ -144,6 +146,7 @@ unsafe impl<'gc> gc_arena::Collect for SystemPrototypes<'gc> {
self.string.trace(cc);
self.number.trace(cc);
self.boolean.trace(cc);
self.matrix.trace(cc);
}
}
@ -183,6 +186,7 @@ pub fn create_globals<'gc>(
let number_proto: Object<'gc> = number::create_proto(gc_context, object_proto, function_proto);
let boolean_proto: Object<'gc> =
boolean::create_proto(gc_context, object_proto, function_proto);
let matrix_proto: Object<'gc> = matrix::create_proto(gc_context, object_proto, function_proto);
//TODO: These need to be constructors and should also set `.prototype` on each one
let object = object::create_object_object(gc_context, object_proto, function_proto);
@ -253,9 +257,17 @@ pub fn create_globals<'gc>(
let boolean =
boolean::create_boolean_object(gc_context, Some(boolean_proto), Some(function_proto));
let flash = ScriptObject::object(gc_context, Some(object_proto));
let geom = ScriptObject::object(gc_context, Some(object_proto));
let matrix = matrix::create_matrix_object(gc_context, Some(matrix_proto), Some(function_proto));
flash.define_value(gc_context, "geom", geom.into(), EnumSet::empty());
geom.define_value(gc_context, "Matrix", matrix.into(), EnumSet::empty());
let listeners = SystemListeners::new(gc_context, Some(array_proto));
let mut globals = ScriptObject::bare_object(gc_context);
globals.define_value(gc_context, "flash", flash.into(), EnumSet::empty());
globals.define_value(gc_context, "Array", array.into(), EnumSet::empty());
globals.define_value(gc_context, "Button", button.into(), EnumSet::empty());
globals.define_value(gc_context, "Color", color.into(), EnumSet::empty());
@ -381,6 +393,7 @@ pub fn create_globals<'gc>(
string: string_proto,
number: number_proto,
boolean: boolean_proto,
matrix: matrix_proto,
},
globals.into(),
listeners,

View File

@ -0,0 +1,498 @@
//! flash.geom.Matrix
use crate::avm1::function::{Executable, FunctionObject};
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, Value};
use crate::context::UpdateContext;
use enumset::EnumSet;
use gc_arena::MutationContext;
use swf::{Matrix, Twips};
pub fn value_to_matrix<'gc>(
value: Value<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Matrix, Error> {
object_to_matrix(value.as_object()?, avm, context)
}
pub fn gradient_object_to_matrix<'gc>(
object: Object<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Matrix, Error> {
if object
.get("matrixType", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?
== "box"
{
let width = object
.get("w", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?;
let height = object
.get("h", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?;
let rotation = object
.get("r", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?;
let tx = object
.get("x", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?;
let ty = object
.get("y", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?;
Ok(Matrix::create_gradient_box(
width as f32,
height as f32,
rotation as f32,
Twips::from_pixels(tx),
Twips::from_pixels(ty),
))
} else {
// TODO: You can apparently pass a 3x3 matrix here. Did anybody actually? How does it work?
object_to_matrix(object, avm, context)
}
}
pub fn object_to_matrix<'gc>(
object: Object<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Matrix, Error> {
let a = object
.get("a", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)? as f32;
let b = object
.get("b", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)? as f32;
let c = object
.get("c", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)? as f32;
let d = object
.get("d", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)? as f32;
let tx = Twips::from_pixels(
object
.get("tx", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?,
);
let ty = Twips::from_pixels(
object
.get("ty", avm, context)?
.resolve(avm, context)?
.as_number(avm, context)?,
);
Ok(Matrix { a, b, c, d, tx, ty })
}
// We'll need this soon!
#[allow(dead_code)]
pub fn matrix_to_object<'gc>(
matrix: Matrix,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<Object<'gc>, Error> {
let proto = context.system_prototypes.matrix;
let args = [
matrix.a.into(),
matrix.b.into(),
matrix.c.into(),
matrix.d.into(),
matrix.tx.to_pixels().into(),
matrix.ty.to_pixels().into(),
];
let object = proto.new(avm, context, proto, &args)?;
let _ = constructor(avm, context, object, &args)?;
Ok(object)
}
pub fn apply_matrix_to_object<'gc>(
matrix: Matrix,
object: Object<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
object.set("a", matrix.a.into(), avm, context)?;
object.set("b", matrix.b.into(), avm, context)?;
object.set("c", matrix.c.into(), avm, context)?;
object.set("d", matrix.d.into(), avm, context)?;
object.set("tx", matrix.tx.to_pixels().into(), avm, context)?;
object.set("ty", matrix.ty.to_pixels().into(), avm, context)?;
Ok(())
}
fn constructor<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if args.is_empty() {
apply_matrix_to_object(Matrix::identity(), this, avm, context)?;
} else {
if let Some(a) = args.get(0) {
this.set("a", a.clone(), avm, context)?;
}
if let Some(b) = args.get(1) {
this.set("b", b.clone(), avm, context)?;
}
if let Some(c) = args.get(2) {
this.set("c", c.clone(), avm, context)?;
}
if let Some(d) = args.get(3) {
this.set("d", d.clone(), avm, context)?;
}
if let Some(tx) = args.get(4) {
this.set("tx", tx.clone(), avm, context)?;
}
if let Some(ty) = args.get(5) {
this.set("ty", ty.clone(), avm, context)?;
}
}
Ok(Value::Undefined.into())
}
fn identity<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
apply_matrix_to_object(Matrix::identity(), this, avm, context)?;
Ok(Value::Undefined.into())
}
fn clone<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let proto = context.system_prototypes.matrix;
let args = [
this.get("a", avm, context)?.resolve(avm, context)?,
this.get("b", avm, context)?.resolve(avm, context)?,
this.get("c", avm, context)?.resolve(avm, context)?,
this.get("d", avm, context)?.resolve(avm, context)?,
this.get("tx", avm, context)?.resolve(avm, context)?,
this.get("ty", avm, context)?.resolve(avm, context)?,
];
let cloned = proto.new(avm, context, proto, &args)?;
let _ = constructor(avm, context, cloned, &args)?;
Ok(cloned.into())
}
fn scale<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let scale_x = args
.get(0)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let scale_y = args
.get(1)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let mut matrix = Matrix::scale(scale_x as f32, scale_y as f32);
matrix *= object_to_matrix(this, avm, context)?;
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn rotate<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let angle = args
.get(0)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let mut matrix = Matrix::rotate(angle as f32);
matrix *= object_to_matrix(this, avm, context)?;
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn translate<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let translate_x = args
.get(0)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let translate_y = args
.get(1)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let mut matrix = Matrix::translate(
Twips::from_pixels(translate_x),
Twips::from_pixels(translate_y),
);
matrix *= object_to_matrix(this, avm, context)?;
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn concat<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let mut matrix = object_to_matrix(this, avm, context)?;
let other = value_to_matrix(
args.get(0).unwrap_or(&Value::Undefined).clone(),
avm,
context,
)?;
matrix = other * matrix;
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn invert<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let mut matrix = object_to_matrix(this, avm, context)?;
matrix.invert();
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn create_box<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let scale_x = args
.get(0)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let scale_y = args
.get(1)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
// [NA] Docs say rotation is optional and defaults to 0, but that's wrong?
let rotation = args
.get(2)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let translate_x = if let Some(value) = args.get(3) {
value.as_number(avm, context)?
} else {
0.0
};
let translate_y = if let Some(value) = args.get(4) {
value.as_number(avm, context)?
} else {
0.0
};
let matrix = Matrix::create_box(
scale_x as f32,
scale_y as f32,
rotation as f32,
Twips::from_pixels(translate_x),
Twips::from_pixels(translate_y),
);
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn create_gradient_box<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let width = args
.get(0)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let height = args
.get(1)
.unwrap_or(&Value::Undefined)
.as_number(avm, context)?;
let rotation = if let Some(value) = args.get(2) {
value.as_number(avm, context)?
} else {
0.0
};
let translate_x = if let Some(value) = args.get(3) {
value.as_number(avm, context)?
} else {
0.0
};
let translate_y = if let Some(value) = args.get(4) {
value.as_number(avm, context)?
} else {
0.0
};
let matrix = Matrix::create_gradient_box(
width as f32,
height as f32,
rotation as f32,
Twips::from_pixels(translate_x),
Twips::from_pixels(translate_y),
);
apply_matrix_to_object(matrix, this, avm, context)?;
Ok(Value::Undefined.into())
}
fn to_string<'gc>(
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
let a = this
.get("a", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
let b = this
.get("b", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
let c = this
.get("c", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
let d = this
.get("d", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
let tx = this
.get("tx", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
let ty = this
.get("ty", avm, context)?
.resolve(avm, context)?
.coerce_to_string(avm, context)?;
Ok(format!("(a={}, b={}, c={}, d={}, tx={}, ty={})", a, b, c, d, tx, ty).into())
}
pub fn create_matrix_object<'gc>(
gc_context: MutationContext<'gc, '_>,
matrix_proto: Option<Object<'gc>>,
fn_proto: Option<Object<'gc>>,
) -> Object<'gc> {
FunctionObject::function(
gc_context,
Executable::Native(constructor),
fn_proto,
matrix_proto,
)
}
pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>,
fn_proto: Object<'gc>,
) -> Object<'gc> {
let mut object = ScriptObject::object(gc_context, Some(proto));
object.force_set_function(
"toString",
to_string,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"identity",
identity,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function("clone", clone, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function("scale", scale, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function(
"rotate",
rotate,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"translate",
translate,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"concat",
concat,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"invert",
invert,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"createBox",
create_box,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.force_set_function(
"createGradientBox",
create_gradient_box,
gc_context,
EnumSet::empty(),
Some(fn_proto),
);
object.into()
}

View File

@ -1,15 +1,20 @@
//! MovieClip prototype
use crate::avm1::globals::display_object::{self, AVM_DEPTH_BIAS, AVM_MAX_DEPTH};
use crate::avm1::globals::matrix::gradient_object_to_matrix;
use crate::avm1::property::Attribute::*;
use crate::avm1::return_value::ReturnValue;
use crate::avm1::{Avm1, Error, Object, ScriptObject, TObject, UpdateContext, Value};
use crate::backend::navigator::NavigationMethod;
use crate::display_object::{DisplayObject, EditText, MovieClip, TDisplayObject};
use crate::prelude::*;
use crate::shape_utils::DrawCommand;
use crate::tag_utils::SwfSlice;
use gc_arena::MutationContext;
use swf::Twips;
use swf::{
FillStyle, Gradient, GradientInterpolation, GradientRecord, GradientSpread, LineCapStyle,
LineJoinStyle, LineStyle, Twips,
};
/// Implements `MovieClip`
pub fn constructor<'gc>(
@ -123,12 +128,293 @@ pub fn create_proto<'gc>(
"stopDrag" => stop_drag,
"swapDepths" => swap_depths,
"toString" => to_string,
"unloadMovie" => unload_movie
"unloadMovie" => unload_movie,
"beginFill" => begin_fill,
"beginGradientFill" => begin_gradient_fill,
"moveTo" => move_to,
"lineTo" => line_to,
"curveTo" => curve_to,
"endFill" => end_fill,
"lineStyle" => line_style,
"clear" => clear
);
object.into()
}
fn line_style<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(width) = args.get(0) {
let width = Twips::from_pixels(width.as_number(avm, context)?.min(255.0).max(0.0));
let color = if let Some(rgb) = args.get(1) {
let rgb = rgb.coerce_to_u32(avm, context)?;
let alpha = if let Some(alpha) = args.get(2) {
alpha.as_number(avm, context)?.min(100.0).max(0.0)
} else {
100.0
} as f32
/ 100.0
* 255.0;
Color::from_rgb(rgb, alpha as u8)
} else {
Color::from_rgb(0, 255)
};
let is_pixel_hinted = args
.get(3)
.map_or(false, |v| v.as_bool(avm.current_swf_version()));
let (allow_scale_x, allow_scale_y) = match args
.get(4)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
.as_deref()
{
Some("normal") => (true, true),
Some("vertical") => (true, false),
Some("horizontal") => (false, true),
_ => (false, false),
};
let cap_style = match args
.get(5)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
.as_deref()
{
Some("square") => LineCapStyle::Square,
Some("none") => LineCapStyle::None,
_ => LineCapStyle::Round,
};
let join_style = match args
.get(6)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
.as_deref()
{
Some("miter") => {
if let Some(limit) = args.get(7) {
LineJoinStyle::Miter(limit.as_number(avm, context)?.max(0.0).min(255.0) as f32)
} else {
LineJoinStyle::Miter(3.0)
}
}
Some("bevel") => LineJoinStyle::Bevel,
_ => LineJoinStyle::Round,
};
movie_clip.set_line_style(
context,
Some(LineStyle {
width,
color,
start_cap: cap_style,
end_cap: cap_style,
join_style,
fill_style: None,
allow_scale_x,
allow_scale_y,
is_pixel_hinted,
allow_close: false,
}),
);
} else {
movie_clip.set_line_style(context, None);
}
Ok(Value::Undefined.into())
}
fn begin_fill<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(rgb) = args.get(0) {
let rgb = rgb.coerce_to_u32(avm, context)?;
let alpha = if let Some(alpha) = args.get(1) {
alpha.as_number(avm, context)?.min(100.0).max(0.0)
} else {
100.0
} as f32
/ 100.0
* 255.0;
movie_clip.set_fill_style(
context,
Some(FillStyle::Color(Color::from_rgb(rgb, alpha as u8))),
);
} else {
movie_clip.set_fill_style(context, None);
}
Ok(Value::Undefined.into())
}
fn begin_gradient_fill<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let (Some(method), Some(colors), Some(alphas), Some(ratios), Some(matrix)) = (
args.get(0),
args.get(1),
args.get(2),
args.get(3),
args.get(4),
) {
let method = method.clone().coerce_to_string(avm, context)?;
let colors = colors.as_object()?.array();
let alphas = alphas.as_object()?.array();
let ratios = ratios.as_object()?.array();
let matrix_object = matrix.as_object()?;
if colors.len() != alphas.len() || colors.len() != ratios.len() {
log::warn!(
"beginGradientFill() received different sized arrays for colors, alphas and ratios"
);
return Ok(Value::Undefined.into());
}
let mut records = Vec::with_capacity(colors.len());
for i in 0..colors.len() {
let ratio = ratios[i].as_number(avm, context)?.min(255.0).max(0.0);
let rgb = colors[i].coerce_to_u32(avm, context)?;
let alpha = alphas[i].as_number(avm, context)?.min(100.0).max(0.0);
records.push(GradientRecord {
ratio: ratio as u8,
color: Color::from_rgb(rgb, (alpha / 100.0 * 255.0) as u8),
});
}
let matrix = gradient_object_to_matrix(matrix_object, avm, context)?;
let spread = match args
.get(5)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
.as_deref()
{
Some("reflect") => GradientSpread::Reflect,
Some("repeat") => GradientSpread::Repeat,
_ => GradientSpread::Pad,
};
let interpolation = match args
.get(6)
.and_then(|v| v.clone().coerce_to_string(avm, context).ok())
.as_deref()
{
Some("linearRGB") => GradientInterpolation::LinearRGB,
_ => GradientInterpolation::RGB,
};
let gradient = Gradient {
matrix,
spread,
interpolation,
records,
};
let style = match method.as_str() {
"linear" => FillStyle::LinearGradient(gradient),
"radial" => {
if let Some(focal_point) = args.get(7) {
FillStyle::FocalGradient {
gradient,
focal_point: focal_point.as_number(avm, context)? as f32,
}
} else {
FillStyle::RadialGradient(gradient)
}
}
other => {
log::warn!("beginGradientFill() received invalid fill type {:?}", other);
return Ok(Value::Undefined.into());
}
};
movie_clip.set_fill_style(context, Some(style));
} else {
movie_clip.set_fill_style(context, None);
}
Ok(Value::Undefined.into())
}
fn move_to<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let (Some(x), Some(y)) = (args.get(0), args.get(1)) {
let x = x.as_number(avm, context)?;
let y = y.as_number(avm, context)?;
movie_clip.draw_command(
context,
DrawCommand::MoveTo {
x: Twips::from_pixels(x),
y: Twips::from_pixels(y),
},
);
}
Ok(Value::Undefined.into())
}
fn line_to<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let (Some(x), Some(y)) = (args.get(0), args.get(1)) {
let x = x.as_number(avm, context)?;
let y = y.as_number(avm, context)?;
movie_clip.draw_command(
context,
DrawCommand::LineTo {
x: Twips::from_pixels(x),
y: Twips::from_pixels(y),
},
);
}
Ok(Value::Undefined.into())
}
fn curve_to<'gc>(
movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let (Some(x1), Some(y1), Some(x2), Some(y2)) =
(args.get(0), args.get(1), args.get(2), args.get(3))
{
let x1 = x1.as_number(avm, context)?;
let y1 = y1.as_number(avm, context)?;
let x2 = x2.as_number(avm, context)?;
let y2 = y2.as_number(avm, context)?;
movie_clip.draw_command(
context,
DrawCommand::CurveTo {
x1: Twips::from_pixels(x1),
y1: Twips::from_pixels(y1),
x2: Twips::from_pixels(x2),
y2: Twips::from_pixels(y2),
},
);
}
Ok(Value::Undefined.into())
}
fn end_fill<'gc>(
movie_clip: MovieClip<'gc>,
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
movie_clip.set_fill_style(context, None);
Ok(Value::Undefined.into())
}
fn clear<'gc>(
movie_clip: MovieClip<'gc>,
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
movie_clip.clear(context);
Ok(Value::Undefined.into())
}
fn attach_movie<'gc>(
mut movie_clip: MovieClip<'gc>,
avm: &mut Avm1<'gc>,

View File

@ -7,6 +7,7 @@ pub use swf;
pub trait RenderBackend: Downcast {
fn set_viewport_dimensions(&mut self, width: u32, height: u32);
fn register_shape(&mut self, shape: DistilledShape) -> ShapeHandle;
fn replace_shape(&mut self, shape: DistilledShape, handle: ShapeHandle);
fn register_glyph_shape(&mut self, shape: &swf::Glyph) -> ShapeHandle;
fn register_bitmap_jpeg(
&mut self,
@ -83,6 +84,7 @@ impl RenderBackend for NullRenderer {
fn register_shape(&mut self, _shape: DistilledShape) -> ShapeHandle {
ShapeHandle(0)
}
fn replace_shape(&mut self, _shape: DistilledShape, _handle: ShapeHandle) {}
fn register_glyph_shape(&mut self, _shape: &swf::Glyph) -> ShapeHandle {
ShapeHandle(0)
}

View File

@ -1,7 +1,7 @@
use crate::matrix::Matrix;
use swf::Matrix;
use swf::Twips;
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct BoundingBox {
pub x_min: Twips,
pub y_min: Twips,
@ -43,6 +43,29 @@ impl BoundingBox {
}
}
pub fn encompass(&mut self, x: Twips, y: Twips) {
if self.valid {
if x < self.x_min {
self.x_min = x;
}
if x > self.x_max {
self.x_max = x;
}
if y < self.y_min {
self.y_min = y;
}
if y > self.y_max {
self.y_max = y;
}
} else {
self.x_min = x;
self.x_max = x;
self.y_min = y;
self.y_max = y;
self.valid = true;
}
}
pub fn union(&mut self, other: &BoundingBox) {
use std::cmp::{max, min};
if self.valid && other.valid {
@ -97,3 +120,15 @@ impl From<swf::Rectangle> for BoundingBox {
}
}
}
impl From<&swf::Rectangle> for BoundingBox {
fn from(rect: &swf::Rectangle) -> Self {
Self {
x_min: rect.x_min,
y_min: rect.y_min,
x_max: rect.x_max,
y_max: rect.y_max,
valid: true,
}
}
}

View File

@ -767,7 +767,7 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>>
// PlaceObject tags only apply if this onject has not been dynamically moved by AS code.
if !self.transformed_by_script() {
if let Some(matrix) = &place_object.matrix {
self.set_matrix(gc_context, &matrix.clone().into());
self.set_matrix(gc_context, &matrix);
}
if let Some(color_transform) = &place_object.color_transform {
self.set_color_transform(gc_context, &color_transform.clone().into());
@ -918,19 +918,19 @@ macro_rules! impl_display_object {
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<crate::matrix::Matrix> {
fn matrix(&self) -> std::cell::Ref<swf::Matrix> {
std::cell::Ref::map(self.0.read(), |o| o.$field.matrix())
}
fn matrix_mut(
&mut self,
context: gc_arena::MutationContext<'gc, '_>,
) -> std::cell::RefMut<crate::matrix::Matrix> {
) -> std::cell::RefMut<swf::Matrix> {
std::cell::RefMut::map(self.0.write(context), |o| o.$field.matrix_mut(context))
}
fn set_matrix(
&mut self,
context: gc_arena::MutationContext<'gc, '_>,
matrix: &crate::matrix::Matrix,
matrix: &swf::Matrix,
) {
self.0.write(context).$field.set_matrix(context, matrix)
}

View File

@ -247,7 +247,7 @@ impl<'gc> ButtonData<'gc> {
.instantiate_by_id(record.id, context.gc_context)
{
child.set_parent(context.gc_context, Some(self_display_object));
child.set_matrix(context.gc_context, &record.matrix.clone().into());
child.set_matrix(context.gc_context, &record.matrix);
child.set_color_transform(
context.gc_context,
&record.color_transform.clone().into(),
@ -280,7 +280,7 @@ impl<'gc> ButtonData<'gc> {
{
Ok(mut child) => {
{
child.set_matrix(context.gc_context, &record.matrix.clone().into());
child.set_matrix(context.gc_context, &record.matrix);
child.set_parent(context.gc_context, Some(self_display_object));
child.set_depth(context.gc_context, record.depth.into());
child.post_instantiation(avm, context, child, None);

View File

@ -161,7 +161,7 @@ impl MorphShapeStatic {
.collect();
FillStyle::LinearGradient(Gradient {
matrix: start.matrix.clone(),
matrix: start.matrix,
spread: start.spread,
interpolation: start.interpolation,
records,

View File

@ -1,14 +1,17 @@
//! `MovieClip` display object and support code.
use crate::avm1::{Avm1, Object, StageObject, TObject, Value};
use crate::backend::audio::AudioStreamHandle;
use crate::character::Character;
use crate::context::{ActionType, RenderContext, UpdateContext};
use crate::display_object::{
Bitmap, Button, DisplayObjectBase, EditText, Graphic, MorphShapeStatic, TDisplayObject, Text,
};
use crate::drawing::Drawing;
use crate::events::{ButtonKeyCode, ClipEvent};
use crate::font::Font;
use crate::prelude::*;
use crate::shape_utils::DrawCommand;
use crate::tag_utils::{self, DecodeResult, SwfMovie, SwfSlice, SwfStream};
use enumset::{EnumSet, EnumSetType};
use gc_arena::{Collect, Gc, GcCell, MutationContext};
@ -18,6 +21,7 @@ use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::sync::Arc;
use swf::read::SwfRead;
use swf::{FillStyle, LineStyle};
type FrameNumber = u16;
@ -42,6 +46,7 @@ pub struct MovieClipData<'gc> {
clip_actions: SmallVec<[ClipAction; 2]>,
flags: EnumSet<MovieClipFlags>,
avm1_constructor: Option<Object<'gc>>,
drawing: Drawing,
}
impl<'gc> MovieClip<'gc> {
@ -60,6 +65,7 @@ impl<'gc> MovieClip<'gc> {
clip_actions: SmallVec::new(),
flags: EnumSet::empty(),
avm1_constructor: None,
drawing: Drawing::new(),
},
))
}
@ -92,6 +98,7 @@ impl<'gc> MovieClip<'gc> {
clip_actions: SmallVec::new(),
flags: MovieClipFlags::Playing.into(),
avm1_constructor: None,
drawing: Drawing::new(),
},
))
}
@ -550,6 +557,34 @@ impl<'gc> MovieClip<'gc> {
actions.into_iter()
}
pub fn set_fill_style(
self,
context: &mut UpdateContext<'_, 'gc, '_>,
style: Option<FillStyle>,
) {
let mut mc = self.0.write(context.gc_context);
mc.drawing.set_fill_style(style);
}
pub fn clear(self, context: &mut UpdateContext<'_, 'gc, '_>) {
let mut mc = self.0.write(context.gc_context);
mc.drawing.clear();
}
pub fn set_line_style(
self,
context: &mut UpdateContext<'_, 'gc, '_>,
style: Option<LineStyle>,
) {
let mut mc = self.0.write(context.gc_context);
mc.drawing.set_line_style(style);
}
pub fn draw_command(self, context: &mut UpdateContext<'_, 'gc, '_>, command: DrawCommand) {
let mut mc = self.0.write(context.gc_context);
mc.drawing.draw_command(command);
}
}
impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
@ -592,12 +627,12 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
context.transform_stack.push(&*self.transform());
crate::display_object::render_children(context, &self.0.read().children);
self.0.read().drawing.render(context);
context.transform_stack.pop();
}
fn self_bounds(&self) -> BoundingBox {
// No inherent bounds; contains child DisplayObjects.
BoundingBox::default()
self.0.read().drawing.self_bounds()
}
fn hit_test(&self, point: (Twips, Twips)) -> bool {

View File

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

223
core/src/drawing.rs Normal file
View File

@ -0,0 +1,223 @@
use crate::backend::render::ShapeHandle;
use crate::bounding_box::BoundingBox;
use crate::context::RenderContext;
use crate::shape_utils::{DistilledShape, DrawCommand, DrawPath};
use std::cell::Cell;
use swf::{FillStyle, LineStyle, Twips};
#[derive(Clone, Debug)]
pub struct Drawing {
render_handle: Cell<Option<ShapeHandle>>,
shape_bounds: BoundingBox,
edge_bounds: BoundingBox,
dirty: Cell<bool>,
fills: Vec<(FillStyle, Vec<DrawCommand>)>,
lines: Vec<(LineStyle, Vec<DrawCommand>)>,
current_fill: Option<(FillStyle, Vec<DrawCommand>)>,
current_line: Option<(LineStyle, Vec<DrawCommand>)>,
cursor: (Twips, Twips),
}
impl Drawing {
pub fn new() -> Self {
Self {
render_handle: Cell::new(None),
shape_bounds: BoundingBox::default(),
edge_bounds: BoundingBox::default(),
dirty: Cell::new(false),
fills: Vec::new(),
lines: Vec::new(),
current_fill: None,
current_line: None,
cursor: (Twips::zero(), Twips::zero()),
}
}
pub fn set_fill_style(&mut self, style: Option<FillStyle>) {
// TODO: If current_fill is not closed, we should close it and also close current_line
if let Some(existing) = self.current_fill.take() {
self.fills.push(existing);
}
if let Some(style) = style {
self.current_fill = Some((
style,
vec![DrawCommand::MoveTo {
x: self.cursor.0,
y: self.cursor.1,
}],
));
}
self.dirty.set(true);
}
pub fn clear(&mut self) {
self.current_fill = None;
self.current_line = None;
self.fills.clear();
self.lines.clear();
self.edge_bounds = BoundingBox::default();
self.shape_bounds = BoundingBox::default();
self.dirty.set(true);
self.cursor = (Twips::zero(), Twips::zero());
}
pub fn set_line_style(&mut self, style: Option<LineStyle>) {
if let Some(existing) = self.current_line.take() {
self.lines.push(existing);
}
if let Some(style) = style {
self.current_line = Some((
style,
vec![DrawCommand::MoveTo {
x: self.cursor.0,
y: self.cursor.1,
}],
));
}
self.dirty.set(true);
}
pub fn draw_command(&mut self, command: DrawCommand) {
let mut include_last = false;
let stroke_width = if let Some((style, _)) = &self.current_line {
style.width
} else {
Twips::zero()
};
match command {
DrawCommand::MoveTo { .. } => {}
DrawCommand::LineTo { .. } => {
stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, &command, Twips::zero());
include_last = true;
}
DrawCommand::CurveTo { .. } => {
stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, &command, Twips::zero());
include_last = true;
}
}
self.cursor = command.end_point();
if let Some((_, commands)) = &mut self.current_line {
commands.push(command.clone());
}
if let Some((_, commands)) = &mut self.current_fill {
commands.push(command);
}
if include_last {
if let Some(command) = self
.current_fill
.as_ref()
.and_then(|(_, commands)| commands.last())
{
stretch_bounding_box(&mut self.shape_bounds, command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, command, Twips::zero());
}
if let Some(command) = self
.current_line
.as_ref()
.and_then(|(_, commands)| commands.last())
{
stretch_bounding_box(&mut self.shape_bounds, command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, command, Twips::zero());
}
}
self.dirty.set(true);
}
pub fn render(&self, context: &mut RenderContext) {
if self.dirty.get() {
self.dirty.set(false);
let mut paths = Vec::new();
for (style, commands) in &self.fills {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
// TODO: If the current_fill is not closed, we should automatically close current_line
if let Some((style, commands)) = &self.current_fill {
paths.push(DrawPath::Fill {
style,
commands: commands.to_owned(),
})
}
for (style, commands) in &self.lines {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
if let Some((style, commands)) = &self.current_line {
paths.push(DrawPath::Stroke {
style,
commands: commands.to_owned(),
is_closed: false, // TODO: Determine this
})
}
let shape = DistilledShape {
paths,
shape_bounds: self.shape_bounds.clone(),
edge_bounds: self.edge_bounds.clone(),
id: 0,
};
if let Some(handle) = self.render_handle.get() {
context.renderer.replace_shape(shape, handle);
} else {
self.render_handle
.set(Some(context.renderer.register_shape(shape)));
}
}
if let Some(handle) = self.render_handle.get() {
context
.renderer
.render_shape(handle, context.transform_stack.transform());
}
}
pub fn self_bounds(&self) -> BoundingBox {
self.shape_bounds.clone()
}
}
fn stretch_bounding_box(
bounding_box: &mut BoundingBox,
command: &DrawCommand,
stroke_width: Twips,
) {
let radius = stroke_width / 2;
match *command {
DrawCommand::MoveTo { x, y } => {
bounding_box.encompass(x - radius, y - radius);
bounding_box.encompass(x + radius, y + radius);
}
DrawCommand::LineTo { x, y } => {
bounding_box.encompass(x - radius, y - radius);
bounding_box.encompass(x + radius, y + radius);
}
DrawCommand::CurveTo { x1, y1, x2, y2 } => {
bounding_box.encompass(x1 - radius, y1 - radius);
bounding_box.encompass(x1 + radius, y1 + radius);
bounding_box.encompass(x2 - radius, y2 - radius);
bounding_box.encompass(x2 + radius, y2 + radius);
}
}
}

View File

@ -14,11 +14,11 @@ mod bounding_box;
mod character;
pub mod color_transform;
mod context;
mod drawing;
pub mod events;
mod font;
mod library;
mod loader;
pub mod matrix;
mod player;
mod prelude;
mod property_map;

View File

@ -2,8 +2,8 @@ pub use crate::bounding_box::BoundingBox;
pub use crate::color_transform::ColorTransform;
pub use crate::display_object::{DisplayObject, TDisplayObject};
pub use crate::impl_display_object;
pub use crate::matrix::Matrix;
pub use log::{error, info, trace, warn};
pub use swf::Matrix;
pub use swf::{CharacterId, Color, Twips};
/// A depth for a Flash display object in AVM1.

View File

@ -1,6 +1,7 @@
use crate::bounding_box::BoundingBox;
use fnv::FnvHashMap;
use std::num::NonZeroU32;
use swf::{CharacterId, FillStyle, LineStyle, Rectangle, 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 {
@ -79,8 +80,8 @@ pub enum DrawPath<'a> {
#[derive(Debug, PartialEq, Clone)]
pub struct DistilledShape<'a> {
pub paths: Vec<DrawPath<'a>>,
pub shape_bounds: Rectangle,
pub edge_bounds: Rectangle,
pub shape_bounds: BoundingBox,
pub edge_bounds: BoundingBox,
pub id: CharacterId,
}
@ -88,8 +89,8 @@ impl<'a> From<&'a swf::Shape> for DistilledShape<'a> {
fn from(shape: &'a Shape) -> Self {
Self {
paths: ShapeConverter::from_shape(shape).into_commands(),
shape_bounds: shape.shape_bounds.clone(),
edge_bounds: shape.edge_bounds.clone(),
shape_bounds: (&shape.shape_bounds).into(),
edge_bounds: (&shape.edge_bounds).into(),
id: shape.id,
}
}
@ -115,6 +116,16 @@ pub enum DrawCommand {
},
}
impl DrawCommand {
pub fn end_point(&self) -> (Twips, Twips) {
match self {
DrawCommand::MoveTo { x, y } => (*x, *y),
DrawCommand::LineTo { x, y } => (*x, *y),
DrawCommand::CurveTo { x2, y2, .. } => (*x2, *y2),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Point {
x: Twips,

View File

@ -72,6 +72,7 @@ swf_tests! {
(execution_order3, "avm1/execution_order3", 5),
(single_frame, "avm1/single_frame", 2),
(looping, "avm1/looping", 6),
(matrix, "avm1/matrix", 1),
(goto_advance1, "avm1/goto_advance1", 2),
(goto_advance2, "avm1/goto_advance2", 2),
(goto_both_ways1, "avm1/goto_both_ways1", 2),

View File

@ -0,0 +1,151 @@
// new Matrix()
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// new Matrix(1)
(a=1, b=undefined, c=undefined, d=undefined, tx=undefined, ty=undefined)
// new Matrix(1, 2)
(a=1, b=2, c=undefined, d=undefined, tx=undefined, ty=undefined)
// new Matrix(1, 2, 3)
(a=1, b=2, c=3, d=undefined, tx=undefined, ty=undefined)
// new Matrix(1, 2, 3, {})
(a=1, b=2, c=3, d=[object Object], tx=undefined, ty=undefined)
// new Matrix(1, 2, 3, 4, 5)
(a=1, b=2, c=3, d=4, tx=5, ty=undefined)
// new Matrix(1, 2, 3, 4, 5, 6)
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// new Matrix(1, 2, 3, 4, 5, 6, 7)
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// new Matrix(1, 2, 3, 4, 5, 6) .identity()
(a=1, b=0, c=0, d=1, tx=0, ty=0)
/// Clones
// matrix
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// cloned
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// matrix === cloned
false
// matrix
(a=1, b=2, c=[object Object], d=4, tx=5, ty=6)
// cloned
(a=1, b=2, c=[object Object], d=4, tx=5, ty=6)
// matrix === cloned
false
/// scale
// matrix
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix.scale(3, 5)
(a=3, b=0, c=0, d=5, tx=0, ty=0)
// matrix
(a=2, b=0, c=0, d=2, tx=100, ty=100)
// matrix.scale(7, 11)
(a=14, b=0, c=0, d=22, tx=700, ty=1100)
// matrix
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// matrix.scale()
(a=13, b=34, c=39, d=68, tx=65, ty=102)
/// rotate
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix.rotate(0)
(a=1, b=0, c=0, d=1, tx=0, ty=0)
/// rotate
// matrix
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// matrix.rotate(0)
(a=1, b=2, c=3, d=4, tx=5, ty=6)
// matrix
(a=1, b=2, c=3, d=4, tx=5, ty=6)
/// translate
// matrix
// matrix.translate(3, 5)
(a=1, b=0, c=0, d=1, tx=3, ty=5)
// matrix
// matrix.translate(7, 11)
(a=2, b=0, c=0, d=2, tx=107, ty=111)
/// concat
// matrix
(a=11, b=13, c=17, d=19, tx=23, ty=29)
// matrix
(a=33, b=65, c=51, d=95, tx=69, ty=145)
// matrix
(a=33, b=65, c=51, d=95, tx=76, ty=154)
/// invert
// matrix
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix.invert()
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix
(a=2, b=3, c=5, d=7, tx=9, ty=11)
// matrix.invert()
(a=-7, b=3, c=5, d=-2, tx=8, ty=-5)
/// createBox
// matrix = nw Matrix();
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix.createBox(2, 3)
(a=NaN, b=NaN, c=NaN, d=NaN, tx=0, ty=0)
// matrix.createBox(2, 3, 0)
(a=2, b=0, c=0, d=3, tx=0, ty=0)
/// createGradientBox
// matrix = nw Matrix();
(a=1, b=0, c=0, d=1, tx=0, ty=0)
// matrix.createGradientBox(200, 300)
(a=0.1220703125, b=0, c=0, d=0.18310546875, tx=100, ty=150)
// matrix.createGradientBox(200, 300, 0)
(a=0.1220703125, b=0, c=0, d=0.18310546875, tx=100, ty=150)

Binary file not shown.

Binary file not shown.

View File

@ -455,6 +455,26 @@ impl RenderBackend for WebCanvasRenderBackend {
handle
}
fn replace_shape(&mut self, shape: DistilledShape, handle: ShapeHandle) {
let mut bitmaps = HashMap::new();
for (id, handle) in &self.id_to_bitmap {
let bitmap_data = &self.bitmaps[handle.0];
bitmaps.insert(
*id,
(&bitmap_data.data[..], bitmap_data.width, bitmap_data.height),
);
}
let data = swf_shape_to_canvas_commands(
&shape,
&bitmaps,
self.pixelated_property_value,
&self.context,
)
.unwrap_or_else(|| swf_shape_to_svg(shape, &bitmaps, self.pixelated_property_value));
self.shapes[handle.0] = data;
}
fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle {
// Per SWF19 p.164, the FontBoundsTable can contain empty bounds for every glyph (reserved).
// SWF19 says this is true through SWFv7, but it seems like it might be generally true?
@ -723,8 +743,8 @@ fn swf_shape_to_svg(
pixelated_property_value: &str,
) -> ShapeData {
use fnv::FnvHashSet;
use ruffle_core::matrix::Matrix;
use ruffle_core::shape_utils::DrawPath;
use ruffle_core::swf::Matrix;
use svg::node::element::{
path::Data, Definitions, Image, LinearGradient, Path as SvgPath, Pattern, RadialGradient,
Stop,
@ -782,7 +802,7 @@ fn swf_shape_to_svg(
format!("rgba({},{},{},{})", r, g, b, f32::from(*a) / 255.0)
}
FillStyle::LinearGradient(gradient) => {
let matrix: Matrix = Matrix::from(gradient.matrix.clone());
let matrix: Matrix = gradient.matrix;
let shift = Matrix {
a: 32768.0 / width,
d: 32768.0 / height,
@ -829,7 +849,7 @@ fn swf_shape_to_svg(
fill_id
}
FillStyle::RadialGradient(gradient) => {
let matrix = Matrix::from(gradient.matrix.clone());
let matrix = gradient.matrix;
let shift = Matrix {
a: 32768.0,
d: 32768.0,
@ -880,7 +900,7 @@ fn swf_shape_to_svg(
gradient,
focal_point,
} => {
let matrix = Matrix::from(gradient.matrix.clone());
let matrix = gradient.matrix;
let shift = Matrix {
a: 32768.0,
d: 32768.0,
@ -970,7 +990,7 @@ fn swf_shape_to_svg(
defs = defs.add(bitmap_pattern);
bitmap_defs.insert(*id);
}
let a = Matrix::from(matrix.clone());
let a = *matrix;
let bitmap_matrix = a;
let svg_pattern = Pattern::new()
@ -1140,7 +1160,6 @@ fn swf_shape_to_canvas_commands(
_pixelated_property_value: &str,
context: &CanvasRenderingContext2d,
) -> Option<ShapeData> {
use ruffle_core::matrix::Matrix;
use ruffle_core::shape_utils::DrawPath;
use swf::{FillStyle, LineCapStyle, LineJoinStyle};
@ -1220,7 +1239,7 @@ fn swf_shape_to_canvas_commands(
// when cached? (Issue #412)
image.set_src(*bitmap_data);
let a = Matrix::from(matrix.clone());
let a = *matrix;
let matrix = matrix_factory.create_svg_matrix();

View File

@ -99,7 +99,7 @@ impl ShapeTessellator {
ratios,
colors,
num_colors: gradient.records.len() as u32,
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
matrix: swf_to_gl_matrix(gradient.matrix),
repeat_mode: gradient.spread,
focal_point: 0.0,
};
@ -141,7 +141,7 @@ impl ShapeTessellator {
ratios,
colors,
num_colors: gradient.records.len() as u32,
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
matrix: swf_to_gl_matrix(gradient.matrix),
repeat_mode: gradient.spread,
focal_point: 0.0,
};
@ -186,7 +186,7 @@ impl ShapeTessellator {
ratios,
colors,
num_colors: gradient.records.len() as u32,
matrix: swf_to_gl_matrix(gradient.matrix.clone()),
matrix: swf_to_gl_matrix(gradient.matrix),
repeat_mode: gradient.spread,
focal_point: *focal_point,
};
@ -220,11 +220,7 @@ impl ShapeTessellator {
(get_bitmap_dimensions)(*id).unwrap_or((1, 1));
let bitmap = Bitmap {
matrix: swf_bitmap_to_gl_matrix(
matrix.clone(),
bitmap_width,
bitmap_height,
),
matrix: swf_bitmap_to_gl_matrix(*matrix, bitmap_width, bitmap_height),
id: *id,
is_smoothed: *is_smoothed,
is_repeating: *is_repeating,
@ -342,15 +338,15 @@ pub struct Bitmap {
#[allow(clippy::many_single_char_names)]
fn swf_to_gl_matrix(m: swf::Matrix) -> [[f32; 3]; 3] {
let tx = m.translate_x.get() as f32;
let ty = m.translate_y.get() as f32;
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
let mut a = m.scale_y / det;
let mut b = -m.rotate_skew_1 / det;
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
let mut d = -m.rotate_skew_0 / det;
let mut e = m.scale_x / det;
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
let tx = m.tx.get() as f32;
let ty = m.ty.get() as f32;
let det = m.a * m.d - m.c * m.b;
let mut a = m.d / det;
let mut b = -m.c / det;
let mut c = -(tx * m.d - m.c * ty) / det;
let mut d = -m.b / det;
let mut e = m.a / det;
let mut f = (tx * m.b - m.a * ty) / det;
a *= 20.0 / 32768.0;
b *= 20.0 / 32768.0;
@ -369,15 +365,15 @@ fn swf_bitmap_to_gl_matrix(m: swf::Matrix, bitmap_width: u32, bitmap_height: u32
let bitmap_width = bitmap_width as f32;
let bitmap_height = bitmap_height as f32;
let tx = m.translate_x.get() as f32;
let ty = m.translate_y.get() as f32;
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
let mut a = m.scale_y / det;
let mut b = -m.rotate_skew_1 / det;
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
let mut d = -m.rotate_skew_0 / det;
let mut e = m.scale_x / det;
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
let tx = m.tx.get() as f32;
let ty = m.ty.get() as f32;
let det = m.a * m.d - m.c * m.b;
let mut a = m.d / det;
let mut b = -m.c / det;
let mut c = -(tx * m.d - m.c * ty) / det;
let mut d = -m.b / det;
let mut e = m.a / det;
let mut f = (tx * m.b - m.a * ty) / det;
a *= 20.0 / bitmap_width;
b *= 20.0 / bitmap_width;

View File

@ -423,11 +423,9 @@ impl WebGlRenderBackend {
Ok(())
}
fn register_shape_internal(&mut self, shape: DistilledShape) -> ShapeHandle {
fn register_shape_internal(&mut self, shape: DistilledShape) -> Mesh {
use ruffle_render_common_tess::DrawType as TessDrawType;
let handle = ShapeHandle(self.meshes.len());
let textures = &self.textures;
let lyon_mesh = self.shape_tessellator.tessellate_shape(shape, |id| {
textures
@ -573,9 +571,7 @@ impl WebGlRenderBackend {
}
}
self.meshes.push(Mesh { draws });
handle
Mesh { draws }
}
fn build_matrices(&mut self) {
@ -718,7 +714,15 @@ impl RenderBackend for WebGlRenderBackend {
}
fn register_shape(&mut self, shape: DistilledShape) -> ShapeHandle {
self.register_shape_internal(shape)
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal(shape);
self.meshes.push(mesh);
handle
}
fn replace_shape(&mut self, shape: DistilledShape, handle: ShapeHandle) {
let mesh = self.register_shape_internal(shape);
self.meshes[handle.0] = mesh;
}
fn register_glyph_shape(&mut self, glyph: &swf::Glyph) -> ShapeHandle {
@ -741,7 +745,10 @@ impl RenderBackend for WebGlRenderBackend {
},
shape: glyph.shape_records.clone(),
};
self.register_shape_internal((&shape).into())
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal((&shape).into());
self.meshes.push(mesh);
handle
}
fn register_bitmap_jpeg(
@ -904,7 +911,7 @@ impl RenderBackend for WebGlRenderBackend {
}
// Scale the quad to the bitmap's dimensions.
use ruffle_core::matrix::Matrix;
use ruffle_core::swf::Matrix;
let scale_transform = Transform {
matrix: transform.matrix
* Matrix {

View File

@ -226,9 +226,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
}
#[allow(clippy::cognitive_complexity)]
fn register_shape_internal(&mut self, shape: DistilledShape) -> ShapeHandle {
let handle = ShapeHandle(self.meshes.len());
fn register_shape_internal(&mut self, shape: DistilledShape) -> Mesh {
use lyon::tessellation::{FillOptions, StrokeOptions};
let transforms_label = create_debug_label!("Shape {} transforms ubo", shape.id);
@ -377,7 +375,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
repeat_mode: gradient_spread_mode_index(gradient.spread),
focal_point: 0.0,
};
let matrix = swf_to_gl_matrix(gradient.matrix.clone());
let matrix = swf_to_gl_matrix(gradient.matrix);
flush_draw(
shape.id,
@ -446,7 +444,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
repeat_mode: gradient_spread_mode_index(gradient.spread),
focal_point: 0.0,
};
let matrix = swf_to_gl_matrix(gradient.matrix.clone());
let matrix = swf_to_gl_matrix(gradient.matrix);
flush_draw(
shape.id,
@ -518,7 +516,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
repeat_mode: gradient_spread_mode_index(gradient.spread),
focal_point: *focal_point,
};
let matrix = swf_to_gl_matrix(gradient.matrix.clone());
let matrix = swf_to_gl_matrix(gradient.matrix);
flush_draw(
shape.id,
@ -585,7 +583,7 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
shape.id,
IncompleteDrawType::Bitmap {
texture_transform: swf_bitmap_to_gl_matrix(
matrix.clone(),
*matrix,
texture.width,
texture.height,
),
@ -671,15 +669,13 @@ impl<T: RenderTarget> WgpuRenderBackend<T> {
&self.pipelines,
);
self.meshes.push(Mesh {
Mesh {
draws,
transforms: transforms_ubo,
colors_buffer: colors_ubo,
colors_last: ColorTransform::default(),
shape_id: shape.id,
});
handle
}
}
fn register_bitmap(
@ -928,7 +924,15 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
}
fn register_shape(&mut self, shape: DistilledShape) -> ShapeHandle {
self.register_shape_internal(shape)
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal(shape);
self.meshes.push(mesh);
handle
}
fn replace_shape(&mut self, shape: DistilledShape, handle: ShapeHandle) {
let mesh = self.register_shape_internal(shape);
self.meshes[handle.0] = mesh;
}
fn register_glyph_shape(&mut self, glyph: &Glyph) -> ShapeHandle {
@ -951,7 +955,10 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
},
shape: glyph.shape_records.clone(),
};
self.register_shape_internal((&shape).into())
let handle = ShapeHandle(self.meshes.len());
let mesh = self.register_shape_internal((&shape).into());
self.meshes.push(mesh);
handle
}
fn register_bitmap_jpeg(
@ -1050,7 +1057,7 @@ impl<T: RenderTarget + 'static> RenderBackend for WgpuRenderBackend<T> {
return;
};
use ruffle_core::matrix::Matrix;
use ruffle_core::swf::Matrix;
let transform = Transform {
matrix: transform.matrix
* Matrix {

View File

@ -56,15 +56,15 @@ pub fn ruffle_path_to_lyon_path(commands: Vec<DrawCommand>, is_closed: bool) ->
#[allow(clippy::many_single_char_names)]
pub fn swf_to_gl_matrix(m: swf::Matrix) -> [[f32; 4]; 4] {
let tx = m.translate_x.get() as f32;
let ty = m.translate_y.get() as f32;
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
let mut a = m.scale_y / det;
let mut b = -m.rotate_skew_1 / det;
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
let mut d = -m.rotate_skew_0 / det;
let mut e = m.scale_x / det;
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
let tx = m.tx.get() as f32;
let ty = m.ty.get() as f32;
let det = m.a * m.d - m.c * m.b;
let mut a = m.d / det;
let mut b = -m.c / det;
let mut c = -(tx * m.d - m.c * ty) / det;
let mut d = -m.b / det;
let mut e = m.a / det;
let mut f = (tx * m.b - m.a * ty) / det;
a *= 20.0 / 32768.0;
b *= 20.0 / 32768.0;
@ -92,15 +92,15 @@ pub fn swf_bitmap_to_gl_matrix(
let bitmap_width = bitmap_width as f32;
let bitmap_height = bitmap_height as f32;
let tx = m.translate_x.get() as f32;
let ty = m.translate_y.get() as f32;
let det = m.scale_x * m.scale_y - m.rotate_skew_1 * m.rotate_skew_0;
let mut a = m.scale_y / det;
let mut b = -m.rotate_skew_1 / det;
let mut c = -(tx * m.scale_y - m.rotate_skew_1 * ty) / det;
let mut d = -m.rotate_skew_0 / det;
let mut e = m.scale_x / det;
let mut f = (tx * m.rotate_skew_0 - m.scale_x * ty) / det;
let tx = m.tx.get() as f32;
let ty = m.ty.get() as f32;
let det = m.a * m.d - m.c * m.b;
let mut a = m.d / det;
let mut b = -m.c / det;
let mut c = -(tx * m.d - m.c * ty) / det;
let mut d = -m.b / det;
let mut e = m.a / det;
let mut f = (tx * m.b - m.a * ty) / det;
a *= 20.0 / bitmap_width;
b *= 20.0 / bitmap_width;

View File

@ -19,6 +19,9 @@ log = "0.4"
flate2 = {version = "1.0", optional = true}
xz2 = {version = "0.1.6", optional = true}
[dev-dependencies]
approx = "0.3.2"
[features]
default = ["libflate"]
lzma = ["xz2"]

View File

@ -830,23 +830,23 @@ impl<R: Read> Reader<R> {
fn read_matrix(&mut self) -> Result<Matrix> {
self.byte_align();
let mut m = Matrix::new();
let mut m = Matrix::identity();
// Scale
if self.read_bit()? {
let num_bits = self.read_ubits(5)? as usize;
m.scale_x = self.read_fbits(num_bits)?;
m.scale_y = self.read_fbits(num_bits)?;
m.a = self.read_fbits(num_bits)?;
m.d = self.read_fbits(num_bits)?;
}
// Rotate/Skew
if self.read_bit()? {
let num_bits = self.read_ubits(5)? as usize;
m.rotate_skew_0 = self.read_fbits(num_bits)?;
m.rotate_skew_1 = self.read_fbits(num_bits)?;
m.b = self.read_fbits(num_bits)?;
m.c = self.read_fbits(num_bits)?;
}
// Translate (always present)
let num_bits = self.read_ubits(5)? as usize;
m.translate_x = self.read_sbits_twips(num_bits)?;
m.translate_y = self.read_sbits_twips(num_bits)?;
m.tx = self.read_sbits_twips(num_bits)?;
m.ty = self.read_sbits_twips(num_bits)?;
Ok(m)
}
@ -3092,12 +3092,12 @@ pub mod tests {
assert_eq!(
matrix,
Matrix {
translate_x: Twips::from_pixels(0.0),
translate_y: Twips::from_pixels(0.0),
scale_x: 1f32,
scale_y: 1f32,
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
tx: Twips::from_pixels(0.0),
ty: Twips::from_pixels(0.0),
a: 1f32,
d: 1f32,
b: 0f32,
c: 0f32,
}
);
}
@ -3173,7 +3173,7 @@ pub mod tests {
let fill_style = FillStyle::Bitmap {
id: 20,
matrix: Matrix::new(),
matrix: Matrix::identity(),
is_smoothed: false,
is_repeating: true,
};
@ -3182,8 +3182,8 @@ pub mod tests {
fill_style
);
let mut matrix = Matrix::new();
matrix.translate_x = Twips::from_pixels(1.0);
let mut matrix = Matrix::identity();
matrix.tx = Twips::from_pixels(1.0);
let fill_style = FillStyle::Bitmap {
id: 33,
matrix,

View File

@ -203,7 +203,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
.into_iter()
.collect(),
depth: 1,
matrix: Matrix::new(),
matrix: Matrix::identity(),
color_transform: ColorTransform::new(),
filters: vec![],
blend_mode: BlendMode::Normal,
@ -214,7 +214,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
.into_iter()
.collect(),
depth: 1,
matrix: Matrix::new(),
matrix: Matrix::identity(),
color_transform: ColorTransform::new(),
filters: vec![],
blend_mode: BlendMode::Normal,
@ -242,7 +242,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
.into_iter()
.collect(),
depth: 1,
matrix: Matrix::new(),
matrix: Matrix::identity(),
color_transform: ColorTransform {
r_multiply: 1f32,
g_multiply: 1f32,
@ -266,7 +266,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
.into_iter()
.collect(),
depth: 1,
matrix: Matrix::new(),
matrix: Matrix::identity(),
color_transform: ColorTransform {
r_multiply: 0f32,
g_multiply: 1f32,
@ -737,12 +737,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
fill_styles: vec![FillStyle::LinearGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(40.0),
translate_y: Twips::from_pixels(40.0),
scale_x: 0.024429321,
scale_y: 0.024429321,
rotate_skew_0: 0.024429321,
rotate_skew_1: -0.024429321,
tx: Twips::from_pixels(40.0),
ty: Twips::from_pixels(40.0),
a: 0.024429321,
d: 0.024429321,
b: 0.024429321,
c: -0.024429321,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -840,12 +840,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
fill_styles: vec![FillStyle::LinearGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(48.4),
translate_y: Twips::from_pixels(34.65),
scale_x: 0.0058898926,
scale_y: 0.030914307,
rotate_skew_0: 0.0,
rotate_skew_1: 0.0,
tx: Twips::from_pixels(48.4),
ty: Twips::from_pixels(34.65),
a: 0.0058898926,
d: 0.030914307,
b: 0.0,
c: 0.0,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -958,12 +958,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
fill_styles: vec![FillStyle::FocalGradient {
gradient: Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(116.05),
translate_y: Twips::from_pixels(135.05),
scale_x: 0.11468506,
scale_y: 0.18927002,
rotate_skew_0: 0.0,
rotate_skew_1: 0.0,
tx: Twips::from_pixels(116.05),
ty: Twips::from_pixels(135.05),
a: 0.11468506,
d: 0.18927002,
b: 0.0,
c: 0.0,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -1081,12 +1081,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
fill_styles: vec![FillStyle::FocalGradient {
gradient: Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(164.0),
translate_y: Twips::from_pixels(150.05),
scale_x: 0.036087036,
scale_y: 0.041992188,
rotate_skew_0: 0.1347351,
rotate_skew_1: -0.15675354,
tx: Twips::from_pixels(164.0),
ty: Twips::from_pixels(150.05),
a: 0.036087036,
d: 0.041992188,
b: 0.1347351,
c: -0.15675354,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -1217,12 +1217,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
fill_styles: vec![FillStyle::RadialGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(100.00),
translate_y: Twips::from_pixels(100.00),
scale_x: 0.1725769,
scale_y: 0.1725769,
rotate_skew_0: 0.0,
rotate_skew_1: 0.0,
tx: Twips::from_pixels(100.00),
ty: Twips::from_pixels(100.00),
a: 0.1725769,
d: 0.1725769,
b: 0.0,
c: 0.0,
},
spread: GradientSpread::Reflect,
interpolation: GradientInterpolation::LinearRGB,
@ -1305,12 +1305,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
},
fill_styles: vec![FillStyle::RadialGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(100.00),
translate_y: Twips::from_pixels(100.00),
scale_x: 0.000015258789,
scale_y: 0.000015258789,
rotate_skew_0: 0.084503174,
rotate_skew_1: -0.084503174,
tx: Twips::from_pixels(100.00),
ty: Twips::from_pixels(100.00),
a: 0.000015258789,
d: 0.000015258789,
b: 0.084503174,
c: -0.084503174,
},
spread: GradientSpread::Reflect,
interpolation: GradientInterpolation::LinearRGB,
@ -1514,12 +1514,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
styles: ShapeStyles {
fill_styles: vec![FillStyle::RadialGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(24.95),
translate_y: Twips::from_pixels(24.95),
scale_x: 0.030731201f32,
scale_y: 0.030731201f32,
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
tx: Twips::from_pixels(24.95),
ty: Twips::from_pixels(24.95),
a: 0.030731201f32,
d: 0.030731201f32,
b: 0f32,
c: 0f32,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -1637,12 +1637,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
FillStyle::FocalGradient {
gradient: Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(49.55),
translate_y: Twips::from_pixels(46.55),
scale_x: 0.06199646f32,
scale_y: 0.06199646f32,
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
tx: Twips::from_pixels(49.55),
ty: Twips::from_pixels(46.55),
a: 0.06199646f32,
d: 0.06199646f32,
b: 0f32,
c: 0f32,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::LinearRGB,
@ -1701,12 +1701,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
join_style: LineJoinStyle::Round,
fill_style: Some(FillStyle::LinearGradient(Gradient {
matrix: Matrix {
translate_x: Twips::from_pixels(50.0),
translate_y: Twips::from_pixels(50.0),
scale_x: 0.07324219f32,
scale_y: 0.07324219f32,
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
tx: Twips::from_pixels(50.0),
ty: Twips::from_pixels(50.0),
a: 0.07324219f32,
d: 0.07324219f32,
b: 0f32,
c: 0f32,
},
spread: GradientSpread::Pad,
interpolation: GradientInterpolation::RGB,
@ -1888,7 +1888,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
y_min: Twips::from_pixels(4.1),
y_max: Twips::from_pixels(18.45),
},
matrix: Matrix::new(),
matrix: Matrix::identity(),
records: vec![TextRecord {
font_id: Some(1),
color: Some(Color {
@ -2090,7 +2090,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(1),
depth: 1,
matrix: Some(Matrix::new()),
matrix: Some(Matrix::identity()),
color_transform: None,
ratio: None,
name: None,
@ -2113,7 +2113,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix::new()),
matrix: Some(Matrix::identity()),
color_transform: None,
ratio: None,
name: None,
@ -2143,7 +2143,7 @@ pub fn tag_tests() -> Vec<TagTestData> {
version: 2,
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix::new()),
matrix: Some(Matrix::identity()),
color_transform: None,
ratio: None,
name: None,
@ -2186,12 +2186,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
action: PlaceObjectAction::Place(1),
depth: 1,
matrix: Some(Matrix {
translate_x: Twips::from_pixels(0.0),
translate_y: Twips::from_pixels(0.0),
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
scale_x: 1.0f32,
scale_y: 1.0f32,
tx: Twips::from_pixels(0.0),
ty: Twips::from_pixels(0.0),
b: 0f32,
c: 0f32,
a: 1.0f32,
d: 1.0f32,
}),
color_transform: None,
ratio: None,
@ -2216,12 +2216,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix {
translate_x: Twips::from_pixels(10.0),
translate_y: Twips::from_pixels(10.0),
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
scale_x: 2.0f32,
scale_y: 2.0f32,
tx: Twips::from_pixels(10.0),
ty: Twips::from_pixels(10.0),
b: 0f32,
c: 0f32,
a: 2.0f32,
d: 2.0f32,
}),
color_transform: Some(ColorTransform {
a_multiply: 1.0f32,
@ -2352,12 +2352,12 @@ pub fn tag_tests() -> Vec<TagTestData> {
action: PlaceObjectAction::Place(2),
depth: 1,
matrix: Some(Matrix {
translate_x: Twips::from_pixels(10.0),
translate_y: Twips::from_pixels(10.0),
rotate_skew_0: 0.0,
rotate_skew_1: 0.0,
scale_x: 1.0,
scale_y: 1.0,
tx: Twips::from_pixels(10.0),
ty: Twips::from_pixels(10.0),
b: 0.0,
c: 0.0,
a: 1.0,
d: 1.0,
}),
color_transform: None,
ratio: None,

View File

@ -6,6 +6,10 @@
use enumset::{EnumSet, EnumSetType};
use std::collections::HashSet;
mod matrix;
pub use matrix::Matrix;
/// A complete header and tags in the SWF file.
/// This is returned by the `swf::read_swf` convenience method.
#[derive(Debug, PartialEq)]
@ -68,6 +72,11 @@ impl Twips {
Self(twips.into())
}
/// Creates a new `Twips` object set to the value of 0.
pub fn zero() -> Self {
Self(0)
}
/// Returns the number of twips.
pub fn get(self) -> i32 {
self.0
@ -206,35 +215,6 @@ impl Default for ColorTransform {
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Matrix {
pub translate_x: Twips,
pub translate_y: Twips,
pub scale_x: f32,
pub scale_y: f32,
pub rotate_skew_0: f32,
pub rotate_skew_1: f32,
}
impl Matrix {
pub fn new() -> Matrix {
Matrix {
translate_x: Default::default(),
translate_y: Default::default(),
scale_x: 1f32,
scale_y: 1f32,
rotate_skew_0: 0f32,
rotate_skew_1: 0f32,
}
}
}
impl Default for Matrix {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Language {
Unknown,

View File

@ -1,16 +1,115 @@
use swf::Twips;
use crate::Twips;
#[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 fn identity() -> Self {
Self {
a: 1.0,
c: 0.0,
tx: Twips::new(0),
b: 0.0,
d: 1.0,
ty: Twips::new(0),
}
}
pub fn scale(scale_x: f32, scale_y: f32) -> Self {
Self {
a: scale_x,
c: 0.0,
tx: Twips::new(0),
b: 0.0,
d: scale_y,
ty: Twips::new(0),
}
}
pub fn rotate(angle: f32) -> Self {
Self {
a: angle.cos(),
c: -angle.sin(),
tx: Twips::new(0),
b: angle.sin(),
d: angle.cos(),
ty: Twips::new(0),
}
}
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;
@ -33,19 +132,6 @@ impl Matrix {
}
}
impl From<swf::Matrix> for Matrix {
fn from(matrix: swf::Matrix) -> Matrix {
Matrix {
a: matrix.scale_x,
b: matrix.rotate_skew_0,
c: matrix.rotate_skew_1,
d: matrix.scale_y,
tx: matrix.translate_x,
ty: matrix.translate_y,
}
}
}
impl std::ops::Mul for Matrix {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
@ -77,14 +163,7 @@ impl std::ops::Mul<(Twips, Twips)> for Matrix {
impl std::default::Default for Matrix {
fn default() -> Matrix {
Matrix {
a: 1.0,
c: 0.0,
tx: Twips::new(0),
b: 0.0,
d: 1.0,
ty: Twips::new(0),
}
Matrix::identity()
}
}

View File

@ -475,31 +475,28 @@ impl<W: Write> Writer<W> {
fn write_matrix(&mut self, m: &Matrix) -> Result<()> {
self.flush_bits()?;
// Scale
let has_scale = m.scale_x != 1f32 || m.scale_y != 1f32;
let has_scale = m.a != 1f32 || m.d != 1f32;
self.write_bit(has_scale)?;
if has_scale {
let num_bits = max(count_fbits(m.scale_x), count_fbits(m.scale_y));
let num_bits = max(count_fbits(m.a), count_fbits(m.d));
self.write_ubits(5, num_bits.into())?;
self.write_fbits(num_bits, m.scale_x)?;
self.write_fbits(num_bits, m.scale_y)?;
self.write_fbits(num_bits, m.a)?;
self.write_fbits(num_bits, m.d)?;
}
// Rotate/Skew
let has_rotate_skew = m.rotate_skew_0 != 0f32 || m.rotate_skew_1 != 0f32;
let has_rotate_skew = m.b != 0f32 || m.c != 0f32;
self.write_bit(has_rotate_skew)?;
if has_rotate_skew {
let num_bits = max(count_fbits(m.rotate_skew_0), count_fbits(m.rotate_skew_1));
let num_bits = max(count_fbits(m.b), count_fbits(m.c));
self.write_ubits(5, num_bits.into())?;
self.write_fbits(num_bits, m.rotate_skew_0)?;
self.write_fbits(num_bits, m.rotate_skew_1)?;
self.write_fbits(num_bits, m.b)?;
self.write_fbits(num_bits, m.c)?;
}
// Translate (always written)
let num_bits = max(
count_sbits_twips(m.translate_x),
count_sbits_twips(m.translate_y),
);
let num_bits = max(count_sbits_twips(m.tx), count_sbits_twips(m.ty));
self.write_ubits(5, num_bits.into())?;
self.write_sbits_twips(num_bits, m.translate_x)?;
self.write_sbits_twips(num_bits, m.translate_y)?;
self.write_sbits_twips(num_bits, m.tx)?;
self.write_sbits_twips(num_bits, m.ty)?;
Ok(())
}
@ -1911,7 +1908,7 @@ impl<W: Write> Writer<W> {
if let Some(ref matrix) = place_object.matrix {
writer.write_matrix(matrix)?;
} else {
writer.write_matrix(&Matrix::new())?;
writer.write_matrix(&Matrix::identity())?;
}
if let Some(ref color_transform) = place_object.color_transform {
writer.write_color_transform_no_alpha(color_transform)?;
@ -3037,7 +3034,7 @@ mod tests {
buf
}
let m = Matrix::new();
let m = Matrix::identity();
assert_eq!(write_to_buf(&m), [0]);
}