Fixed error in NEON implementation of MulDiv::multiply_alpha() and MulDiv::multiply_alpha_inplace() for U8x2 pixels (#49).

(cherry picked from commit f4e15602fe)
This commit is contained in:
Kirill Kuzminykh
2025-04-07 01:36:55 +03:00
parent 75bd369a6b
commit a8e47c38b9
4 changed files with 87 additions and 88 deletions
+8
View File
@@ -1,3 +1,11 @@
## [Unreleased] - ReleaseDate
## Fixed
- Fixed error in `NEON` implementation of `MulDiv::multiply_alpha()` and
`MulDiv::multiply_alpha_inplace()` for `U8x2` pixels
([#49](https://github.com/Cykooz/fast_image_resize/issues/49)).
## [4.2.1] - 2024-07-24
### Fixed
+13 -24
View File
@@ -36,13 +36,13 @@ unsafe fn multiply_alpha_row(src_row: &[U8x2], dst_row: &mut [U8x2]) {
foreach_with_pre_reading(
src_dst,
|(src, dst)| {
let pixels = neon_utils::load_deintrel_u8x16x2(src, 0);
let pixels = neon_utils::load_deintrel_u8x16x4(src, 0);
let dst_ptr = dst.as_mut_ptr() as *mut u8;
(pixels, dst_ptr)
},
|(mut pixels, dst_ptr)| {
pixels = multiplies_alpha_32_pixels(pixels);
vst2q_u8(dst_ptr, pixels);
vst4q_u8(dst_ptr, pixels);
},
);
@@ -82,19 +82,19 @@ unsafe fn multiply_alpha_row_inplace(row: &mut [U8x2]) {
foreach_with_pre_reading(
&mut chunks,
|chunk| {
let pixels = neon_utils::load_deintrel_u8x16x2(chunk, 0);
let pixels = neon_utils::load_deintrel_u8x16x4(chunk, 0);
let dst_ptr = chunk.as_mut_ptr() as *mut u8;
(pixels, dst_ptr)
},
|(mut pixels, dst_ptr)| {
pixels = multiplies_alpha_32_pixels(pixels);
vst2q_u8(dst_ptr, pixels);
vst4q_u8(dst_ptr, pixels);
},
);
let reminder = chunks.into_remainder();
let mut chunks = reminder.chunks_exact_mut(16);
for chunk in &mut chunks {
if let Some(chunk) = chunks.next() {
let mut pixels = neon_utils::load_deintrel_u8x8x4(chunk, 0);
pixels = multiplies_alpha_16_pixels(pixels);
let chunk_ptr = chunk.as_mut_ptr() as *mut u8;
@@ -103,7 +103,7 @@ unsafe fn multiply_alpha_row_inplace(row: &mut [U8x2]) {
let reminder = chunks.into_remainder();
let mut chunks = reminder.chunks_exact_mut(8);
for chunk in &mut chunks {
if let Some(chunk) = chunks.next() {
let mut pixels = neon_utils::load_deintrel_u8x8x2(chunk, 0);
pixels = multiplies_alpha_8_pixels(pixels);
let chunk_ptr = chunk.as_mut_ptr() as *mut u8;
@@ -116,31 +116,20 @@ unsafe fn multiply_alpha_row_inplace(row: &mut [U8x2]) {
}
}
// #[inline(always)]
// unsafe fn multiplies_alpha_64_pixels(mut pixels: uint8x16x4_t) -> uint8x16x4_t {
// let zero_u8x16 = vdupq_n_u8(0);
// let alpha_u16 = uint16x8x2_t(
// vreinterpretq_u16_u8(vzip1q_u8(pixels.1, zero_u8x16)),
// vreinterpretq_u16_u8(vzip2q_u8(pixels.1, zero_u8x16)),
// );
// pixels.0 = neon_utils::mul_color_to_alpha_u8x16(pixels.0, alpha_u16, zero_u8x16);
// let alpha_u16 = uint16x8x2_t(
// vreinterpretq_u16_u8(vzip1q_u8(pixels.3, zero_u8x16)),
// vreinterpretq_u16_u8(vzip2q_u8(pixels.3, zero_u8x16)),
// );
// pixels.2 = neon_utils::mul_color_to_alpha_u8x16(pixels.2, alpha_u16, zero_u8x16);
// pixels
// }
#[inline(always)]
unsafe fn multiplies_alpha_32_pixels(mut pixels: uint8x16x2_t) -> uint8x16x2_t {
unsafe fn multiplies_alpha_32_pixels(mut pixels: uint8x16x4_t) -> uint8x16x4_t {
let zero_u8x16 = vdupq_n_u8(0);
let alpha_u16 = uint16x8x2_t(
vreinterpretq_u16_u8(vzip1q_u8(pixels.1, zero_u8x16)),
vreinterpretq_u16_u8(vzip2q_u8(pixels.1, zero_u8x16)),
);
pixels.0 = neon_utils::mul_color_to_alpha_u8x16(pixels.0, alpha_u16, zero_u8x16);
let alpha_u16 = uint16x8x2_t(
vreinterpretq_u16_u8(vzip1q_u8(pixels.3, zero_u8x16)),
vreinterpretq_u16_u8(vzip2q_u8(pixels.3, zero_u8x16)),
);
pixels.2 = neon_utils::mul_color_to_alpha_u8x16(pixels.2, alpha_u16, zero_u8x16);
pixels
}
+1 -1
View File
@@ -149,7 +149,7 @@ impl Image<'static> {
}
impl<'a> Image<'a> {
/// Create an image with from slice with pixels data.
/// Create an image from slice with pixels data.
pub fn from_slice_u8(
width: u32,
height: u32,
+65 -63
View File
@@ -23,78 +23,80 @@ fn mul_div_alpha_test<P: PixelTrait>(
return;
}
let width: u32 = 8 + 8 + 7;
let height: u32 = 3;
for width in [1, 9, 17, 25, 33, 41, 49, 57, 65] {
let src_size = width as usize * height as usize;
let src_pixels: Vec<P> = src_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
let mut dst_pixels = src_pixels.clone();
let src_size = width as usize * height as usize;
let src_pixels: Vec<P> = src_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
let mut dst_pixels = src_pixels.clone();
let src_image = TypedImageRef::new(width, height, &src_pixels).unwrap();
let mut dst_image = TypedImage::from_pixels_slice(width, height, &mut dst_pixels).unwrap();
let src_image = TypedImageRef::new(width, height, &src_pixels).unwrap();
let mut dst_image = TypedImage::from_pixels_slice(width, height, &mut dst_pixels).unwrap();
let mut alpha_mul_div: MulDiv = Default::default();
unsafe {
alpha_mul_div.set_cpu_extensions(cpu_extensions);
}
let mut alpha_mul_div: MulDiv = Default::default();
unsafe {
alpha_mul_div.set_cpu_extensions(cpu_extensions);
}
match oper {
Oper::Mul => alpha_mul_div
.multiply_alpha_typed(&src_image, &mut dst_image)
.unwrap(),
Oper::Div => alpha_mul_div
.divide_alpha_typed(&src_image, &mut dst_image)
.unwrap(),
}
match oper {
Oper::Mul => alpha_mul_div
.multiply_alpha_typed(&src_image, &mut dst_image)
.unwrap(),
Oper::Div => alpha_mul_div
.divide_alpha_typed(&src_image, &mut dst_image)
.unwrap(),
}
let oper_str = if oper == Oper::Mul {
"multiple"
} else {
"divide"
};
let oper_str = if oper == Oper::Mul {
"multiple"
} else {
"divide"
};
let cpu_ext_str = cpu_ext_into_str(cpu_extensions);
let expected_pixels: Vec<P> = expected_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
for ((s, r), e) in src_pixels
.iter()
.zip(dst_pixels)
.zip(expected_pixels.iter())
{
assert_eq!(
r, *e,
"failed test for {oper_str} alpha with '{cpu_ext_str}' CPU extensions \
and image width {width}: src={s:?}, result={r:?}, expected_result={e:?}",
);
}
let cpu_ext_str = cpu_ext_into_str(cpu_extensions);
let expected_pixels: Vec<P> = expected_pixels_tpl
.iter()
.copied()
.cycle()
.take(src_size)
.collect();
for ((s, r), e) in src_pixels
.iter()
.zip(dst_pixels)
.zip(expected_pixels.iter())
{
assert_eq!(
r, *e,
"failed test for {oper_str} alpha with '{cpu_ext_str}' CPU extensions: \
src={s:?}, result={r:?}, expected_result={e:?}",
);
}
// Inplace
let mut src_pixels_clone = src_pixels.clone();
let mut image =
TypedImage::from_pixels_slice(width, height, &mut src_pixels_clone).unwrap();
// Inplace
let mut src_pixels_clone = src_pixels.clone();
let mut image = TypedImage::from_pixels_slice(width, height, &mut src_pixels_clone).unwrap();
match oper {
Oper::Mul => alpha_mul_div
.multiply_alpha_inplace_typed(&mut image)
.unwrap(),
Oper::Div => alpha_mul_div
.divide_alpha_inplace_typed(&mut image)
.unwrap(),
}
match oper {
Oper::Mul => alpha_mul_div
.multiply_alpha_inplace_typed(&mut image)
.unwrap(),
Oper::Div => alpha_mul_div
.divide_alpha_inplace_typed(&mut image)
.unwrap(),
}
for ((s, r), e) in src_pixels.iter().zip(src_pixels_clone).zip(expected_pixels) {
assert_eq!(
r, e,
"failed inplace test for {oper_str} alpha: src={s:?}, result={r:?}, expected_result={e:?}",
);
for ((s, r), e) in src_pixels.iter().zip(src_pixels_clone).zip(expected_pixels) {
assert_eq!(
r, e,
"failed inplace test for {oper_str} alpha with '{cpu_ext_str}' CPU extensions \
and image width {width}: src={s:?}, result={r:?}, expected_result={e:?}",
);
}
}
}