avm2: partially implement Graphics.drawTriangles and drawGraphicsData (#12559)

This commit is contained in:
SuchAFuriousDeath 2023-08-26 12:57:07 +02:00 committed by GitHub
parent d25fda7cc8
commit 40daa56f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 679 additions and 81 deletions

View File

@ -77,6 +77,7 @@ pub struct SystemClasses<'gc> {
pub graphicspath: ClassObject<'gc>,
pub graphicstrianglepath: ClassObject<'gc>,
pub graphicssolidfill: ClassObject<'gc>,
pub graphicsshaderfill: ClassObject<'gc>,
pub graphicsstroke: ClassObject<'gc>,
pub loader: ClassObject<'gc>,
pub loaderinfo: ClassObject<'gc>,
@ -203,6 +204,7 @@ impl<'gc> SystemClasses<'gc> {
graphicspath: object,
graphicstrianglepath: object,
graphicssolidfill: object,
graphicsshaderfill: object,
graphicsstroke: object,
loader: object,
loaderinfo: object,

View File

@ -97,8 +97,8 @@ pub fn begin_gradient_fill<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this.as_display_object() {
let gradient_type = args.get_string(activation, 0);
let gradient_type = parse_gradient_type(activation, gradient_type?)?;
let gradient_type = args.get_string(activation, 0)?;
let gradient_type = parse_gradient_type(activation, gradient_type)?;
let colors = args.get_object(activation, 1, "colors")?;
let alphas = args.get_object(activation, 2, "alphas")?;
let ratios = args.get_object(activation, 3, "ratios")?;
@ -865,8 +865,278 @@ pub fn draw_path<'gc>(
let commands = commands
.as_vector_storage()
.expect("commands is not a Vector");
let data = &*data.as_vector_storage().expect("data is not a Vector");
let data = data.as_vector_storage().expect("data is not a Vector");
process_commands(activation, &mut drawing, &commands, &data);
Ok(Value::Undefined)
}
/// Implements `Graphics.drawRoundRectComplex`
pub fn draw_round_rect_complex<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "drawRoundRectComplex");
Ok(Value::Undefined)
}
/// Implements `Graphics.drawTriangles`
pub fn draw_triangles<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this.as_display_object() {
if let Some(mut drawing) = this.as_drawing(activation.context.gc_context) {
let vertices = args.get_object(activation, 0, "vertices")?;
let indices = args.try_get_object(activation, 1);
let uvt_data = args.try_get_object(activation, 2);
let culling = {
let culling = args.get_string(activation, 3)?;
culling_to_triangle_culling(activation, culling)?
};
draw_triangles_internal(
activation,
&mut drawing,
&vertices,
indices.as_ref(),
uvt_data.as_ref(),
culling,
)?;
}
}
Ok(Value::Undefined)
}
fn draw_triangles_internal<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
vertices: &Object<'gc>,
indices: Option<&Object<'gc>>,
_uvt_data: Option<&Object<'gc>>,
culling: TriangleCulling,
) -> Result<(), Error<'gc>> {
let vertices = vertices
.as_vector_storage()
.expect("vertices is not a Vector");
if let Some(indices) = indices {
let indices = indices
.as_vector_storage()
.expect("indices is not a Vector");
fn read_point<'gc>(
vertices: &VectorStorage<'gc>,
index: usize,
activation: &mut Activation<'_, 'gc>,
) -> Result<Point<Twips>, Error<'gc>> {
let x = {
let x = vertices
.get(2 * index, activation)?
.coerce_to_number(activation)?;
Twips::from_pixels(x)
};
let y = {
let y = vertices
.get(2 * index + 1, activation)?
.coerce_to_number(activation)?;
Twips::from_pixels(y)
};
Ok(Point::new(x, y))
}
fn next_triangle<'gc>(
vertices: &VectorStorage<'gc>,
indices: &mut impl Iterator<Item = Value<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<Triangle>, Error<'gc>> {
match (indices.next(), indices.next(), indices.next()) {
(Some(i0), Some(i1), Some(i2)) => {
let i0 = i0.coerce_to_u32(activation)? as usize;
let i1 = i1.coerce_to_u32(activation)? as usize;
let i2 = i2.coerce_to_u32(activation)? as usize;
let p0 = read_point(vertices, i0, activation)?;
let p1 = read_point(vertices, i1, activation)?;
let p2 = read_point(vertices, i2, activation)?;
Ok(Some((p0, p1, p2)))
}
_ => Ok(None),
}
}
let indices = &mut indices.iter();
while let Some(triangle) = next_triangle(&vertices, indices, activation)? {
draw_triangle_internal(triangle, drawing, culling);
}
} else {
let mut vertices = vertices.iter();
fn read_point<'gc>(
vertices: &mut impl Iterator<Item = Value<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<Point<Twips>>, Error<'gc>> {
let x = {
let x = vertices.next();
let x = match x {
Some(x) => x.coerce_to_number(activation)?,
None => return Ok(None),
};
Twips::from_pixels(x)
};
let y = {
let y = vertices.next();
let y = match y {
Some(y) => y.coerce_to_number(activation)?,
None => return Ok(None),
};
Twips::from_pixels(y)
};
Ok(Some(Point::new(x, y)))
}
fn next_triangle<'gc>(
vertices: &mut impl Iterator<Item = Value<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<Triangle>, Error<'gc>> {
match (
read_point(vertices, activation)?,
read_point(vertices, activation)?,
read_point(vertices, activation)?,
) {
(Some(p0), Some(p1), Some(p2)) => Ok(Some((p0, p1, p2))),
_ => Ok(None),
}
}
while let Some(triangle) = next_triangle(&mut vertices, activation)? {
draw_triangle_internal(triangle, drawing, culling);
}
}
Ok(())
}
fn draw_triangle_internal((a, b, c): Triangle, drawing: &mut Drawing, culling: TriangleCulling) {
match culling {
TriangleCulling::None => {
drawing.draw_command(DrawCommand::MoveTo(a));
drawing.draw_command(DrawCommand::LineTo(b));
drawing.draw_command(DrawCommand::LineTo(c));
drawing.draw_command(DrawCommand::LineTo(a));
}
TriangleCulling::Positive => {
panic!("Positive culling not implemented")
}
TriangleCulling::Negative => {
panic!("Negative culling not implemented")
}
}
}
/// Implements `Graphics.drawGraphicsData`
pub fn draw_graphics_data<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(vector) = args
.get_object(activation, 0, "graphicsData")?
.as_vector_storage()
{
//assert_eq!(vector.value_type(), Some(activation.avm2().classes().igraphicsdata));
let this = this.as_display_object().expect("Bad this");
if let Some(mut drawing) = this.as_drawing(activation.context.gc_context) {
for elem in vector.iter() {
let obj = elem.coerce_to_object(activation)?;
handle_igraphics_data(activation, &mut drawing, &obj)?;
}
};
}
Ok(Value::Undefined)
}
/// Implements `Graphics.lineBitmapStyle`
pub fn line_bitmap_style<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this.as_display_object() {
let bitmap = args
.get_object(activation, 0, "bitmap")?
.as_bitmap_data()
.expect("Bitmap argument is ensured to be a BitmapData from actionscript");
let matrix = if let Some(matrix) = args.try_get_object(activation, 1) {
Matrix::from(object_to_matrix(matrix, activation)?)
} else {
// Users can explicitly pass in `null` to mean identity matrix
Matrix::IDENTITY
};
let is_repeating = args.get_bool(2);
let is_smoothed = args.get_bool(3);
let handle =
bitmap.bitmap_handle(activation.context.gc_context, activation.context.renderer);
let bitmap = ruffle_render::bitmap::BitmapInfo {
handle,
width: bitmap.width() as u16,
height: bitmap.height() as u16,
};
let scale_matrix = Matrix::scale(
Fixed16::from_f64(bitmap.width as f64),
Fixed16::from_f64(bitmap.height as f64),
);
if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
let id = draw.add_bitmap(bitmap);
draw.set_line_fill_style(FillStyle::Bitmap {
id,
matrix: matrix * scale_matrix,
is_smoothed,
is_repeating,
});
}
}
Ok(Value::Undefined)
}
/// Implements `Graphics.readGraphicsData`
pub fn read_graphics_data<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData");
let value_type = activation.avm2().classes().igraphicsdata;
let new_storage = VectorStorage::new(0, false, Some(value_type), activation);
Ok(VectorObject::from_vector(new_storage, activation)?.into())
}
fn process_commands<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
commands: &VectorStorage<'gc>,
data: &VectorStorage<'gc>,
) {
let mut data_index = 0;
for i in 0..commands.length() {
@ -942,95 +1212,421 @@ pub fn draw_path<'gc>(
drawing.draw_command(draw_command);
}
}
Ok(Value::Undefined)
}
/// Implements `Graphics.drawRoundRectComplex`
pub fn draw_round_rect_complex<'gc>(
fn handle_igraphics_data<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "drawRoundRectComplex");
Ok(Value::Undefined)
}
drawing: &mut Drawing,
obj: &Object<'gc>,
) -> Result<(), Error<'gc>> {
let class = obj.instance_of().expect("No class");
/// Implements `Graphics.drawTriangles`
pub fn draw_triangles<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "drawTriangles");
Ok(Value::Undefined)
}
if class == activation.avm2().classes().graphicsbitmapfill {
let style = handle_bitmap_fill(activation, drawing, obj)?;
drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicsendfill {
drawing.set_fill_style(None);
} else if class == activation.avm2().classes().graphicsgradientfill {
let style = handle_gradient_fill(activation, obj)?;
drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicspath {
let commands = obj
.get_public_property("commands", activation)?
.coerce_to_object(activation)?;
/// Implements `Graphics.drawGraphicsData`
pub fn draw_graphics_data<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "drawGraphicsData");
Ok(Value::Undefined)
}
let data = obj
.get_public_property("data", activation)?
.coerce_to_object(activation)?;
/// Implements `Graphics.lineBitmapStyle`
pub fn line_bitmap_style<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this.as_display_object() {
let bitmap = args
.get_object(activation, 0, "bitmap")?
.as_bitmap_data()
.expect("Bitmap argument is ensured to be a BitmapData from actionscript");
let matrix = if let Some(matrix) = args.try_get_object(activation, 1) {
Matrix::from(object_to_matrix(matrix, activation)?)
} else {
// Users can explicitly pass in `null` to mean identity matrix
Matrix::IDENTITY
};
let is_repeating = args.get_bool(2);
let is_smoothed = args.get_bool(3);
//TODO implement winding
let _winding = obj
.get_public_property("winding", activation)?
.coerce_to_string(activation)?;
let handle =
bitmap.bitmap_handle(activation.context.gc_context, activation.context.renderer);
let bitmap = ruffle_render::bitmap::BitmapInfo {
handle,
width: bitmap.width() as u16,
height: bitmap.height() as u16,
};
let scale_matrix = Matrix::scale(
Fixed16::from_f64(bitmap.width as f64),
Fixed16::from_f64(bitmap.height as f64),
process_commands(
activation,
drawing,
&commands
.as_vector_storage()
.expect("commands is not a Vector"),
&data.as_vector_storage().expect("data is not a Vector"),
);
} else if class == activation.avm2().classes().graphicssolidfill {
let style = handle_solid_fill(activation, obj)?;
drawing.set_fill_style(Some(style));
} else if class == activation.avm2().classes().graphicsshaderfill {
tracing::warn!("Graphics shader fill unimplemented {:?}", class);
drawing.set_fill_style(None);
} else if class == activation.avm2().classes().graphicsstroke {
let thickness = obj
.get_public_property("thickness", activation)?
.coerce_to_number(activation)?;
if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
let id = draw.add_bitmap(bitmap);
draw.set_line_fill_style(FillStyle::Bitmap {
id,
matrix: matrix * scale_matrix,
is_smoothed,
is_repeating,
});
if thickness.is_nan() {
drawing.set_line_style(None);
} else {
let caps = {
let caps = obj
.get_public_property("caps", activation)?
.coerce_to_string(activation);
caps_to_cap_style(caps.ok())
};
let fill = {
let fill = obj
.get_public_property("fill", activation)?
.coerce_to_object(activation)?;
handle_igraphics_fill(activation, drawing, &fill)?
};
let joints = obj
.get_public_property("joints", activation)?
.coerce_to_string(activation)
.ok();
let miter_limit = obj
.get_public_property("miterLimit", activation)?
.coerce_to_number(activation)?;
let pixel_hinting = obj
.get_public_property("pixelHinting", activation)?
.coerce_to_boolean();
let scale_mode = obj
.get_public_property("scaleMode", activation)?
.coerce_to_string(activation)?;
let width = Twips::from_pixels(thickness.clamp(0.0, 255.0));
let join_style = joints_to_join_style(joints, miter_limit);
let (allow_scale_x, allow_scale_y) = scale_mode_to_allow_scale_bits(&scale_mode)?;
let mut line_style = LineStyle::new()
.with_width(width)
.with_start_cap(caps)
.with_end_cap(caps)
.with_join_style(join_style)
.with_allow_scale_x(allow_scale_x)
.with_allow_scale_y(allow_scale_y)
.with_is_pixel_hinted(pixel_hinting)
.with_allow_close(false);
if let Some(fill) = fill {
line_style = line_style.with_fill_style(fill);
}
drawing.set_line_style(Some(line_style));
}
} else if class == activation.avm2().classes().graphicstrianglepath {
handle_graphics_triangle_path(activation, drawing, obj)?;
} else {
panic!("Unknown graphics data class {:?}", class);
}
Ok(Value::Undefined)
Ok(())
}
/// Implements `Graphics.readGraphicsData`
pub fn read_graphics_data<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData");
let value_type = activation.avm2().classes().igraphicsdata;
let new_storage = VectorStorage::new(0, false, Some(value_type), activation);
Ok(VectorObject::from_vector(new_storage, activation)?.into())
#[derive(Debug, Clone, Copy)]
enum TriangleCulling {
None,
Positive,
Negative,
}
fn culling_to_triangle_culling<'gc>(
activation: &mut Activation<'_, 'gc>,
culling: AvmString,
) -> Result<TriangleCulling, Error<'gc>> {
if &culling == b"none" {
Ok(TriangleCulling::None)
} else if &culling == b"positive" {
Ok(TriangleCulling::Positive)
} else if &culling == b"negative" {
Ok(TriangleCulling::Negative)
} else {
Err(make_error_2008(activation, "culling"))
}
}
type Triangle = (Point<Twips>, Point<Twips>, Point<Twips>);
fn handle_graphics_triangle_path<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
obj: &Object<'gc>,
) -> Result<(), Error<'gc>> {
let culling = {
let culling = obj
.get_public_property("culling", activation)?
.coerce_to_string(activation)?;
culling_to_triangle_culling(activation, culling)?
};
let vertices = obj
.get_public_property("vertices", activation)?
.coerce_to_object(activation)?;
let indices = obj
.get_public_property("indices", activation)?
.coerce_to_object(activation)?;
let _uvt_data = obj
.get_public_property("uvtData", activation)?
.coerce_to_object(activation)?;
if let Some(vertices) = vertices.as_vector_storage() {
if let Some(indices) = indices.as_vector_storage() {
fn read_point<'gc>(
vertices: &VectorStorage<'gc>,
index: usize,
activation: &mut Activation<'_, 'gc>,
) -> Result<Point<Twips>, Error<'gc>> {
let x = {
let x = vertices
.get(2 * index, activation)?
.coerce_to_number(activation)?;
Twips::from_pixels(x)
};
let y = {
let y = vertices
.get(2 * index + 1, activation)?
.coerce_to_number(activation)?;
Twips::from_pixels(y)
};
Ok(Point::new(x, y))
}
fn next_triangle<'gc>(
vertices: &VectorStorage<'gc>,
indices: &mut impl Iterator<Item = Value<'gc>>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Option<Triangle>, Error<'gc>> {
match (indices.next(), indices.next(), indices.next()) {
(Some(i0), Some(i1), Some(i2)) => {
let i0 = i0.coerce_to_u32(activation)? as usize;
let i1 = i1.coerce_to_u32(activation)? as usize;
let i2 = i2.coerce_to_u32(activation)? as usize;
let p0 = read_point(vertices, i0, activation)?;
let p1 = read_point(vertices, i1, activation)?;
let p2 = read_point(vertices, i2, activation)?;
Ok(Some((p0, p1, p2)))
}
_ => Ok(None),
}
}
let indices = &mut indices.iter();
while let Some((a, b, c)) = next_triangle(&vertices, indices, activation)? {
match culling {
TriangleCulling::None => {
drawing.draw_command(DrawCommand::MoveTo(a));
drawing.draw_command(DrawCommand::LineTo(b));
drawing.draw_command(DrawCommand::LineTo(c));
drawing.draw_command(DrawCommand::LineTo(a));
}
TriangleCulling::Positive => {
panic!("Positive culling not implemented")
}
TriangleCulling::Negative => {
panic!("Negative culling not implemented")
}
}
}
} else {
panic!("Indices is not a vector");
};
}
Ok(())
}
fn handle_igraphics_fill<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
obj: &Object<'gc>,
) -> Result<Option<FillStyle>, Error<'gc>> {
let class = obj.instance_of().expect("No class");
if class == activation.avm2().classes().graphicsbitmapfill {
let style = handle_bitmap_fill(activation, drawing, obj)?;
Ok(Some(style))
} else if class == activation.avm2().classes().graphicsendfill {
Ok(None)
} else if class == activation.avm2().classes().graphicsgradientfill {
let style = handle_gradient_fill(activation, obj)?;
Ok(Some(style))
} else if class == activation.avm2().classes().graphicssolidfill {
let style = handle_solid_fill(activation, obj)?;
Ok(Some(style))
} else if class == activation.avm2().classes().graphicsshaderfill {
tracing::warn!("Graphics shader fill unimplemented {:?}", class);
Ok(None)
} else {
tracing::warn!("Unknown graphics fill class {:?}", class);
Ok(None)
}
}
fn handle_solid_fill<'gc>(
activation: &mut Activation<'_, 'gc>,
obj: &Object<'gc>,
) -> Result<FillStyle, Error<'gc>> {
let alpha = obj
.get_public_property("alpha", activation)?
.coerce_to_number(activation)
.unwrap_or(1.0);
let color = obj
.get_public_property("color", activation)?
.coerce_to_u32(activation)
.unwrap_or(0);
Ok(FillStyle::Color(color_from_args(color, alpha)))
}
fn handle_gradient_fill<'gc>(
activation: &mut Activation<'_, 'gc>,
obj: &Object<'gc>,
) -> Result<FillStyle, Error<'gc>> {
let alphas = obj
.get_public_property("alphas", activation)?
.coerce_to_object(activation)?;
let colors = obj
.get_public_property("colors", activation)?
.coerce_to_object(activation)?;
let ratios = obj
.get_public_property("ratios", activation)?
.coerce_to_object(activation)?;
let gradient_type = {
let gradient_type = obj
.get_public_property("type", activation)?
.coerce_to_string(activation)?;
parse_gradient_type(activation, gradient_type)?
};
let records = build_gradient_records(
activation,
&colors.as_array_storage().expect("Guaranteed by AS"),
&alphas.as_array_storage().expect("Guaranteed by AS"),
&ratios.as_array_storage().expect("Guaranteed by AS"),
)?;
let matrix = {
let matrix = obj
.get_public_property("matrix", activation)
.ok()
.and_then(|mat| mat.coerce_to_object(activation).ok());
match matrix {
Some(matrix) => Matrix::from(object_to_matrix(matrix, activation)?),
None => Matrix::IDENTITY,
}
};
let spread = {
let spread_method = obj
.get_public_property("spreadMethod", activation)?
.coerce_to_string(activation)?;
parse_spread_method(spread_method)
};
let interpolation = {
let interpolation_method = obj
.get_public_property("interpolationMethod", activation)?
.coerce_to_string(activation)?;
parse_interpolation_method(interpolation_method)
};
let focal_point = obj
.get_public_property("focalPointRatio", activation)?
.coerce_to_number(activation)?;
let fill = match gradient_type {
GradientType::Linear => FillStyle::LinearGradient(Gradient {
matrix,
spread,
interpolation,
records,
}),
GradientType::Radial if focal_point == 0.0 => FillStyle::RadialGradient(Gradient {
matrix,
spread,
interpolation,
records,
}),
_ => FillStyle::FocalGradient {
gradient: Gradient {
matrix,
spread,
interpolation,
records,
},
focal_point: Fixed8::from_f64(focal_point),
},
};
Ok(fill)
}
fn handle_bitmap_fill<'gc>(
activation: &mut Activation<'_, 'gc>,
drawing: &mut Drawing,
obj: &Object<'gc>,
) -> Result<FillStyle, Error<'gc>> {
let bitmap_data = obj
.get_public_property("bitmapData", activation)?
.coerce_to_object(activation)?
.as_bitmap_data()
.expect("Bitmap argument is ensured to be a BitmapData from actionscript");
let matrix = obj
.get_public_property("matrix", activation)
.and_then(|prop| {
let matrix = prop.coerce_to_object(activation)?;
let matrix = Matrix::from(object_to_matrix(matrix, activation)?);
Ok(matrix)
})
.unwrap_or(Matrix::IDENTITY);
let is_repeating = obj
.get_public_property("repeat", activation)?
.coerce_to_boolean();
let is_smoothed = obj
.get_public_property("smooth", activation)?
.coerce_to_boolean();
let handle =
bitmap_data.bitmap_handle(activation.context.gc_context, activation.context.renderer);
let bitmap = ruffle_render::bitmap::BitmapInfo {
handle,
width: bitmap_data.width() as u16,
height: bitmap_data.height() as u16,
};
let scale_matrix = Matrix::scale(
Fixed16::from_f64(bitmap.width as f64),
Fixed16::from_f64(bitmap.height as f64),
);
let id = drawing.add_bitmap(bitmap);
let style = FillStyle::Bitmap {
id,
matrix: matrix * scale_matrix,
is_smoothed,
is_repeating,
};
Ok(style)
}

View File

@ -19,7 +19,7 @@ use std::slice::SliceIndex;
///
/// A vector may also be configured to have a fixed size; when this is enabled,
/// attempts to modify the length fail.
#[derive(Collect, Clone)]
#[derive(Collect, Clone, Debug)]
#[collect(no_drop)]
pub struct VectorStorage<'gc> {
/// The storage for vector values.