Skip to content

Commit 5237534

Browse files
authored
Image: fix incorrect rgba8 premultiply calculations (#9818)
* fix: incorrect rgba8 premultiply calculations I have corrected the rgba8 premultiply calculations in to_rgba8_premultiplied and to_rgba8, which should now be correct. Fixes #9810 * feat: switch to integer math for premultiply calculations I have moved out the calculations to premultiplied_rgba_to_rgba and rgba_to_premultiplied_rgba for testing and readability. I have also switched from floating-point math to integer math instead to avoid use of round. * fix: make premultiply calculation functions private
1 parent db7e383 commit 5237534

File tree

1 file changed

+82
-34
lines changed

1 file changed

+82
-34
lines changed

internal/core/graphics/image.rs

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -770,23 +770,7 @@ impl Image {
770770
SharedImageBuffer::RGBA8Premultiplied(buffer) => SharedPixelBuffer::<Rgba8Pixel> {
771771
width: buffer.width,
772772
height: buffer.height,
773-
data: buffer
774-
.data
775-
.into_iter()
776-
.map(|rgba_premul| {
777-
if rgba_premul.a == 0 {
778-
Rgba8Pixel::new(0, 0, 0, 0)
779-
} else {
780-
let af = rgba_premul.a as f32 / 255.0;
781-
Rgba8Pixel {
782-
r: (rgba_premul.r as f32 * 255. / af) as u8,
783-
g: (rgba_premul.g as f32 * 255. / af) as u8,
784-
b: (rgba_premul.b as f32 * 255. / af) as u8,
785-
a: rgba_premul.a,
786-
}
787-
}
788-
})
789-
.collect(),
773+
data: buffer.data.into_iter().map(Image::premultiplied_rgba_to_rgba).collect(),
790774
},
791775
})
792776
}
@@ -804,28 +788,43 @@ impl Image {
804788
SharedImageBuffer::RGBA8(buffer) => SharedPixelBuffer::<Rgba8Pixel> {
805789
width: buffer.width,
806790
height: buffer.height,
807-
data: buffer
808-
.data
809-
.into_iter()
810-
.map(|rgba| {
811-
if rgba.a == 255 {
812-
rgba
813-
} else {
814-
let af = rgba.a as f32 / 255.0;
815-
Rgba8Pixel {
816-
r: (rgba.r as f32 * af / 255.) as u8,
817-
g: (rgba.g as f32 * af / 255.) as u8,
818-
b: (rgba.b as f32 * af / 255.) as u8,
819-
a: rgba.a,
820-
}
821-
}
822-
})
823-
.collect(),
791+
data: buffer.data.into_iter().map(Image::rgba_to_premultiplied_rgba).collect(),
824792
},
825793
SharedImageBuffer::RGBA8Premultiplied(buffer) => buffer,
826794
})
827795
}
828796

797+
/// Returns the pixel converted from premultiplied RGBA to RGBA.
798+
fn premultiplied_rgba_to_rgba(pixel: Rgba8Pixel) -> Rgba8Pixel {
799+
if pixel.a == 0 {
800+
Rgba8Pixel::new(0, 0, 0, 0)
801+
} else {
802+
let af = pixel.a as u32;
803+
let round = (af / 2) as u32;
804+
Rgba8Pixel {
805+
r: ((pixel.r as u32 * 255 + round) / af).min(255) as u8,
806+
g: ((pixel.g as u32 * 255 + round) / af).min(255) as u8,
807+
b: ((pixel.b as u32 * 255 + round) / af).min(255) as u8,
808+
a: pixel.a,
809+
}
810+
}
811+
}
812+
813+
/// Returns the pixel converted from RGBA to premultiplied RGBA.
814+
fn rgba_to_premultiplied_rgba(pixel: Rgba8Pixel) -> Rgba8Pixel {
815+
if pixel.a == 255 {
816+
pixel
817+
} else {
818+
let af = pixel.a as u32;
819+
Rgba8Pixel {
820+
r: (((pixel.r as u32 * af + 128) * 257) >> 16) as u8,
821+
g: (((pixel.g as u32 * af + 128) * 257) >> 16) as u8,
822+
b: (((pixel.b as u32 * af + 128) * 257) >> 16) as u8,
823+
a: pixel.a,
824+
}
825+
}
826+
}
827+
829828
/// Returns the [WGPU](http://wgpu.rs) 26.x texture that this image wraps; returns None if the image does not
830829
/// hold such a previously wrapped texture.
831830
///
@@ -1472,3 +1471,52 @@ pub struct BorrowedOpenGLTexture {
14721471
/// Origin of the texture when rendering.
14731472
pub origin: BorrowedOpenGLTextureOrigin,
14741473
}
1474+
1475+
#[cfg(test)]
1476+
mod tests {
1477+
use crate::graphics::Rgba8Pixel;
1478+
1479+
use super::Image;
1480+
1481+
#[test]
1482+
fn test_premultiplied_to_rgb_zero_alpha() {
1483+
let pixel = Rgba8Pixel::new(5, 10, 15, 0);
1484+
let converted = Image::premultiplied_rgba_to_rgba(pixel);
1485+
assert_eq!(converted, Rgba8Pixel::new(0, 0, 0, 0));
1486+
}
1487+
1488+
#[test]
1489+
fn test_premultiplied_to_rgb_full_alpha() {
1490+
let pixel = Rgba8Pixel::new(5, 10, 15, 255);
1491+
let converted = Image::premultiplied_rgba_to_rgba(pixel);
1492+
assert_eq!(converted, Rgba8Pixel::new(5, 10, 15, 255));
1493+
}
1494+
1495+
#[test]
1496+
fn test_premultiplied_to_rgb() {
1497+
let pixel = Rgba8Pixel::new(5, 10, 15, 128);
1498+
let converted = Image::premultiplied_rgba_to_rgba(pixel);
1499+
assert_eq!(converted, Rgba8Pixel::new(10, 20, 30, 128));
1500+
}
1501+
1502+
#[test]
1503+
fn test_rgb_to_premultiplied_zero_alpha() {
1504+
let pixel = Rgba8Pixel::new(10, 20, 30, 0);
1505+
let converted = Image::rgba_to_premultiplied_rgba(pixel);
1506+
assert_eq!(converted, Rgba8Pixel::new(0, 0, 0, 0));
1507+
}
1508+
1509+
#[test]
1510+
fn test_rgb_to_premultiplied_full_alpha() {
1511+
let pixel = Rgba8Pixel::new(10, 20, 30, 255);
1512+
let converted = Image::rgba_to_premultiplied_rgba(pixel);
1513+
assert_eq!(converted, Rgba8Pixel::new(10, 20, 30, 255));
1514+
}
1515+
1516+
#[test]
1517+
fn test_rgb_to_premultiplied() {
1518+
let pixel = Rgba8Pixel::new(10, 20, 30, 128);
1519+
let converted = Image::rgba_to_premultiplied_rgba(pixel);
1520+
assert_eq!(converted, Rgba8Pixel::new(5, 10, 15, 128));
1521+
}
1522+
}

0 commit comments

Comments
 (0)