From 6b89c671e2cbed544f871c6d98b5aa079a0d502f Mon Sep 17 00:00:00 2001 From: Fancy2209 Date: Tue, 11 Nov 2025 18:35:45 -0100 Subject: [PATCH 1/2] avm2: Partially implement Graphics.readGraphicsData Rebased version of Evilpie's PR Co-Authored by: evilpies@gmail.com --- core/src/avm2/globals.rs | 32 +++++ .../avm2/globals/flash/display/graphics.rs | 109 +++++++++++++++++- core/src/display_object/graphic.rs | 2 +- core/src/drawing.rs | 22 ++-- 4 files changed, 151 insertions(+), 14 deletions(-) diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 13c1be875975..94b594b54144 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -178,6 +178,15 @@ pub struct SystemClasses<'gc> { pub workerdomain: ClassObject<'gc>, pub messagechannel: ClassObject<'gc>, pub securitydomain: ClassObject<'gc>, + pub graphicsbitmapfill: ClassObject<'gc>, + pub graphicsendfill: ClassObject<'gc>, + pub graphicsgradientfill: ClassObject<'gc>, + pub graphicspath: ClassObject<'gc>, + pub graphicstrianglepath: ClassObject<'gc>, + pub graphicssolidfill: ClassObject<'gc>, + pub graphicsshaderfill: ClassObject<'gc>, + pub graphicsstroke: ClassObject<'gc>, + } #[derive(Clone, Collect)] @@ -356,6 +365,14 @@ impl<'gc> SystemClasses<'gc> { workerdomain: object, messagechannel: object, securitydomain: object, + graphicsbitmapfill: object, + graphicsendfill: object, + graphicsgradientfill: object, + graphicspath: object, + graphicstrianglepath: object, + graphicssolidfill: object, + graphicsshaderfill: object, + graphicsstroke: object, } } } @@ -684,6 +701,21 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) { ("flash.display", "Scene", scene), ("flash.display", "FrameLabel", framelabel), ("flash.display", "Graphics", graphics), + ("flash.display", "GraphicsBitmapFill", graphicsbitmapfill), + ("flash.display", "GraphicsEndFill", graphicsendfill), + ( + "flash.display", + "GraphicsGradientFill", + graphicsgradientfill + ), + ("flash.display", "GraphicsPath", graphicspath), + ( + "flash.display", + "GraphicsTrianglePath", + graphicstrianglepath + ), + ("flash.display", "GraphicsSolidFill", graphicssolidfill), + ("flash.display", "GraphicsStroke", graphicsstroke), ("flash.display", "Loader", loader), ("flash.display", "LoaderInfo", loaderinfo), ("flash.display", "MorphShape", morphshape), diff --git a/core/src/avm2/globals/flash/display/graphics.rs b/core/src/avm2/globals/flash/display/graphics.rs index f59cae9fe4d5..7d89da699af5 100644 --- a/core/src/avm2/globals/flash/display/graphics.rs +++ b/core/src/avm2/globals/flash/display/graphics.rs @@ -19,7 +19,7 @@ use crate::avm2::vector::VectorStorage; use crate::avm2::{ArrayStorage, Error}; use crate::avm2_stub_method; use crate::display_object::TDisplayObject; -use crate::drawing::Drawing; +use crate::drawing::{Drawing, DrawingFill, DrawingPath}; use crate::string::{AvmString, WStr}; use ruffle_render::shape_utils::{DrawCommand, FillRule, GradientType}; use std::f64::consts::FRAC_1_SQRT_2; @@ -1347,13 +1347,114 @@ pub fn line_bitmap_style<'gc>( /// Implements `Graphics.readGraphicsData` pub fn read_graphics_data<'gc>( activation: &mut Activation<'_, 'gc>, - _this: Value<'gc>, + this: Value<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData"); + let mut result = Vec::new(); + let this = this.as_object().unwrap(); + + if let Some(this) = this.as_display_object() { + if let Some(draw) = this.as_drawing() { + for path in draw.paths() { + match path { + DrawingPath::Fill(DrawingFill { + style, commands, .. + }) => { + match style { + FillStyle::Color(color) => { + let color_value = Value::Integer(color.to_rgb() as i32); + let alpha_value = Value::Number(255.0 / (color.a as f64)); + + + result.push( + activation + .avm2() + .classes() + .graphicssolidfill + .construct(activation, &[color_value, alpha_value])?, + ); + } + _ => println!("unsupported style {style:?}"), + }; + + + let mut path_commands = Vec::new(); + let mut path_data = Vec::new(); + + + let mut add_point = |p: &Point| { + let x = p.x.to_pixels(); + let y = p.y.to_pixels(); + path_data.push(x); + path_data.push(y); + }; + + + for command in commands { + match command { + DrawCommand::MoveTo(point) => { + path_commands.push(1 /* MOVE_TO */); + add_point(&point); + } + DrawCommand::LineTo(point) => { + path_commands.push(2 /* LINE_TO */); + add_point(&point); + } + DrawCommand::QuadraticCurveTo { control, anchor } => { + path_commands.push(3 /* CURVE_TO */); + add_point(&control); + add_point(&anchor); + } + _ => println!("unsupported command {command:?}"), + } + } + + + let commands_storage = VectorStorage::from_values( + path_commands.into_iter().map(|v| v.into()).collect(), + true, + Some(activation.avm2().class_defs().int), + ); + let commands_vector = VectorObject::from_vector(commands_storage, activation); + + + let data_storage = VectorStorage::from_values( + path_data.into_iter().map(|v| v.into()).collect(), + true, + Some(activation.avm2().class_defs().number), + ); + let data_vector = VectorObject::from_vector(data_storage, activation); + + + result.push( + activation + .avm2() + .classes() + .graphicspath + .construct(activation, &[commands_vector.into(), data_vector.into()])?, + ); + + + // Only do this at the end? + result.push( + activation + .avm2() + .classes() + .graphicsendfill + .construct(activation, &[])?, + ); + } + _ => println!("unsupported path: {path:?}"), + } + } + } + } + + let value_type = activation.avm2().class_defs().igraphicsdata; - let new_storage = VectorStorage::new(0, false, Some(value_type)); - Ok(VectorObject::from_vector(new_storage, activation).into()) + let storage = VectorStorage::from_values(result, false, Some(value_type)); + Ok(VectorObject::from_vector(storage, activation).into()) } fn read_point<'gc>( diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index c4b0e117de9f..0e58ceeeb832 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -110,7 +110,7 @@ impl<'gc> Graphic<'gc> { } pub fn drawing_mut(&self) -> RefMut<'_, Drawing> { - self.0.drawing.get_or_init(Default::default).borrow_mut() + self.0.drawing.get_or_init(||Box::new(RefCell::new(Drawing::from_swf_shape(&self.0.shared.get().shape)))).borrow_mut() } pub fn set_avm2_class(self, mc: &Mutation<'gc>, class: Avm2ClassObject<'gc>) { diff --git a/core/src/drawing.rs b/core/src/drawing.rs index be9c108d1bea..9d965d2cff39 100644 --- a/core/src/drawing.rs +++ b/core/src/drawing.rs @@ -415,6 +415,10 @@ impl Drawing { } } } + + pub fn paths(&self) -> &[DrawingPath] { + return self.paths.as_slice(); + } } impl BitmapSource for Drawing { @@ -430,21 +434,21 @@ impl BitmapSource for Drawing { } #[derive(Debug, Clone)] -struct DrawingFill { - style: FillStyle, - rule: FillRule, - commands: Vec, +pub struct DrawingFill { + pub style: FillStyle, + pub rule: FillRule, + pub commands: Vec, } #[derive(Debug, Clone)] -struct DrawingLine { - style: LineStyle, - commands: Vec, - is_closed: bool, +pub struct DrawingLine { + pub style: LineStyle, + pub commands: Vec, + pub is_closed: bool, } #[derive(Debug, Clone)] -enum DrawingPath { +pub enum DrawingPath { Fill(DrawingFill), Line(DrawingLine), } From f5a9913054adff9fcbcffa0823f089b92be90aba Mon Sep 17 00:00:00 2001 From: Fancy2209 Date: Tue, 11 Nov 2025 22:45:15 -0100 Subject: [PATCH 2/2] Implement BitmapFills in Graphics.readGraphicsData without actual bitmapdata Need to add <'gc> lifetime to drawing forproper impl --- .../avm2/globals/flash/display/graphics.rs | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/globals/flash/display/graphics.rs b/core/src/avm2/globals/flash/display/graphics.rs index 7d89da699af5..3bdf94ab6a54 100644 --- a/core/src/avm2/globals/flash/display/graphics.rs +++ b/core/src/avm2/globals/flash/display/graphics.rs @@ -19,6 +19,7 @@ use crate::avm2::vector::VectorStorage; use crate::avm2::{ArrayStorage, Error}; use crate::avm2_stub_method; use crate::display_object::TDisplayObject; +use crate::prelude::TDisplayObjectContainer; use crate::drawing::{Drawing, DrawingFill, DrawingPath}; use crate::string::{AvmString, WStr}; use ruffle_render::shape_utils::{DrawCommand, FillRule, GradientType}; @@ -1348,14 +1349,32 @@ pub fn line_bitmap_style<'gc>( pub fn read_graphics_data<'gc>( activation: &mut Activation<'_, 'gc>, this: Value<'gc>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error<'gc>> { avm2_stub_method!(activation, "flash.display.Graphics", "readGraphicsData"); - let mut result = Vec::new(); + let recurse = args.get_bool(0); let this = this.as_object().unwrap(); + let mut drawings: Vec = Vec::new(); + let mut result = Vec::new(); + if let Some(this) = this.as_display_object() { + if let Some(draw) = this.as_drawing() { + drawings.push(draw.clone()); + } + + if recurse { + if let Some(container) = this.as_container() { + for child in container.iter_render_list() { + if let Some(draw) = child.as_drawing() { + drawings.push(draw.clone()); + } + } + } + } + + for draw in drawings { for path in draw.paths() { match path { DrawingPath::Fill(DrawingFill { @@ -1375,6 +1394,37 @@ pub fn read_graphics_data<'gc>( .construct(activation, &[color_value, alpha_value])?, ); } + FillStyle::Bitmap { + id: _, + matrix, + is_smoothed:_, // According to the docs this value is hardcoded in return + is_repeating:_, // According to the docs this value is hardcoded in return + } => { + + // TODO + //let bitmap = draw.bitmaps().get(*id as usize); + let args = [ + Value::Number(matrix.a.into()), + Value::Number(matrix.b.into()), + Value::Number(matrix.c.into()), + Value::Number(matrix.d.into()), + Value::Number(matrix.tx.to_pixels().into()), + Value::Number(matrix.ty.to_pixels().into()), + ]; + let matrix = activation + .avm2() + .classes() + .matrix + .construct(activation, &args)?; + + result.push( + activation + .avm2() + .classes() + .graphicsbitmapfill + .construct(activation, &[Value::Undefined, matrix.into(), Value::Bool(true), Value::Bool(false)])?, + ); + } _ => println!("unsupported style {style:?}"), };