diff --git a/core/src/bitmap/operations.rs b/core/src/bitmap/operations.rs index 67c7b1fe7d82..0f661a5b48d8 100644 --- a/core/src/bitmap/operations.rs +++ b/core/src/bitmap/operations.rs @@ -1318,37 +1318,23 @@ fn blend_and_transform<'gc>( dest_region: PixelRegion, transform: &ColorTransform, ) { - if !source.transparency() && source.ptr_eq(dest) && source_region == dest_region { - // Copying the same area of opaque self to self, noop - return; - } if source.ptr_eq(dest) { let dest = dest.sync(); let mut write = dest.write(context.gc_context); - if write.transparency() { - for y in 0..dest_region.height() { - for x in 0..dest_region.width() { - let mut color = - write.get_pixel32_raw(source_region.x_min + x, source_region.y_min + y); - color = write - .get_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y) - .blend_over(&color); - color = - Color::from(transform * swf::Color::from(color.to_un_multiplied_alpha())) - .to_premultiplied_alpha(true); - write.set_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y, color); - } - } - } else { - // Can't blend if copying from opaque - for y in 0..dest_region.height() { - for x in 0..dest_region.width() { - let mut color = - write.get_pixel32_raw(source_region.x_min + x, source_region.y_min + y); - color = Color::from(transform * swf::Color::from(color)).with_alpha(255); - write.set_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y, color); + for y in 0..dest_region.height() { + for x in 0..dest_region.width() { + let mut color = + write.get_pixel32_raw(source_region.x_min + x, source_region.y_min + y); + color = Color::from(transform * swf::Color::from(color.to_un_multiplied_alpha())) + .to_premultiplied_alpha(true); + color = write + .get_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y) + .blend_over(&color); + if !write.transparency() { + color = color.with_alpha(255); } + write.set_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y, color); } } @@ -1363,10 +1349,10 @@ fn blend_and_transform<'gc>( for x in 0..dest_region.width() { let mut color = source_read.get_pixel32_raw(source_region.x_min + x, source_region.y_min + y); + color = Color::from(transform * swf::Color::from(color)); color = dest_write .get_pixel32_raw(dest_region.x_min + x, dest_region.y_min + y) .blend_over(&color); - color = Color::from(transform * swf::Color::from(color)); if opaque { color = color.with_alpha(255); } @@ -1431,7 +1417,7 @@ pub fn draw<'gc>( &mut source_region, ); - if transform.color_transform == ColorTransform::default() { + if transform.color_transform != ColorTransform::default() { blend_and_transform( context, *source, diff --git a/render/src/bitmap.rs b/render/src/bitmap.rs index f10bf4ba2a39..ca5cc76f34bf 100644 --- a/render/src/bitmap.rs +++ b/render/src/bitmap.rs @@ -247,6 +247,43 @@ impl BitmapFormat { } } +#[inline] +fn intersection_same_coordinate_system( + (r1_x_min, r1_y_min, r1_x_max, r1_y_max): (i32, i32, i32, i32), + (r2_x_min, r2_y_min, r2_x_max, r2_y_max): (i32, i32, i32, i32), +) -> (i32, i32, i32, i32) { + // To guard against 'min' being larger than 'max'. + let r1_x_min = r1_x_min.min(r1_x_max); + let r1_y_min = r1_y_min.min(r1_y_max); + let r2_x_min = r2_x_min.min(r2_x_max); + let r2_y_min = r2_y_min.min(r2_y_max); + + // First part of intersection. + let r3_x_min = r1_x_min.max(r2_x_min); + let r3_y_min = r1_y_min.max(r2_y_min); + let r3_x_max = r1_x_max.min(r2_x_max); + let r3_y_max = r1_y_max.min(r2_y_max); + + // In case of no overlap. + let r3_x_min = r3_x_min.min(r3_x_max); + let r3_y_min = r3_y_min.min(r3_y_max); + + (r3_x_min, r3_y_min, r3_x_max, r3_y_max) +} + +#[inline] +fn translate_region( + (r_x_min, r_y_min, r_x_max, r_y_max): (i32, i32, i32, i32), + (trans_x, trans_y): (i32, i32), +) -> (i32, i32, i32, i32) { + ( + r_x_min + trans_x, + r_y_min + trans_y, + r_x_max + trans_x, + r_y_max + trans_y, + ) +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct PixelRegion { pub x_min: u32, @@ -398,46 +435,54 @@ impl PixelRegion { size: (i32, i32), other: &mut PixelRegion, ) { - // First place the ideal overlap box within each region, clamping as needed - let self_x_min = self_point.0.max(self.x_min as i32) as u32; - let self_y_min = self_point.1.max(self.y_min as i32) as u32; - let self_x_max = (self_point.0 + size.0).min(self.x_max as i32).max(0) as u32; - let self_y_max = (self_point.1 + size.1).min(self.y_max as i32).max(0) as u32; - - let other_x_min = other_point.0.max(other.x_min as i32) as u32; - let other_y_min = other_point.1.max(other.y_min as i32) as u32; - let other_x_max = (other_point.0 + size.0).min(other.x_max as i32).max(0) as u32; - let other_y_max = (other_point.1 + size.1).min(other.y_max as i32).max(0) as u32; - - // `self` and `other` now have `_point` and `size` clamped within their respective regions - // But the clamping might have changed the rect size, so now we have to find out the new size - - let x = (self_x_min as i32 - self_point.0) - .max(other_x_min as i32 - other_point.0) - .max(0); - let y = (self_y_min as i32 - self_point.1) - .max(other_y_min as i32 - other_point.1) - .max(0); - let width = ((self_x_max - self_x_min) as i32) - .min((other_x_max - other_x_min) as i32) - .max(0) as u32; - let height = ((self_y_max - self_y_min) as i32) - .min((other_y_max - other_y_min) as i32) - .max(0) as u32; - - // Now resize the boxes a final time with the new dimensions - // It's safe to assume the dimensions fall within the safe area of each box - // due to them being calculated using those safe areas - - self.x_min = (self_point.0 + x) as u32; - self.y_min = (self_point.1 + y) as u32; - self.x_max = self.x_min + width; - self.y_max = self.y_min + height; - - other.x_min = (other_point.0 + x) as u32; - other.y_min = (other_point.1 + y) as u32; - other.x_max = other.x_min + width; - other.y_max = other.y_min + height; + // Translate both regions to same coordinate system. + + let r1 = ( + self.x_min as i32, + self.y_min as i32, + self.x_max as i32, + self.y_max as i32, + ); + let r2 = ( + other.x_min as i32, + other.y_min as i32, + other.x_max as i32, + other.y_max as i32, + ); + + let r1_trans = translate_region(r1, (-self_point.0, -self_point.1)); + let r2_trans = translate_region(r2, (-other_point.0, -other_point.1)); + + // Intersection. + + let inters = intersection_same_coordinate_system( + intersection_same_coordinate_system(r1_trans, r2_trans), + (0, 0, size.0, size.1), + ); + + // Translate the intersection back. + + let r1_result = translate_region(inters, self_point); + let r2_result = translate_region(inters, other_point); + + // Ensure empty results yield (0, 0, 0, 0). + + let is_empty = inters.0 == inters.2 || inters.1 == inters.3; + + let r1_result = if is_empty { (0, 0, 0, 0) } else { r1_result }; + let r2_result = if is_empty { (0, 0, 0, 0) } else { r2_result }; + + // Mutate. + + self.x_min = r1_result.0 as u32; + self.y_min = r1_result.1 as u32; + self.x_max = r1_result.2 as u32; + self.y_max = r1_result.3 as u32; + + other.x_min = r2_result.0 as u32; + other.y_min = r2_result.1 as u32; + other.x_max = r2_result.2 as u32; + other.y_max = r2_result.3 as u32; } } @@ -522,5 +567,25 @@ mod test { PixelRegion::for_region_i32(400, 440, 40, 40), PixelRegion::for_region_i32(40, 0, 40, 40), ); + + test( + PixelRegion::for_whole_size(240, 180), + PixelRegion::for_whole_size(238, 164), + (-1, 0), + (0, 0), + (240, 180), + PixelRegion::for_region_i32(0, 0, 237, 164), + PixelRegion::for_region_i32(1, 0, 237, 164), + ); + + test( + PixelRegion::for_whole_size(10, 10), + PixelRegion::for_whole_size(10, 10), + (15, 0), + (0, 15), + (100, 100), + PixelRegion::for_region_i32(0, 0, 0, 0), + PixelRegion::for_region_i32(0, 0, 0, 0), + ); } }