core: Implement get/set pixel(32) functions

This commit is contained in:
CUB3D 2020-10-06 00:53:43 +01:00 committed by Mike Welsh
parent e45e942a1f
commit 3afd20063f
7 changed files with 416 additions and 27 deletions

View File

@ -589,7 +589,7 @@ pub fn create_globals<'gc>(
EnumSet::empty(), EnumSet::empty(),
); );
let bitmap_data_proto = bitmap_data::create_proto(gc_context, object_proto, Some(function_proto)); let bitmap_data_proto = bitmap_data::create_proto(gc_context, object_proto, function_proto);
let bitmap_data = bitmap_data::create_bitmap_data_object(gc_context, bitmap_data_proto, Some(function_proto)); let bitmap_data = bitmap_data::create_bitmap_data_object(gc_context, bitmap_data_proto, Some(function_proto));
display.define_value( display.define_value(

View File

@ -2,11 +2,10 @@
use crate::avm1::activation::Activation; use crate::avm1::activation::Activation;
use crate::avm1::error::Error; use crate::avm1::error::Error;
use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::avm1::{Object, TObject, Value};
use enumset::EnumSet; use enumset::EnumSet;
use gc_arena::MutationContext; use gc_arena::MutationContext;
use crate::avm1::function::{FunctionObject, Executable}; use crate::avm1::function::{FunctionObject, Executable};
use crate::display_object::TDisplayObject;
use crate::avm1::object::bitmap_data::BitmapDataObject; use crate::avm1::object::bitmap_data::BitmapDataObject;
pub fn constructor<'gc>( pub fn constructor<'gc>(
@ -14,7 +13,9 @@ pub fn constructor<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
//TODO: check default for this and height
//TODO: if either width or height is missing then the constructor should fail
let width = args.get(0) let width = args.get(0)
.unwrap_or(&Value::Number(0.0)) .unwrap_or(&Value::Number(0.0))
.coerce_to_u32(activation)?; .coerce_to_u32(activation)?;
@ -23,7 +24,12 @@ pub fn constructor<'gc>(
.unwrap_or(&Value::Number(0.0)) .unwrap_or(&Value::Number(0.0))
.coerce_to_u32(activation)?; .coerce_to_u32(activation)?;
let transparent = args.get(2) if width > 2880 || height > 2880 || width <= 0 || height <= 0{
log::warn!("Invalid BitmapData size {}x{}", width, height);
return Ok(Value::Undefined)
}
let transparency = args.get(2)
.unwrap_or(&Value::Bool(true)) .unwrap_or(&Value::Bool(true))
.as_bool(activation.current_swf_version()); .as_bool(activation.current_swf_version());
@ -33,25 +39,20 @@ pub fn constructor<'gc>(
// 0xFFFFFFFF as f64; // 0xFFFFFFFF as f64;
let fill_color = args.get(3) let fill_color = args.get(3)
.unwrap_or(&Value::Number(4294967295_f64)) .unwrap_or(&Value::Number(4294967295_f64))
.coerce_to_u32(activation)?; .coerce_to_i32(activation)?;
//TODO: respect size limits
log::warn!("BitmapData constructor w: {}, h: {}, t: {}, fc:{}", width, height, transparent, fill_color);
//TODO: respect transparency (can we maybe use less memory when disabled?) //TODO: respect transparency (can we maybe use less memory when disabled?)
let bitmap_data = this.as_bitmap_data_object().unwrap(); let bitmap_data = this.as_bitmap_data_object().unwrap();
bitmap_data.init_pixels(activation.context.gc_context, width, height, fill_color); bitmap_data.init_pixels(activation.context.gc_context, width, height, fill_color);
bitmap_data.set_transparency(activation.context.gc_context, transparency);
Ok(Value::Undefined) Ok(Value::Undefined)
} }
pub fn load_bitmap<'gc>( pub fn load_bitmap<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>, _this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
//TODO: how does this handle no args //TODO: how does this handle no args
@ -75,14 +76,238 @@ pub fn load_bitmap<'gc>(
Ok(new_bitmap.into()) Ok(new_bitmap.into())
} }
pub fn get_height<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(this.as_bitmap_data_object().unwrap().get_height().into())
}
pub fn get_width<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(this.as_bitmap_data_object().unwrap().get_width().into())
}
pub fn get_transparent<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok(this.as_bitmap_data_object().unwrap().get_transparency().into())
}
pub fn get_rectangle<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let bitmap_data = this.as_bitmap_data_object().unwrap();
let proto = activation.context.system_prototypes.rectangle_constructor;
let rect = proto.construct(activation, &[0.into(), 0.into(), bitmap_data.get_width().into(), bitmap_data.get_height().into()])?;
Ok(rect.into())
}
//TODO: out of bounds / missing args / neg args
pub fn get_pixel<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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
Ok(bitmap_data.get_pixel(x, y).unwrap_or(0).into())
}
//TODO: out of bounds / missing args / neg args
pub fn set_pixel<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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);
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<Value<'gc>, 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);
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<Value<'gc>, 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 = bitmap_data.get_pixel32(x, y).unwrap_or(0);
Ok(x.into())
}
//TODO: missing args / out of bounds
pub fn copy_channel<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let source_bitmap = args.get(0)
.unwrap_or(&Value::Undefined)
//TODO: unwrap
.coerce_to_object(activation);
let source_rect = args.get(1)
.unwrap_or(&Value::Undefined)
//TODO: unwrap
.coerce_to_object(activation);
let dest_point = args.get(2)
.unwrap_or(&Value::Undefined)
//TODO: unwrap
.coerce_to_object(activation);
let source_channel = args.get(3)
.unwrap_or(&Value::Undefined)
.coerce_to_i32(activation)?;
let dest_channel = args.get(4)
.unwrap_or(&Value::Undefined)
.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, source_channel as u8, dest_channel as u8);
}
Ok(Value::Undefined)
}
pub fn create_proto<'gc>( pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>, gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>, proto: Object<'gc>,
fn_proto: Option<Object<'gc>>, fn_proto: Object<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let mut object = BitmapDataObject::empty_object(gc_context, Some(proto)); let bitmap_data_object = BitmapDataObject::empty_object(gc_context, Some(proto));
let mut object = bitmap_data_object.as_script_object().unwrap();
object.into() object.add_property(
gc_context,
"height",
FunctionObject::function(
gc_context,
Executable::Native(get_height),
Some(fn_proto),
fn_proto,
),
None,
EnumSet::empty(),
);
object.add_property(
gc_context,
"width",
FunctionObject::function(
gc_context,
Executable::Native(get_width),
Some(fn_proto),
fn_proto,
),
None,
EnumSet::empty(),
);
object.add_property(
gc_context,
"transparent",
FunctionObject::function(
gc_context,
Executable::Native(get_transparent),
Some(fn_proto),
fn_proto,
),
None,
EnumSet::empty(),
);
object.add_property(
gc_context,
"rectangle",
FunctionObject::function(
gc_context,
Executable::Native(get_rectangle),
Some(fn_proto),
fn_proto,
),
None,
EnumSet::empty(),
);
object.force_set_function("getPixel", get_pixel, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function("getPixel32", get_pixel32, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function("setPixel", set_pixel, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function("setPixel32", set_pixel32, gc_context, EnumSet::empty(), Some(fn_proto));
object.force_set_function("copyChannel", copy_channel, gc_context, EnumSet::empty(), Some(fn_proto));
bitmap_data_object.into()
} }
pub fn create_bitmap_data_object<'gc>( pub fn create_bitmap_data_object<'gc>(

View File

@ -6,6 +6,7 @@ use gc_arena::{Collect, GcCell, MutationContext};
use crate::avm1::activation::Activation; use crate::avm1::activation::Activation;
use std::fmt; use std::fmt;
use swf::Rectangle;
/// A BitmapData /// A BitmapData
#[derive(Clone, Copy, Collect)] #[derive(Clone, Copy, Collect)]
@ -19,9 +20,11 @@ pub struct BitmapDataData<'gc> {
base: ScriptObject<'gc>, base: ScriptObject<'gc>,
/// The pixels in the bitmap, stored as a array of ARGB colour values /// The pixels in the bitmap, stored as a array of ARGB colour values
pixels: Vec<u32>, pixels: Vec<i32>,
//todO: track width and height width: u32,
height: u32,
transparency: bool,
} }
impl fmt::Debug for BitmapDataObject<'_> { impl fmt::Debug for BitmapDataObject<'_> {
@ -29,50 +32,140 @@ impl fmt::Debug for BitmapDataObject<'_> {
let this = self.0.read(); let this = self.0.read();
f.debug_struct("BitmapData") f.debug_struct("BitmapData")
.field("pixels", &this.pixels) .field("pixels", &this.pixels)
.field("width", &this.width)
.field("height", &this.height)
.field("transparency", &this.transparency)
.finish() .finish()
} }
} }
impl<'gc> BitmapDataObject<'gc> { impl<'gc> BitmapDataObject<'gc> {
add_field_accessors!(
[set_transparency, get_transparency, transparency, bool],
);
pub fn empty_object(gc_context: MutationContext<'gc, '_>, proto: Option<Object<'gc>>) -> Self { pub fn empty_object(gc_context: MutationContext<'gc, '_>, proto: Option<Object<'gc>>) -> Self {
BitmapDataObject(GcCell::allocate( BitmapDataObject(GcCell::allocate(
gc_context, gc_context,
BitmapDataData { BitmapDataData {
base: ScriptObject::object(gc_context, proto), base: ScriptObject::object(gc_context, proto),
pixels: Vec::new(), pixels: Vec::new(),
width: 0,
height: 0,
transparency: true,
}, },
)) ))
} }
pub fn init_pixels(&self, gc_context: MutationContext<'gc, '_>, width: u32, height: u32, fill_color: u32) { pub fn get_pixels(&self) -> Vec<i32> {
self.0.read().pixels.clone()
}
pub fn init_pixels(&self, gc_context: MutationContext<'gc, '_>, width: u32, height: u32, fill_color: i32) {
self.0.write(gc_context).width = width;
self.0.write(gc_context).height = height;
self.0.write(gc_context).pixels = vec![fill_color; (width * height) as usize] self.0.write(gc_context).pixels = vec![fill_color; (width * height) as usize]
} }
pub fn get_pixel32(&self, x: u32, y: u32) -> Option<u32> { pub fn get_pixel32(&self, x: u32, y: u32) -> Option<i32> {
//TODO: look into the effects of pre-multiplication //TODO: look into the effects of pre-multiplication
self.0.read().pixels.get((x * y) as usize).cloned() self.0.read().pixels.get((x * y) as usize).cloned()
} }
pub fn get_pixel(&self, x: u32, y: u32) -> Option<u32> { pub fn get_pixel(&self, x: u32, y: u32) -> Option<i32> {
self.get_pixel32(x, y).map(|p| p & 0xFFFFFF) self.get_pixel32(x, y).map(|p| p & 0xFFFFFF)
} }
pub fn set_pixel32(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: u32) { pub fn set_pixel32(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: i32) {
let alpha = (color >> 24) & 0xFF;
// Internally flash uses pre-multiplied values however that will cause issues with accuracy (assuming they are using fixed point like with other color types)
// so we will just fake it for now, if the alpha is 0 then it will clear out the colors
//tODO: how well does this handle less edge case values eg 0x12345678?
let adjusted_color = if alpha == 0 && self.0.read().transparency {
// Alpha = 0 and transparency is on so clear out rest of color
0
} else {
// If we don't have transparency then force the alpha to 0xFF
//TODO: could we do that earlier to make this cleaner
if self.0.read().transparency {
color
} else {
(color & 0xFFFFFF) | (0xFF << 24)
}
};
//TODO: bounds check //TODO: bounds check
self.0.write(gc_context).pixels[(x * y) as usize] = color self.0.write(gc_context).pixels[(x * y) as usize] = adjusted_color;
} }
pub fn set_pixel(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: u32) { pub fn set_pixel(&self, gc_context: MutationContext<'gc, '_>, x: u32, y: u32, color: i32) {
let current_alpha = self.get_pixel32(x, y).unwrap_or(0); let current_alpha = (self.get_pixel32(x, y).unwrap_or(0) >> 24) & 0xFF;
//todo: check this shift //todo: check this shift
self.set_pixel32(gc_context, x, y, color | (current_alpha << 24)) self.set_pixel32(gc_context, x, y, color | (current_alpha << 24))
} }
pub fn dispose(&self, gc_context: MutationContext<'gc, '_>) { pub fn dispose(&self, gc_context: MutationContext<'gc, '_>) {
self.0.write(gc_context).pixels.clear() self.0.write(gc_context).pixels.clear();
self.0.write(gc_context).width = 0;
self.0.write(gc_context).height = 0;
}
pub fn copy_channel(&self, gc_context: MutationContext<'gc, '_>, source: BitmapDataObject, /*rect: Rectangle, pnt: Point,*/ source_channel: u8, dest_channel: u8) {
let other_pixels = source.get_pixels();
let mut new_self_pixels = Vec::new();//(self.0.read().pixels.len());
let width = self.get_width();
let height = self.get_height();
for x in 0..width {
for y in 0..height {
// TODO: if rect.contains((x, y)) and offset by pnt
//TODO: how does this handle out of bounds
let original_color = self.0.read().pixels[(y * width + x) as usize];
//TODO: does this calculation work if they are different sizes
let source_color = other_pixels[(y * width + x) as usize];
//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)
let channel_shift = match source_channel {
8 => 24,
4 => 16,
2 => 8,
1 => 0,
//TODO:
_ => {panic!()}
};
let source_part = (source_color >> channel_shift) & 0xFF;
let dest_channel_shift = match dest_channel {
8 => 24,
4 => 16,
2 => 8,
1 => 0,
//TODO:
_ => {panic!()}
};
let original_color = if dest_channel_shift == 0 {
original_color & (4278255615_u32 as i32)//& 0xFF00FFFF
} else {
original_color
};
let new_dest_color = original_color | ((source_part << dest_channel_shift) & 0xFF);
//new_self_pixels.insert((y * width + x) as usize, new_dest_color);
new_self_pixels.push(new_dest_color);
}
}
self.0.write(gc_context).pixels = new_self_pixels;
} }
//TODO: probably wont handle the edge cases correctly, also may have differences if we dont use premultiplied alpha in our impl (wonder if premultipliing only for functions that need it would be benificial in any way) //TODO: probably wont handle the edge cases correctly, also may have differences if we dont use premultiplied alpha in our impl (wonder if premultipliing only for functions that need it would be benificial in any way)
pub fn threshold(&self, mask: u32, threshold: u32, new_color: u32, copy_source: bool) -> Vec<u32> { pub fn threshold(&self, mask: i32, threshold: i32, new_color: i32, copy_source: bool) -> Vec<i32> {
self.0.read().pixels.iter().cloned().map(|p| { self.0.read().pixels.iter().cloned().map(|p| {
//TODO: support other operations //TODO: support other operations
if (p & mask) == (threshold & mask) { if (p & mask) == (threshold & mask) {
@ -87,6 +180,13 @@ impl<'gc> BitmapDataObject<'gc> {
}) })
.collect() .collect()
} }
pub fn get_width(&self) -> u32 {
self.0.read().width
}
pub fn get_height(&self) -> u32 {
self.0.read().height
}
} }
impl<'gc> TObject<'gc> for BitmapDataObject<'gc> { impl<'gc> TObject<'gc> for BitmapDataObject<'gc> {

View File

@ -289,6 +289,7 @@ swf_tests! {
(date_set_year, "avm1/date/setYear", 1), (date_set_year, "avm1/date/setYear", 1),
(this_scoping, "avm1/this_scoping", 1), (this_scoping, "avm1/this_scoping", 1),
(bevel_filter, "avm1/bevel_filter", 1), (bevel_filter, "avm1/bevel_filter", 1),
(bitmap_data, "avm1/bitmap_data", 1),
(as3_hello_world, "avm2/hello_world", 1), (as3_hello_world, "avm2/hello_world", 1),
(as3_function_call, "avm2/function_call", 1), (as3_function_call, "avm2/function_call", 1),
(as3_function_call_via_call, "avm2/function_call_via_call", 1), (as3_function_call_via_call, "avm2/function_call_via_call", 1),

View File

@ -0,0 +1,63 @@
// bitmap
[object Object]
// bitmap.width
10
// bitmap.height
10
// bitmap.rectangle
(x=0, y=0, w=10, h=10)
// bitmap.transparent
false
// bitmap.width (after set to 100)
10
// bitmap.height (after set to 100)
10
// get/set pixel with transparent = true
// getPixel32(0, 0) after setPixel32(0, 0, 0xffffffff)
-1
// getPixel(0, 0)
16777215
// getPixel32(0, 0) after setPixel32(0, 0, 0x80ffffff)
-2130706433
// getPixel(0, 0)
16777215
// getPixel32(0, 0) after setPixel(0, 0, 0xBBBBBB)
-2135180357
// getPixel(0, 0)
12303291
// getPixel32(0, 0) after setPixel32(0, 0, 0x00ffffff)
0
// getPixel(0, 0)
0
// get/set pixel with transparent = false
// getPixel32(0, 0) after setPixel32(0, 0, 0xffffffff)
-1
// getPixel(0, 0)
16777215
// getPixel32(0, 0) after setPixel32(0, 0, 0x80ffffff)
-1
// getPixel(0, 0)
16777215
// getPixel32(0, 0) after setPixel(0, 0, 0xBBBBBB)
-4473925
// getPixel(0, 0)
12303291
// getPixel32(0, 0) after setPixel32(0, 0, 0x00ffffff)
-1
// getPixel(0, 0)
16777215
// bitmap_clone
[object Object]
// bitmap_clone == bitmap
false
// clone2
undefined
// dest.copyChannel(src, src.rectangle, pnt, 1, 1)
// dest.getPixel32(0, 0)
ffffff
// dest.getPixel32(1, 0)
ffff
// dest.getPixel32(0, 1)
ffff
// dest.getPixel32(1, 1)
effff