avm1: Refactor and fix `BitmapData::compare`

This commit is contained in:
relrelb 2021-11-19 11:17:38 +02:00 committed by relrelb
parent 687069ba7d
commit 56b5183262
3 changed files with 110 additions and 79 deletions

View File

@ -1069,62 +1069,61 @@ pub fn compare<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(bitmap_data) = this.as_bitmap_data_object() {
if bitmap_data.disposed() {
return Ok((-2).into());
}
const EQUIVALENT: i32 = 0;
const NOT_BITMAP: i32 = -1;
const BITMAP_DISPOSED: i32 = -2;
const DIFFERENT_WIDTHS: i32 = -3;
const DIFFERENT_HEIGHTS: i32 = -4;
let other_bitmap = args
.get(0)
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation);
let this_bitmap_data = if let Some(bitmap_data) = this.as_bitmap_data_object() {
bitmap_data
} else {
return Ok(NOT_BITMAP.into());
};
if let Some(other_bitmap_data) = other_bitmap.as_bitmap_data_object() {
if other_bitmap_data.disposed() {
return Ok((-2).into());
}
if bitmap_data.bitmap_data().read().width()
!= other_bitmap_data.bitmap_data().read().width()
{
return Ok((-3).into());
}
if bitmap_data.bitmap_data().read().height()
!= other_bitmap_data.bitmap_data().read().height()
{
return Ok((-4).into());
}
if bitmap_data
.bitmap_data()
.read()
.pixels()
.eq(other_bitmap_data.bitmap_data().read().pixels())
{
return Ok((0).into());
}
let new_bitmap_data = BitmapDataObject::empty_object(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.bitmap_data),
);
new_bitmap_data
.as_bitmap_data_object()
.unwrap()
.bitmap_data()
.write(activation.context.gc_context)
.compare(
&bitmap_data.bitmap_data().read(),
&other_bitmap_data.bitmap_data().read(),
);
return Ok(new_bitmap_data.into());
}
return Ok(Value::Undefined);
if this_bitmap_data.disposed() {
// The documentation says that -2 should be returned here, but -1 is actually returned.
return Ok(NOT_BITMAP.into());
}
Ok((-1).into())
let other = args
.get(0)
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation);
let other_bitmap_data = if let Some(other_bitmap_data) = other.as_bitmap_data_object() {
other_bitmap_data
} else {
// The documentation says that -1 should be returned here, but -2 is actually returned.
return Ok(BITMAP_DISPOSED.into());
};
if other_bitmap_data.disposed() {
return Ok(BITMAP_DISPOSED.into());
}
let this_bitmap_data = this_bitmap_data.bitmap_data();
let this_bitmap_data = this_bitmap_data.read();
let other_bitmap_data = other_bitmap_data.bitmap_data();
let other_bitmap_data = other_bitmap_data.read();
if this_bitmap_data.width() != other_bitmap_data.width() {
return Ok(DIFFERENT_WIDTHS.into());
}
if this_bitmap_data.height() != other_bitmap_data.height() {
return Ok(DIFFERENT_HEIGHTS.into());
}
match BitmapData::compare(&this_bitmap_data, &other_bitmap_data) {
Some(bitmap_data) => Ok(BitmapDataObject::with_bitmap_data(
activation.context.gc_context,
Some(activation.context.avm1.prototypes.bitmap_data),
bitmap_data,
)
.into()),
None => Ok(EQUIVALENT.into()),
}
}
pub fn create_proto<'gc>(

View File

@ -36,12 +36,20 @@ impl<'gc> BitmapDataObject<'gc> {
);
pub fn empty_object(gc_context: MutationContext<'gc, '_>, proto: Option<Object<'gc>>) -> Self {
BitmapDataObject(GcCell::allocate(
Self::with_bitmap_data(gc_context, proto, Default::default())
}
pub fn with_bitmap_data(
gc_context: MutationContext<'gc, '_>,
proto: Option<Object<'gc>>,
bitmap_data: BitmapData<'gc>,
) -> Self {
Self(GcCell::allocate(
gc_context,
BitmapDataData {
base: ScriptObject::object(gc_context, proto),
disposed: false,
data: GcCell::allocate(gc_context, BitmapData::default()),
data: GcCell::allocate(gc_context, bitmap_data),
},
))
}

View File

@ -133,7 +133,7 @@ bitflags! {
#[collect(no_drop)]
pub struct BitmapData<'gc> {
/// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
pub pixels: Vec<Color>,
pixels: Vec<Color>,
dirty: bool,
width: u32,
height: u32,
@ -854,32 +854,56 @@ impl<'gc> BitmapData<'gc> {
}
}
pub fn compare(&mut self, bitmap: &Self, other: &Self) {
// Should be replaced with i32::abs_diff once stabilized (https://github.com/rust-lang/rust/issues/89492)
fn abs_diff(a: i32, b: i32) -> i32 {
if a > b {
a.wrapping_sub(b)
} else {
b.wrapping_sub(a)
}
}
/// Compare two BitmapData objects.
/// Returns `None` if the bitmaps are equivalent.
pub fn compare(bitmap: &Self, other: &Self) -> Option<Self> {
// This function expects that the two bitmaps have the same dimensions.
// TODO: Relax this assumption and return a special value instead?
debug_assert_eq!(bitmap.width, other.width);
debug_assert_eq!(bitmap.height, other.height);
for i in 0..self.pixels().len() {
let bitmap_pixel = bitmap.pixels()[i];
let other_pixel = other.pixels()[i];
self.pixels[i] = Color(
if bitmap_pixel.with_alpha(0xffu8) != other_pixel.with_alpha(0xffu8) {
(0xff << 24)
+ (abs_diff(bitmap_pixel.red() as i32, other_pixel.red() as i32) << 16)
+ (abs_diff(bitmap_pixel.green() as i32, other_pixel.green() as i32) << 8)
+ abs_diff(bitmap_pixel.blue() as i32, other_pixel.blue() as i32)
} else if bitmap_pixel.alpha() != other_pixel.alpha() {
(abs_diff(bitmap_pixel.alpha() as i32, other_pixel.alpha() as i32) << 24)
+ 0xffffff
let mut different = false;
let pixels = bitmap
.pixels
.iter()
.zip(&other.pixels)
.map(|(bitmap_pixel, other_pixel)| {
let bitmap_pixel = bitmap_pixel.to_un_multiplied_alpha();
let other_pixel = other_pixel.to_un_multiplied_alpha();
if bitmap_pixel == other_pixel {
Color::argb(0, 0, 0, 0)
} else if bitmap_pixel.with_alpha(0) != other_pixel.with_alpha(0) {
different = true;
Color::argb(
0xff,
bitmap_pixel.red().wrapping_sub(other_pixel.red()),
bitmap_pixel.green().wrapping_sub(other_pixel.green()),
bitmap_pixel.blue().wrapping_sub(other_pixel.blue()),
)
} else {
0
},
)
different = true;
Color::argb(
bitmap_pixel.alpha().wrapping_sub(other_pixel.alpha()),
0xff,
0xff,
0xff,
)
}
})
.collect();
if different {
Some(Self {
pixels,
dirty: false,
width: bitmap.width,
height: bitmap.height,
transparency: true,
bitmap_handle: None,
avm2_object: None,
})
} else {
None
}
}