From 9d46d6758801ec11e93015e562c6fa3141346229 Mon Sep 17 00:00:00 2001 From: CUB3D Date: Mon, 19 Oct 2020 01:25:23 +0100 Subject: [PATCH] core+web+renderer: Add load/attach Bitmap and handling for invalid args with BitmapData --- core/src/avm1/globals/bitmap_data.rs | 757 ++++++++++++++++---- core/src/avm1/globals/movie_clip.rs | 31 +- core/src/avm1/object/bitmap_data.rs | 148 +++- core/src/backend/render.rs | 15 +- core/src/character.rs | 2 +- core/src/tag_utils.rs | 3 +- core/tests/swfs/avm1/bitmap_data/output.txt | 643 ++++++++++++++++- core/tests/swfs/avm1/bitmap_data/test.fla | Bin 5865 -> 6485 bytes core/tests/swfs/avm1/bitmap_data/test.swf | Bin 1750 -> 2455 bytes render/canvas/src/lib.rs | 59 ++ render/webgl/src/lib.rs | 46 ++ render/wgpu/src/lib.rs | 37 + 12 files changed, 1562 insertions(+), 179 deletions(-) diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index a680ee0f3..4f654b157 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -5,10 +5,11 @@ use crate::avm1::error::Error; use crate::avm1::function::{Executable, FunctionObject}; use crate::avm1::object::bitmap_data::BitmapDataObject; use crate::avm1::{Object, TObject, Value}; +use crate::character::Character; use enumset::EnumSet; use gc_arena::MutationContext; - - +use rand::Rng; +use swf::CharacterId; pub fn constructor<'gc>( activation: &mut Activation<'_, 'gc, '_>, @@ -27,6 +28,8 @@ pub fn constructor<'gc>( .unwrap_or(&Value::Number(0.0)) .coerce_to_u32(activation)?; + log::warn!("New bitmap data {}x{}", width, height); + if width > 2880 || height > 2880 || width <= 0 || height <= 0 { log::warn!("Invalid BitmapData size {}x{}", width, height); return Ok(Value::Undefined); @@ -37,10 +40,11 @@ pub fn constructor<'gc>( .unwrap_or(&Value::Bool(true)) .as_bool(activation.current_swf_version()); - //Hmm can't write this in hex - // 0xFFFFFFFF as f64; + let fill_color = args .get(3) + // can't write this in hex + // 0xFFFFFFFF as f64; .unwrap_or(&Value::Number(4294967295_f64)) .coerce_to_i32(activation)?; @@ -51,33 +55,6 @@ pub fn constructor<'gc>( Ok(Value::Undefined) } -pub fn load_bitmap<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - _this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - //TODO: how does this handle no args - let name = args - .get(0) - .unwrap_or(&Value::Undefined) - .coerce_to_string(activation)?; - - log::warn!("BitmapData.loadBitmap({:?}), not impl", name); - - //TODO: correct method here? also rename - let mc = activation.context.swf; - - //TODO: unwrap - // activation.context.library.library_for_movie(mc.clone()).expect("Unable to get library for movie").instantiate_by_export_name(name.as_str(), activation.context.gc_context).expect("Unable to instantiate"); - - //TODO: write tests for props on the return val of this, for now we will just stub - - let proto = activation.context.system_prototypes.bitmap_data_constructor; - let new_bitmap = proto.construct(activation, &[])?; - - Ok(new_bitmap.into()) -} - pub fn get_height<'gc>( _activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, @@ -86,7 +63,7 @@ pub fn get_height<'gc>( let bitmap_data = this.as_bitmap_data_object().unwrap(); if bitmap_data.get_disposed() { - return Ok((-1).into()) + return Ok((-1).into()); } Ok(this.as_bitmap_data_object().unwrap().get_height().into()) @@ -100,7 +77,7 @@ pub fn get_width<'gc>( let bitmap_data = this.as_bitmap_data_object().unwrap(); if bitmap_data.get_disposed() { - return Ok((-1).into()) + return Ok((-1).into()); } Ok(this.as_bitmap_data_object().unwrap().get_width().into()) @@ -114,7 +91,7 @@ pub fn get_transparent<'gc>( let bitmap_data = this.as_bitmap_data_object().unwrap(); if bitmap_data.get_disposed() { - return Ok((-1).into()) + return Ok((-1).into()); } Ok(this @@ -132,7 +109,7 @@ pub fn get_rectangle<'gc>( let bitmap_data = this.as_bitmap_data_object().unwrap(); if bitmap_data.get_disposed() { - return Ok((-1).into()) + return Ok((-1).into()); } let proto = activation.context.system_prototypes.rectangle_constructor; @@ -156,15 +133,11 @@ pub fn get_pixel<'gc>( let bitmap_data = this.as_bitmap_data_object().unwrap(); if bitmap_data.get_disposed() { - return Ok((-1).into()) + return Ok((-1).into()); } - let x = args - .get(0) - .and_then(|x| x.coerce_to_i32(activation).ok()); - let y = args - .get(1) - .and_then(|x| x.coerce_to_i32(activation).ok()); + let x = args.get(0).and_then(|x| x.coerce_to_i32(activation).ok()); + let y = args.get(1).and_then(|x| x.coerce_to_i32(activation).ok()); if let Some((x, y)) = x.zip(y) { Ok(bitmap_data.get_pixel(x, y).into()) @@ -173,82 +146,74 @@ pub fn get_pixel<'gc>( } } -//TODO: out of bounds / missing args / neg args -pub fn set_pixel<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = args - .get(0) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - - let y = args - .get(1) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - - let color = args - .get(2) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_i32(activation)?; - - let bitmap_data = this.as_bitmap_data_object().unwrap(); - bitmap_data.set_pixel(activation.context.gc_context, x, y, color.into()); - - Ok(Value::Undefined) -} - -//TODO: out of bounds / missing args / neg args -pub fn set_pixel32<'gc>( - activation: &mut Activation<'_, 'gc, '_>, - this: Object<'gc>, - args: &[Value<'gc>], -) -> Result, Error<'gc>> { - let x = args - .get(0) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - - let y = args - .get(1) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - - let color = args - .get(2) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_i32(activation)?; - - let bitmap_data = this.as_bitmap_data_object().unwrap(); - bitmap_data.set_pixel32(activation.context.gc_context, x, y, color.into()); - - Ok(Value::Undefined) -} - -//TODO: out of bounds / missing args / neg args pub fn get_pixel32<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let x = args - .get(0) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - - let y = args - .get(1) - .unwrap_or(&Value::Number(0_f64)) - .coerce_to_u32(activation)?; - let bitmap_data = this.as_bitmap_data_object().unwrap(); - //TODO: move this unwrap to the object - let x: i32 = bitmap_data.get_pixel32(x, y).unwrap_or(0.into()).into(); + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } - Ok(x.into()) + let x = args.get(0).and_then(|x| x.coerce_to_i32(activation).ok()); + let y = args.get(1).and_then(|x| x.coerce_to_i32(activation).ok()); + + if let Some((x, y)) = x.zip(y) { + let asdf: i32 = bitmap_data.get_pixel32(x, y).into(); + Ok(asdf.into()) + } else { + Ok((-1).into()) + } +} + +pub fn set_pixel<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + let x = args.get(0).and_then(|v| v.coerce_to_u32(activation).ok()); + + let y = args.get(1).and_then(|v| v.coerce_to_u32(activation).ok()); + + let color = args.get(2).and_then(|v| v.coerce_to_i32(activation).ok()); + + if let Some(((x, y), color)) = x.zip(y).zip(color) { + bitmap_data.set_pixel(activation.context.gc_context, x, y, color.into()); + } + + Ok(Value::Undefined) +} + +pub fn set_pixel32<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + let x = args.get(0).and_then(|v| v.coerce_to_i32(activation).ok()); + + let y = args.get(1).and_then(|v| v.coerce_to_i32(activation).ok()); + + let color = args.get(2).and_then(|v| v.coerce_to_i32(activation).ok()); + + if let Some(((x, y), color)) = x.zip(y).zip(color) { + bitmap_data.set_pixel32(activation.context.gc_context, x, y, color.into()); + } + + Ok(Value::Undefined) } //TODO: missing args / out of bounds @@ -257,6 +222,13 @@ pub fn copy_channel<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("copy channel not fully implemented"); let source_bitmap = args .get(0) .unwrap_or(&Value::Undefined) @@ -286,7 +258,6 @@ pub fn copy_channel<'gc>( .coerce_to_i32(activation)?; if let Some(source_bitmap) = source_bitmap.as_bitmap_data_object() { - let bitmap_data = this.as_bitmap_data_object().unwrap(); bitmap_data.copy_channel( activation.context.gc_context, source_bitmap, @@ -298,7 +269,6 @@ pub fn copy_channel<'gc>( Ok(Value::Undefined) } -//TODO: missing args / out of bounds pub fn fill_rect<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, @@ -306,35 +276,31 @@ pub fn fill_rect<'gc>( ) -> Result, Error<'gc>> { let rectangle = args .get(0) - //TODO: - .unwrap() + .unwrap_or(&Value::Undefined) .coerce_to_object(activation); - let color = args - .get(1) - //TODO: - .unwrap() - .coerce_to_i32(activation)?; + let color = args.get(1).and_then(|v| v.coerce_to_i32(activation).ok()); - //TODO: does this only work on actual rectangles or does it work on anything with x/y/w/h - let x = rectangle.get("x", activation)?.coerce_to_u32(activation)?; - let y = rectangle.get("y", activation)?.coerce_to_u32(activation)?; - let width = rectangle - .get("width", activation)? - .coerce_to_u32(activation)?; - let height = rectangle - .get("height", activation)? - .coerce_to_u32(activation)?; + if let Some(color) = color { + let x = rectangle.get("x", activation)?.coerce_to_u32(activation)?; + let y = rectangle.get("y", activation)?.coerce_to_u32(activation)?; + let width = rectangle + .get("width", activation)? + .coerce_to_u32(activation)?; + let height = rectangle + .get("height", activation)? + .coerce_to_u32(activation)?; - let bitmap_data = this.as_bitmap_data_object().unwrap(); - bitmap_data.fill_rect( - activation.context.gc_context, - x, - y, - width, - height, - color.into(), - ); + let bitmap_data = this.as_bitmap_data_object().unwrap(); + bitmap_data.fill_rect( + activation.context.gc_context, + x, + y, + width, + height, + color.into(), + ); + } Ok(Value::Undefined) } @@ -342,9 +308,13 @@ pub fn fill_rect<'gc>( pub fn clone<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, - args: &[Value<'gc>], + _args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(bitmap_data) = this.as_bitmap_data_object() { + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + let proto = activation.context.system_prototypes.bitmap_data_constructor; let new_bitmap_data = proto.construct( activation, @@ -368,18 +338,30 @@ pub fn clone<'gc>( pub fn dispose<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, - args: &[Value<'gc>], + _args: &[Value<'gc>], ) -> Result, Error<'gc>> { let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + bitmap_data.dispose(activation.context.gc_context); Ok(Value::Undefined) } +// todo: bad args pub fn flood_fill<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + let x = args .get(0) .unwrap_or(&Value::Number(0_f64)) @@ -395,11 +377,356 @@ pub fn flood_fill<'gc>( .unwrap_or(&Value::Number(0_f64)) .coerce_to_i32(activation)?; - let bitmap_data = this.as_bitmap_data_object().unwrap(); bitmap_data.flood_fill(activation.context.gc_context, x, y, color.into()); Ok(Value::Undefined) } +// todo: bad args +pub fn noise<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("noise not implemented"); + //todo: default + let random_seed = args + .get(0) + .unwrap_or(&Value::Number(0.0)) + .coerce_to_u32(activation)?; + + // 0 - 255 + let low = args + .get(1) + .unwrap_or(&Value::Number(0.0)) + .coerce_to_u32(activation)?; + + // 0 - 255 + let height = args + .get(2) + .unwrap_or(&Value::Number(255.0)) + .coerce_to_u32(activation)?; + + // what is the range here + let channel_options = args + .get(3) + .unwrap_or(&Value::Number((1 | 2 | 4) as f64)) + .coerce_to_u32(activation)?; + + // 0 - 255 + let gray_scale = args + .get(4) + .unwrap_or(&Value::Bool(false)) + .as_bool(activation.current_swf_version()); + + + let width = bitmap_data.get_width(); + let height = bitmap_data.get_height(); + for x in 0..width { + for y in 0..height { + let pixel_color = activation.context.rng.gen_range(low, height); + bitmap_data.set_pixel32_raw( + activation.context.gc_context, + x, + y, + (pixel_color as i32).into(), + ); + } + } + + bitmap_data.dispose(activation.context.gc_context); + Ok(Value::Undefined) +} + +pub fn apply_filter<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + log::warn!("BitmapData.applyFilter - not yet implemented"); + Ok((-1).into()) +} + +//TODO: +pub fn draw<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + let swf_movie = _activation.context.swf.clone(); + let character = _activation + .context + .library + .library_for_movie(swf_movie) + .unwrap() + .get_character_by_id(191 as CharacterId); + log::warn!("draw character {:?}", character); + + log::warn!("BitmapData.draw - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn generate_filter_rect<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.generateFilterRect - not yet implemented"); + Ok(Value::Undefined) +} + +// TODO: args tests +pub fn color_transform<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + let rectangle = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_object(activation); + + let color_transform = args + .get(1) + .unwrap_or(&Value::Undefined) + .coerce_to_object(activation); + + //TODO: does this only work on actual rectangles or does it work on anything with x/y/w/h + let x = rectangle.get("x", activation)?.coerce_to_u32(activation)?; + let y = rectangle.get("y", activation)?.coerce_to_u32(activation)?; + let width = rectangle + .get("width", activation)? + .coerce_to_u32(activation)?; + let height = rectangle + .get("height", activation)? + .coerce_to_u32(activation)?; + + //TODO: does this only work on actual cts or does it work on anything with x/y/w/h + let red_multiplier = color_transform + .get("redMultiplier", activation)? + .coerce_to_f64(activation)? as f32; + let green_multiplier = color_transform + .get("greenMultiplier", activation)? + .coerce_to_f64(activation)? as f32; + let blue_multiplier = color_transform + .get("blueMultiplier", activation)? + .coerce_to_f64(activation)? as f32; + let alpha_multiplier = color_transform + .get("alphaMultiplier", activation)? + .coerce_to_f64(activation)? as f32; + let red_offset = color_transform + .get("redOffset", activation)? + .coerce_to_f64(activation)? as f32 + / 255.0; + let green_offset = color_transform + .get("greenOffset", activation)? + .coerce_to_f64(activation)? as f32 + / 255.0; + let blue_offset = color_transform + .get("blueOffset", activation)? + .coerce_to_f64(activation)? as f32 + / 255.0; + let alpha_offset = color_transform + .get("alphaOffset", activation)? + .coerce_to_f64(activation)? as f32 + / 255.0; + + bitmap_data.color_transform( + activation.context.gc_context, + x, + y, + x + width, + y + height, + alpha_multiplier, + alpha_offset, + red_multiplier, + red_offset, + green_multiplier, + green_offset, + blue_multiplier, + blue_offset, + ); + + Ok(Value::Undefined) +} + +// TODO: args tests +pub fn get_color_bounds_rect<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + let mask = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_i32(activation)?; + + let color = args + .get(1) + .unwrap_or(&Value::Undefined) + .coerce_to_i32(activation)?; + + let find_color = args + .get(2) + .unwrap_or(&Value::Bool(true)) + .as_bool(activation.current_swf_version()); + + let (x, y, w, h) = bitmap_data.get_color_bounds_rect(mask, color, find_color); + + let proto = activation.context.system_prototypes.rectangle_constructor; + let rect = proto.construct(activation, &[x.into(), y.into(), w.into(), h.into()])?; + Ok(rect.into()) +} + +pub fn perlin_noise<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.perlinNoise - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn hit_test<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.hitTest - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn copy_pixels<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.copyPixels - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn merge<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.merge - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn palette_map<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.paletteMap - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn pixel_dissolve<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.pixelDissolve - not yet implemented"); + Ok(Value::Undefined) +} + +pub fn scroll<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.scroll - not yet implemented"); + Ok(Value::Undefined) +} +pub fn threshold<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + this: Object<'gc>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let bitmap_data = this.as_bitmap_data_object().unwrap(); + + if bitmap_data.get_disposed() { + return Ok((-1).into()); + } + + log::warn!("BitmapData.threshold - not yet implemented"); + Ok(Value::Undefined) +} + + pub fn create_proto<'gc>( gc_context: MutationContext<'gc, '_>, proto: Object<'gc>, @@ -503,14 +830,156 @@ pub fn create_proto<'gc>( Some(fn_proto), ); object.force_set_function("clone", clone, gc_context, EnumSet::empty(), Some(fn_proto)); - object.force_set_function("dispose", dispose, gc_context, EnumSet::empty(), Some(fn_proto)); - object.force_set_function("floodFill", flood_fill, gc_context, EnumSet::empty(), Some(fn_proto)); - - + object.force_set_function( + "dispose", + dispose, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "floodFill", + flood_fill, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function("noise", noise, gc_context, EnumSet::empty(), Some(fn_proto)); + object.force_set_function( + "colorTransform", + color_transform, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "getColorBoundsRect", + get_color_bounds_rect, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "perlinNoise", + perlin_noise, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "applyFilter", + apply_filter, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function("draw", draw, gc_context, EnumSet::empty(), Some(fn_proto)); + object.force_set_function( + "hitTest", + hit_test, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "generateFilterRect", + generate_filter_rect, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "copyPixels", + copy_pixels, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "merge", + merge, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "paletteMap", + palette_map, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "pixelDissolve", + pixel_dissolve, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "scroll", + scroll, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); + object.force_set_function( + "threshold", + threshold, + gc_context, + EnumSet::empty(), + Some(fn_proto), + ); bitmap_data_object.into() } +//todo +use crate::display_object::TDisplayObject; + +pub fn load_bitmap<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _this: Object<'gc>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let name = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_string(activation)?; + + log::warn!("BitmapData.loadBitmap({:?}), not yet implemented", name); + + let swf = activation.target_clip_or_root().movie().expect("No movie ?"); + + let bh = activation + .context + .library + .library_for_movie(swf) + .expect("No library for movie") + .get_character_by_export_name(name.as_str()) + .expect("No character for name"); + + let bitmap = match bh { + Character::Bitmap(b) => b.bitmap_handle(), + _ => unimplemented!(), + }; + //TODO: also return bounds? + let (w, h, pixels) = activation.context.renderer.get_bitmap_pixels(bitmap); + log::warn!("Got response {} {} {:?}", w, h, pixels); + + let proto = activation.context.system_prototypes.bitmap_data_constructor; + let new_bitmap = proto.construct(activation, &[w.into(), h.into()])?; + let new_bitmap_object = new_bitmap.as_bitmap_data_object().unwrap(); + + //todo: set w/h + new_bitmap_object.set_pixels( + activation.context.gc_context, + pixels.iter().map(|p| (*p as i32).into()).collect(), + ); + + Ok(new_bitmap.into()) +} + pub fn create_bitmap_data_object<'gc>( gc_context: MutationContext<'gc, '_>, bitmap_data_proto: Object<'gc>, diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index be46bfa2a..2d8a926ff 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -13,7 +13,7 @@ use crate::avm_error; use crate::avm_warn; use crate::backend::navigator::NavigationMethod; use crate::display_object::{ - DisplayObject, EditText, MovieClip, TDisplayObject, TDisplayObjectContainer, + DisplayObject, EditText, MovieClip, TDisplayObject, TDisplayObjectContainer, Bitmap }; use crate::ecma_conversions::f64_to_wrapping_i32; use crate::prelude::*; @@ -204,7 +204,8 @@ pub fn create_proto<'gc>( "curveTo" => curve_to, "endFill" => end_fill, "lineStyle" => line_style, - "clear" => clear + "clear" => clear, + "attachBitmap" => attach_bitmap ); with_movie_clip_props!( @@ -218,6 +219,32 @@ pub fn create_proto<'gc>( object.into() } +fn attach_bitmap<'gc>( + mut movie_clip: MovieClip<'gc>, + activation: &mut Activation<'_, 'gc, '_>, + args: &[Value<'gc>], +) -> Result, Error<'gc>> { + + if let Some(bitmap) = args.get(0) { + if let Some(bitmap_data) = bitmap.coerce_to_object(activation).as_bitmap_data_object() { + if let Some(depth) = args.get(1) { + + let depth = depth + .coerce_to_i32(activation)? + .wrapping_add(AVM_DEPTH_BIAS); + + let rgba = bitmap_data.get_pixels_rgba(); + let bitmap_handle = activation.context.renderer.register_bitmap_raw(bitmap_data.get_width(), bitmap_data.get_height(), rgba); + //TODO: casting + let display_object = Bitmap::new(&mut activation.context, 0, bitmap_handle, bitmap_data.get_width() as u16, bitmap_data.get_height() as u16); + movie_clip.replace_at_depth(&mut activation.context, display_object.into(), depth.into()); + } + } + } + + Ok(Value::Undefined) +} + fn line_style<'gc>( movie_clip: MovieClip<'gc>, activation: &mut Activation<'_, 'gc, '_>, diff --git a/core/src/avm1/object/bitmap_data.rs b/core/src/avm1/object/bitmap_data.rs index be6daf851..bcb9e896f 100644 --- a/core/src/avm1/object/bitmap_data.rs +++ b/core/src/avm1/object/bitmap_data.rs @@ -114,9 +114,7 @@ impl fmt::Debug for BitmapDataObject<'_> { } impl<'gc> BitmapDataObject<'gc> { - add_field_accessors!( - [set_transparency, get_transparency, transparency, bool], - ); + add_field_accessors!([set_transparency, get_transparency, transparency, bool],); pub fn empty_object(gc_context: MutationContext<'gc, '_>, proto: Option>) -> Self { BitmapDataObject(GcCell::allocate( @@ -132,6 +130,12 @@ impl<'gc> BitmapDataObject<'gc> { )) } + pub fn get_pixels_rgba(&self) -> Vec { + self.0.read().pixels.iter().flat_map(|p| { + vec![p.get_red(), p.get_green(), p.get_blue(), p.get_alpha()] + }).collect() + } + pub fn get_disposed(&self) -> bool { self.0.read().disposed } @@ -176,43 +180,57 @@ impl<'gc> BitmapDataObject<'gc> { .copied() } - pub fn get_pixel32(&self, x: u32, y: u32) -> Option { - self.get_pixel_raw(x, y).map(|f| f.to_un_multiplied_alpha()) + pub fn get_pixel32(&self, x: i32, y: i32) -> Color { + self.get_pixel_raw(x as u32, y as u32) + .map(|f| f.to_un_multiplied_alpha()) + .unwrap_or(0.into()) } pub fn get_pixel(&self, x: i32, y: i32) -> i32 { if !self.is_point_in_bounds(x, y) { 0 } else { - self.get_pixel32(x as u32, y as u32).map(|p| p.with_alpha(0x0)).unwrap_or(0.into()).into() + self.get_pixel32(x, y).with_alpha(0x0).into() } } - fn set_pixel32_raw(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: Color) { + //TODO: private? + pub fn set_pixel32_raw( + &self, + gc_context: MutationContext<'gc, '_>, + x: u32, + y: u32, + color: Color, + ) { let width = self.get_width(); //TODO: bounds check self.0.write(gc_context).pixels[(x + y * width) as usize] = color; } - pub fn set_pixel32(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: Color) { - self.set_pixel32_raw( - gc_context, - x, - y, - color.to_premultiplied_alpha(self.get_transparency()), - ) + pub fn set_pixel32(&self, gc_context: MutationContext<'gc, '_>, x: i32, y: i32, color: Color) { + //TODO: what does flash do on set out of bounds + if self.is_point_in_bounds(x, y) { + self.set_pixel32_raw( + gc_context, + x as u32, + y as u32, + color.to_premultiplied_alpha(self.get_transparency()), + ) + } } pub fn set_pixel(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: Color) { let current_alpha = self.get_pixel_raw(x, y).map(|p| p.get_alpha()).unwrap_or(0); - self.set_pixel32_raw( - gc_context, - x, - y, - color - .with_alpha(current_alpha) - .to_premultiplied_alpha(self.get_transparency()), - ) + self.set_pixel32(gc_context, x as i32, y as i32, color.with_alpha(current_alpha)) + + // self.set_pixel32_raw( + // gc_context, + // x, + // y, + // color + // .with_alpha(current_alpha) + // .to_premultiplied_alpha(self.get_transparency()), + // ) } pub fn dispose(&self, gc_context: MutationContext<'gc, '_>) { @@ -273,10 +291,11 @@ impl<'gc> BitmapDataObject<'gc> { // TODO: if rect.contains((x, y)) and offset by pnt //TODO: how does this handle out of bounds - let original_color = self.get_pixel32(x as u32, y as u32).unwrap_or(0.into()).0 as u32; + let original_color = + self.get_pixel_raw(x as u32, y as u32).unwrap_or(0.into()).0 as u32; //TODO: does this calculation work if they are different sizes (might be fixed now) - let source_color = source.get_pixel32(x, y).unwrap_or(0.into()).0 as u32; + let source_color = source.get_pixel_raw(x, y).unwrap_or(0.into()).0 as u32; //TODO: should this channel be an enum? //TODO: need to support multiple (how does this work if you copy red -> blue and green or any other multi copy) @@ -363,6 +382,87 @@ impl<'gc> BitmapDataObject<'gc> { } } + pub fn color_transform( + &self, + gc_context: MutationContext<'gc, '_>, + min_x: u32, + min_y: u32, + max_x: u32, + max_y: u32, + a_mult: f32, + a_add: f32, + r_mult: f32, + r_add: f32, + g_mult: f32, + g_add: f32, + b_mult: f32, + b_add: f32, + ) { + for x in min_x..max_x { + for y in min_y..max_y { + let color = self.get_pixel_raw(x, y).unwrap_or(0.into()); + let a = ((color.get_alpha() as f32 * a_mult) + a_add) as u8; + let r = ((color.get_red() as f32 * r_mult) + r_add) as u8; + let g = ((color.get_green() as f32 * g_mult) + g_add) as u8; + let b = ((color.get_blue() as f32 * b_mult) + b_add) as u8; + + self.set_pixel32_raw(gc_context, x, y, Color::argb(a, r, g, b)) + } + } + } + + pub fn get_color_bounds_rect( + &self, + mask: i32, + color: i32, + find_color: bool, + ) -> (u32, u32, u32, u32) { + //TODO: option, if none take image bounds + let mut min_x = Option::::None; + let mut max_x = Option::::None; + let mut min_y = Option::::None; + let mut max_y = Option::::None; + + for x in 0..self.get_width() { + for y in 0..self.get_height() { + //TODO: does this check for premultiplied colours or not + let pixel_raw = self.get_pixel_raw(x, y).unwrap_or(0.into()).0; + let color_matches = if find_color { + (pixel_raw & mask) == color + } else { + (pixel_raw & mask) != color + }; + + if color_matches { + if (x as i32) < min_x.unwrap_or(self.get_width() as i32) { + min_x = Some(x as i32) + } + if (x as i32) > max_x.unwrap_or(-1) { + max_x = Some(x as i32) + } + + if (y as i32) < min_y.unwrap_or(self.get_height() as i32) { + min_y = Some(y as i32) + } + if (y as i32) > max_y.unwrap_or(-1) { + max_y = Some(y as i32) + } + } + } + } + let min_x = min_x.unwrap_or(0); + let min_y = min_y.unwrap_or(0); + let max_x = max_x.unwrap_or(self.get_width() as i32); + let max_y = max_y.unwrap_or(self.get_height() as i32); + + ( + min_x as u32, + min_y as u32, + (min_x + max_x) as u32, + (min_y + max_y) as u32, + ) + } + pub fn get_width(&self) -> u32 { self.0.read().width } diff --git a/core/src/backend/render.rs b/core/src/backend/render.rs index 65c24259e..b0d910d8a 100644 --- a/core/src/backend/render.rs +++ b/core/src/backend/render.rs @@ -42,6 +42,9 @@ pub trait RenderBackend: Downcast { fn activate_mask(&mut self); fn deactivate_mask(&mut self); fn pop_mask(&mut self); + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> (u32, u32, Vec); + fn register_bitmap_raw(&mut self, width: u32, height: u32, rgba: Vec) -> BitmapHandle; } impl_downcast!(RenderBackend); @@ -146,6 +149,14 @@ impl RenderBackend for NullRenderer { fn activate_mask(&mut self) {} fn deactivate_mask(&mut self) {} fn pop_mask(&mut self) {} + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> (u32, u32, Vec) { + (0, 0, vec![]) + } + fn register_bitmap_raw(&mut self, width: u32, height: u32, rgba: Vec) -> BitmapHandle + { + BitmapHandle(0) + } } /// The format of image data in a DefineBitsJpeg2/3 tag. @@ -160,7 +171,7 @@ pub enum JpegTagFormat { } /// Decoded bitmap data from an SWF tag. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Bitmap { pub width: u32, pub height: u32, @@ -169,7 +180,7 @@ pub struct Bitmap { /// Decoded bitmap data from an SWF tag. /// The image data will have pre-multiplied alpha. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum BitmapFormat { Rgb(Vec), Rgba(Vec), diff --git a/core/src/character.rs b/core/src/character.rs index c20d04004..a5e0e32e5 100644 --- a/core/src/character.rs +++ b/core/src/character.rs @@ -2,7 +2,7 @@ use crate::backend::audio::SoundHandle; use crate::display_object::{Bitmap, Button, EditText, Graphic, MorphShape, MovieClip, Text}; use crate::font::Font; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Character<'gc> { EditText(EditText<'gc>), Graphic(Graphic<'gc>), diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 92754e323..6ed2b98c4 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -127,7 +127,8 @@ impl SwfMovie { /// Get the URL this SWF was fetched from. pub fn url(&self) -> Option<&str> { - self.url.as_deref() + // self.url.as_deref() + Some("ww.king.com") } pub fn parameters(&self) -> &PropertyMap { diff --git a/core/tests/swfs/avm1/bitmap_data/output.txt b/core/tests/swfs/avm1/bitmap_data/output.txt index e83924c44..9aef09f6d 100644 --- a/core/tests/swfs/avm1/bitmap_data/output.txt +++ b/core/tests/swfs/avm1/bitmap_data/output.txt @@ -66,13 +66,76 @@ undefined -f10001 // fillRect(src.rectangle, 0xFF121212 // src.getPixel32(0, 0) --ededee -// src.getPixel32(1, 0) --ededee +-15592942 // src.getPixel32(0, 1) --ededee +-15592942 +// src.getPixel32(1, 0) +-15592942 // src.getPixel32(1, 1) --ededee +-15592942 +// fillRect(src.rectangle, 0xFF121212 +// src.getPixel32(0, 0) +-15592942 +// src.getPixel32(0, 1) +-15592942 +// src.getPixel32(1, 0) +-15592942 +// src.getPixel32(1, 1) +-15592942 +// fillRect({x: 1, y: 1, width: 1, height: 1}, 0xFF242424 +// src.getPixel32(0, 0) +-14408668 +// src.getPixel32(0, 1) +-14408668 +// src.getPixel32(1, 0) +-14408668 +// src.getPixel32(1, 1) +-14408668 +// fillRect(src.rectangle +// src.getPixel32(0, 0) +-14408668 +// src.getPixel32(0, 1) +-14408668 +// src.getPixel32(1, 0) +-14408668 +// src.getPixel32(1, 1) +-14408668 +// fillRect() +// src.getPixel32(0, 0) +-14408668 +// src.getPixel32(0, 1) +-14408668 +// src.getPixel32(1, 0) +-14408668 +// src.getPixel32(1, 1) +-14408668 +// fillRect(undefined, 0xFF121212 +// src.getPixel32(0, 0) +-14408668 +// src.getPixel32(0, 1) +-14408668 +// src.getPixel32(1, 0) +-14408668 +// src.getPixel32(1, 1) +-14408668 +// fillRect({x: -10, y: 1, width: 1, height: 1}, 0xF3243434 +// src.getPixel32(0, 0) +-215731148 +// src.getPixel32(0, 1) +-215731148 +// src.getPixel32(1, 0) +-215731148 +// src.getPixel32(1, 1) +-215731148 +// fillRect({x: 0, y: 1, width: 100, height: 100}, 0xFF424242 +// src.getPixel32(0, 0) +-12434878 +// src.getPixel32(0, 1) +-12434878 +// src.getPixel32(1, 0) +-12434878 +// src.getPixel32(1, 1) +-12434878 // src.getPixel32(0, 0) alpha = 0 0 // src.getPixel32(0, 0) alpha = 1 @@ -145,8 +208,54 @@ fffffff -1 // disposed.rectangle -1 +// disposed.applyFilter() +-1 +// disposed.clone() +-1 +// disposed.colorTransform() +-1 +// disposed.copyChannel() +-1 +// disposed.copyPixels() +-1 +// disposed.dispose() +-1 +// disposed.draw() +-1 +// disposed.fillRect() +undefined +// disposed.floodFill() +-1 +// disposed.generateFilterRect() +-1 +// disposed.getColorBoundsRect() +-1 // disposed.getPixel(0, 0) -1 +// disposed.getPixel32(0, 0) +-1 +// disposed.hitTest() +-1 +// disposed.loadBitmap() +undefined +// disposed.merge() +-1 +// disposed.noise() +-1 +// disposed.paletteMap() +-1 +// disposed.perlinNoise() +-1 +// disposed.pixelDissolve() +-1 +// disposed.scroll() +-1 +// disposed.setPixel(0, 0, 0) +-1 +// disposed.setPixel32(0, 0, 0) +-1 +// disposed.threshold() +-1 // flood_filled.getPixel32(0, 0) -545455 // flood_filled.getPixel32(1, 0) @@ -163,3 +272,527 @@ fffffff 0 // getPixel (10, 10) 0 +// setPixel32 (noargs) +// getPixel32(0, 0) +-1 +// setPixel32 (0) +// getPixel32(0, 0) +-1 +// setPixel32 (0, 0) +// getPixel32(0, 0) +-1 +// setPixel32 (0, 0, 0xFF00FF00) +// getPixel32(0, 0) +-16711936 +// setPixel (noargs) +// getPixel32(0, 0) +-1 +// setPixel (0) +// getPixel32(0, 0) +-1 +// setPixel (0, 0) +// getPixel32(0, 0) +-1 +// setPixel (0, 0, 0xFF00FF00) +// getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 +// src.getPixel32(0, 0) +-16711936 +// src.getPixel32(0, 1) +0 +// src.getPixel32(0, 2) +0 +// src.getPixel32(0, 3) +0 +// src.getPixel32(0, 4) +0 +// src.getPixel32(1, 0) +0 +// src.getPixel32(1, 1) +0 +// src.getPixel32(1, 2) +0 +// src.getPixel32(1, 3) +0 +// src.getPixel32(1, 4) +0 +// src.getPixel32(2, 0) +0 +// src.getPixel32(2, 1) +0 +// src.getPixel32(2, 2) +0 +// src.getPixel32(2, 3) +0 +// src.getPixel32(2, 4) +0 +// src.getPixel32(3, 0) +0 +// src.getPixel32(3, 1) +0 +// src.getPixel32(3, 2) +0 +// src.getPixel32(3, 3) +0 +// src.getPixel32(3, 4) +0 +// src.getPixel32(4, 0) +0 +// src.getPixel32(4, 1) +0 +// src.getPixel32(4, 2) +0 +// src.getPixel32(4, 3) +0 +// src.getPixel32(4, 4) +0 diff --git a/core/tests/swfs/avm1/bitmap_data/test.fla b/core/tests/swfs/avm1/bitmap_data/test.fla index 8fdd8e57a3674ffe09a83fae7ec27c674dc5c8e3..d9310bbb33078c0868d7d3b6abff23449a5166b6 100644 GIT binary patch delta 3449 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 delta 2815 zcmV_`-;tDTy--dZ0qd9A zb!_qOd3d}#o=CCN^UIIt)L*Yi!qNRIPS~q^7R`($=jkiUx_`U7Gw=E)%@&qx+m7|e z#}C*3lC1E{C}A8YJ~0$(i%6J>13D+BPgmA`7z^CkGLXN{GU($bT9G(P$jop68OFbn zj72n=8LsJqQbsVR+504Vr_od4JSz7`9Ji3IqMoySTXUF1B%; z89ZCVAR=~6V&ZcWt*sz_Q869q;X>#FM+z z&!Ut+4x(^5V*10Ucjsp}XP=@~N;8f^kwj(?F%dfRZNyhNJ;xj$AMJjdF~xzp>=~E^ zq4*dluYZq^j*l!0%`uC7a4O86Z0$p?i&jnp`U)i!;cUSy6p#?FW6lKRE6&hdvY{EI z;2qla)N^ceaEjDlnJ=fvz7O-4=#gjGCp|-SD^bR-Q8nn4%)2Py%Z93I)&iC!S}b`J zq1v^O3{nOsiHP51C(L2#{KtsY(EPt}G$x;$3-|L5odN|~e=!|g0DfrSp zkz5s>nP@-M9S<<)N?Pij-(=D}7dep7Tag^iB&eE@tCjqbw`kQGp{}&+A&p>H1t+hc zTV=cF(c-o(e<$K~it?`qGq@nC1iNT#Q`Tg2Rn!X9WOYqe8monF2;&UF9SJQ6w;<|Q z(SJROdwut$ntizxr9zDEBFL{&u7Zez1^1-oMu?EfKZMA>ZOG>tdfg|zBs&0y_Xln}*OvPktvwZ`e)$T9v_X_FVN!1QIqVeq%83m1RFg34*Pg&dfU;l1*A(?#_Wu8PU*FD>xb!sTkk%3;INI;$)kc0oK2_PQR zQKps_<;j@0=XW1<5gg*G0)JEKp;SH!asA0dRnP)zJawo^T44HTVLEXOf$o7^Q`iD- zT@$uwl(RY$F`*#}*NM+|i^VXeG`JP1qzg{_{3_#2c>$}pxODeFdn?}6fmA?yO5k3- zJ@uvyS1*ct^`5|AFOz%qo@W1}_}ePz6Y5lRBZ7Jfl}|WZDtP_r@qZ4tikMuY1G*L5 zw*5i9mtf(`eE3CGuRwE!HoPOe-g-|g#6tS{^P8!4$RWnmI{e^cN@5}o0h#si@r+fR z&+I+yvG$(!ND_IB#JQdgjr1*pb@6aWGU2mr^bL{X`K9Lwbb0027@000#LlQ0+;f9+V=ZrVT; zedjAi&SPuOj5lHnJMx0nJh+V#r1o{NhhW89JtjbYtq=XA{zAuQ2?mmMiK?h#WDvf- z_Z;S&naha5#iAt99j(Eb-X^&v{P^}=07+~KpS2K$rAMd9hlh&ZJbWBQ4|kCf3m0~8 zurL={nbDenMV98wTv!5$e~U3S`R67K1<v?76f0ig`QaeW}Dl)hr zCBzbfTjU0uXPaI{NnKQ|Pt-)Pu;>es7RYq!?lx3Pkq+x;gdA#;? zl9B;@OnU9_%jF2e=T}me!DE%I9^Ti_SF+Kvr0|-uVpc_Tc+V9$+h3PqXkO;5CV50J zeVYfQB#F&H#WM1S$Q`2o(8bsYj3Cgl>U|j#VE>4p*ALO)nh1k2EE4HF>px+`fYxL&fgU(6H_)Cole; z7cV!n|KE#MwA;Ei?QpNv9sg}xMfpjKAM)V#ly_{7uRl&CCz2D%iR46bA~}(~DU#k` z`#%xQV@sF|7xquHP7N6e1j!-$y_0JeCkX=p2LK8HF8~j-n-)9)3RvZ~T_Fbm04pSu z;TJO;sec^H=NIQ33z}xD)^Y005n+O}hX9 diff --git a/core/tests/swfs/avm1/bitmap_data/test.swf b/core/tests/swfs/avm1/bitmap_data/test.swf index 900f632256c1f7bcca125e6cd5853ad97d5d6958..55cd4321f22a76fa3c8c6d6f299586f9b6e6fa0f 100644 GIT binary patch literal 2455 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 diff --git a/render/canvas/src/lib.rs b/render/canvas/src/lib.rs index d532b45a8..45daa0596 100644 --- a/render/canvas/src/lib.rs +++ b/render/canvas/src/lib.rs @@ -32,6 +32,7 @@ pub struct WebCanvasRenderBackend { use_color_transform_hack: bool, pixelated_property_value: &'static str, deactivating_mask: bool, + bitmap_registry: HashMap, } /// Canvas-drawable shape data extracted from an SWF file. @@ -223,6 +224,7 @@ impl WebCanvasRenderBackend { } else { "pixelated" }, + bitmap_registry: HashMap::new(), }; Ok(renderer) } @@ -374,6 +376,13 @@ impl WebCanvasRenderBackend { id: CharacterId, data: &[u8], ) -> Result { + //TODO: + self.bitmap_registry.insert(id, Bitmap { + width: 123, + height: 123, + data: BitmapFormat::Rgb(vec![]) + }); + let data = ruffle_core::backend::render::remove_invalid_jpeg_data(data); let mut decoder = jpeg_decoder::Decoder::new(&data[..]); decoder.read_info()?; @@ -403,6 +412,8 @@ impl WebCanvasRenderBackend { id: CharacterId, bitmap: Bitmap, ) -> Result { + self.bitmap_registry.insert(id, bitmap.clone()); + let (width, height) = (bitmap.width, bitmap.height); let png = Self::bitmap_to_png_data_uri(bitmap)?; @@ -522,6 +533,9 @@ impl RenderBackend for WebCanvasRenderBackend { ) -> Result { let bitmap = ruffle_core::backend::render::decode_define_bits_lossless(swf_tag)?; + self.bitmap_registry.insert(swf_tag.id, bitmap.clone()); + + let png = Self::bitmap_to_png_data_uri(bitmap)?; let image = HtmlImageElement::new().unwrap(); @@ -746,6 +760,51 @@ impl RenderBackend for WebCanvasRenderBackend { .draw_image_with_html_canvas_element(&maskee_canvas, 0.0, 0.0) .unwrap(); } + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> (u32, u32, Vec) { + log::error!("Get bitmap pixels canvas {:?}", bitmap); + + if let Some((id, _texture)) = self.id_to_bitmap.iter().find(|(_k, v)| v.0 == bitmap.0) { + if let Some(bitmap) = self.bitmap_registry.get(id) { + log::error!("Found bitmap = {:?}", bitmap); + let data = match &bitmap.data { + BitmapFormat::Rgb(x) => { + x.chunks_exact(3).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + (0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + BitmapFormat::Rgba(x) => { + x.chunks_exact(4).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + //TODO: check this order, assuming because rgb_a + let a = chunk[3]; + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + }; + (bitmap.width, bitmap.height, data) + } else { + log::error!("Failed to find bitmap {} in registry", id); + (0, 0, vec![0]) + } + } else { + log::error!("Failed to find bitmap {:?} in id_to_bitmap", bitmap); + (0, 0, vec![1]) + } + } + + fn register_bitmap_raw(&mut self, width: u32, height: u32, rgba: Vec) -> BitmapHandle { + self.register_bitmap_raw(0 as CharacterId, Bitmap { + width, + height, + data: BitmapFormat::Rgba(rgba) + }).unwrap().handle + } } #[allow(clippy::cognitive_complexity)] diff --git a/render/webgl/src/lib.rs b/render/webgl/src/lib.rs index a02bdcda4..1888a47c9 100644 --- a/render/webgl/src/lib.rs +++ b/render/webgl/src/lib.rs @@ -13,6 +13,7 @@ use web_sys::{ WebGlFramebuffer, WebGlProgram, WebGlRenderbuffer, WebGlRenderingContext as Gl, WebGlShader, WebGlTexture, WebGlUniformLocation, WebGlVertexArrayObject, WebglDebugRendererInfo, }; +use std::collections::HashMap; type Error = Box; @@ -70,6 +71,8 @@ pub struct WebGlRenderBackend { view_width: i32, view_height: i32, view_matrix: [[f32; 4]; 4], + + bitmap_registry: HashMap, } const MAX_GRADIENT_COLORS: usize = 15; @@ -208,6 +211,7 @@ impl WebGlRenderBackend { blend_func: (Gl::SRC_ALPHA, Gl::ONE_MINUS_SRC_ALPHA), mult_color: None, add_color: None, + bitmap_registry: HashMap::new(), }; let quad_mesh = renderer.build_quad_mesh()?; @@ -675,6 +679,8 @@ impl WebGlRenderBackend { id: swf::CharacterId, bitmap: Bitmap, ) -> Result { + self.bitmap_registry.insert(id, bitmap.clone()); + let texture = self.gl.create_texture().unwrap(); self.gl.bind_texture(Gl::TEXTURE_2D, Some(&texture)); match bitmap.data { @@ -1266,6 +1272,46 @@ impl RenderBackend for WebGlRenderBackend { }; self.mask_state_dirty = true; } + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> (u32, u32, Vec) { + println!("Get bitmap pixels webgl {:?}", bitmap); + if let Some((id, _texture)) = self.textures.get(bitmap.0) { + if let Some(bitmap) = self.bitmap_registry.get(id) { + let data = match &bitmap.data { + BitmapFormat::Rgb(x) => { + x.chunks_exact(3).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + (0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + BitmapFormat::Rgba(x) => { + x.chunks_exact(4).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + //TODO: check this order, assuming because rgb_a + let a = chunk[3]; + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + }; + (bitmap.width, bitmap.height, data) + } else { + println!("Failed 1"); + (0, 0, vec![]) + } + } else { + println!("Failed 2"); + (0, 0, vec![]) + } + } + + fn register_bitmap_raw(&mut self, width: u32, height: u32, rgba: Vec) -> BitmapHandle { + //TODO: + unimplemented!() + } } struct Texture { diff --git a/render/wgpu/src/lib.rs b/render/wgpu/src/lib.rs index d48d3cfc0..2e218fa67 100644 --- a/render/wgpu/src/lib.rs +++ b/render/wgpu/src/lib.rs @@ -45,6 +45,7 @@ use crate::globals::Globals; use ruffle_core::swf::{Matrix, Twips}; use std::path::Path; pub use wgpu; +use std::collections::HashMap; pub struct Descriptors { pub device: wgpu::Device, @@ -95,6 +96,7 @@ pub struct WgpuRenderBackend { quad_vbo: wgpu::Buffer, quad_ibo: wgpu::Buffer, quad_tex_transforms: wgpu::Buffer, + bitmap_registry: HashMap, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)] @@ -269,6 +271,7 @@ impl WgpuRenderBackend { quad_vbo, quad_ibo, quad_tex_transforms, + bitmap_registry: HashMap::new(), }) } @@ -684,6 +687,8 @@ impl WgpuRenderBackend { bitmap: Bitmap, debug_str: &str, ) -> BitmapInfo { + self.bitmap_registry.insert(id, bitmap.clone()); + let extent = wgpu::Extent3d { width: bitmap.width, height: bitmap.height, @@ -1489,6 +1494,38 @@ impl RenderBackend for WgpuRenderBackend { MaskState::DrawMaskedContent }; } + + fn get_bitmap_pixels(&mut self, bitmap: BitmapHandle) -> (u32, u32, Vec) { + if let Some((id, _texture)) = self.textures.get(bitmap.0) { + if let Some(bitmap) = self.bitmap_registry.get(id) { + let data = match &bitmap.data { + BitmapFormat::Rgb(x) => { + x.chunks_exact(3).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + (0xFF << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + BitmapFormat::Rgba(x) => { + x.chunks_exact(4).map(|chunk| { + let r = chunk[0]; + let g = chunk[1]; + let b = chunk[2]; + //TODO: check this order, assuming because rgb_a + let a = chunk[3]; + ((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) + }).collect() + } + }; + (bitmap.width, bitmap.height, data) + } else { + (0, 0, vec![]) + } + } else { + (0, 0, vec![]) + } + } } fn create_quad_buffers(device: &wgpu::Device) -> (wgpu::Buffer, wgpu::Buffer, wgpu::Buffer) {