use resvg::tiny_skia;
#[derive(Clone)]
pub(crate) struct PremultipliedRgba8 {
pixels: image::RgbaImage,
}
#[derive(Clone, Copy)]
pub(crate) struct RasterGroupCompositing {
transform: tiny_skia::Transform,
opacity: f32,
blend_mode: tiny_skia::BlendMode,
}
impl RasterGroupCompositing {
pub(crate) fn from_css(
transform: crate::style::computed::CssAffineMatrix,
opacity: f32,
blend_mode: crate::style::computed::BlendMode,
pixels_per_point: f32,
) -> Self {
let [a, b, c, d, e, f] = transform.components();
Self {
transform: tiny_skia::Transform::from_row(
a as f32,
b as f32,
c as f32,
d as f32,
(e * f64::from(pixels_per_point)) as f32,
(f * f64::from(pixels_per_point)) as f32,
),
opacity: opacity.clamp(0.0, 1.0),
blend_mode: tiny_skia_blend_mode(blend_mode),
}
}
}
impl PremultipliedRgba8 {
pub(crate) fn transparent(width: u32, height: u32) -> Self {
Self {
pixels: image::RgbaImage::new(width, height),
}
}
pub(crate) fn from_straight(image: &image::RgbaImage) -> Self {
let mut pixels = image.clone();
for pixel in pixels.pixels_mut() {
let alpha = pixel[3];
for channel in &mut pixel.0[..3] {
*channel = premultiply_channel(*channel, alpha);
}
}
Self { pixels }
}
pub(crate) fn from_encoded(pixels: image::RgbaImage) -> Self {
Self { pixels }
}
pub(crate) const fn as_image(&self) -> &image::RgbaImage {
&self.pixels
}
pub(crate) fn as_image_mut(&mut self) -> &mut image::RgbaImage {
&mut self.pixels
}
pub(crate) fn dimensions(&self) -> (u32, u32) {
self.pixels.dimensions()
}
pub(crate) fn width(&self) -> u32 {
self.pixels.width()
}
pub(crate) fn height(&self) -> u32 {
self.pixels.height()
}
pub(crate) fn get_pixel(&self, x: u32, y: u32) -> &image::Rgba<u8> {
self.pixels.get_pixel(x, y)
}
#[cfg(test)]
pub(crate) fn pixels(&self) -> impl Iterator<Item = &image::Rgba<u8>> {
self.pixels.pixels()
}
pub(crate) fn into_straight(mut self) -> image::RgbaImage {
for pixel in self.pixels.pixels_mut() {
let alpha = pixel[3];
for channel in &mut pixel.0[..3] {
*channel = unpremultiply_channel(*channel, alpha);
}
}
self.pixels
}
pub(crate) fn map_straight(
&mut self,
mut map: impl FnMut((f32, f32, f32, f32)) -> (f32, f32, f32, f32),
) {
for pixel in self.pixels.pixels_mut() {
let alpha = f32::from(pixel[3]) / 255.0;
let straight = if alpha > 0.0 {
(
f32::from(pixel[0]) / 255.0 / alpha,
f32::from(pixel[1]) / 255.0 / alpha,
f32::from(pixel[2]) / 255.0 / alpha,
alpha,
)
} else {
(0.0, 0.0, 0.0, 0.0)
};
let (red, green, blue, alpha) = map(straight);
let alpha = alpha.clamp(0.0, 1.0);
let quantize = |component: f32| {
(component.clamp(0.0, 1.0) * alpha * 255.0)
.round()
.clamp(0.0, 255.0) as u8
};
*pixel = image::Rgba([
quantize(red),
quantize(green),
quantize(blue),
(alpha * 255.0).round().clamp(0.0, 255.0) as u8,
]);
}
}
pub(crate) fn composite_over(&mut self, source: &Self, origin: DevicePixelPoint) -> Option<()> {
let target_width = i64::from(self.width());
let target_height = i64::from(self.height());
for (source_x, source_y, source_pixel) in source.pixels.enumerate_pixels() {
let target_x = i64::from(origin.x).checked_add(i64::from(source_x))?;
let target_y = i64::from(origin.y).checked_add(i64::from(source_y))?;
if target_x < 0 || target_y < 0 || target_x >= target_width || target_y >= target_height
{
continue;
}
let target = self
.pixels
.get_pixel_mut(u32::try_from(target_x).ok()?, u32::try_from(target_y).ok()?);
*target = premultiplied_source_over(*source_pixel, *target);
}
Some(())
}
pub(crate) fn composite_group(
&mut self,
source: &Self,
compositing: RasterGroupCompositing,
) -> Option<()> {
let source = tiny_skia::PixmapRef::from_bytes(
source.pixels.as_raw(),
source.width(),
source.height(),
)?;
let width = self.width();
let height = self.height();
let mut target = tiny_skia::PixmapMut::from_bytes(self.pixels.as_mut(), width, height)?;
target.draw_pixmap(
0,
0,
source,
&tiny_skia::PixmapPaint {
opacity: compositing.opacity,
blend_mode: compositing.blend_mode,
quality: tiny_skia::FilterQuality::Bilinear,
},
compositing.transform,
None,
);
Some(())
}
}
fn tiny_skia_blend_mode(mode: crate::style::computed::BlendMode) -> tiny_skia::BlendMode {
use crate::style::computed::BlendMode;
match mode {
BlendMode::Normal | BlendMode::BackgroundList { .. } => tiny_skia::BlendMode::SourceOver,
BlendMode::Multiply => tiny_skia::BlendMode::Multiply,
BlendMode::Screen => tiny_skia::BlendMode::Screen,
BlendMode::Overlay => tiny_skia::BlendMode::Overlay,
BlendMode::Darken => tiny_skia::BlendMode::Darken,
BlendMode::Lighten => tiny_skia::BlendMode::Lighten,
BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,
BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,
BlendMode::HardLight => tiny_skia::BlendMode::HardLight,
BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,
BlendMode::Difference => tiny_skia::BlendMode::Difference,
BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,
BlendMode::Hue => tiny_skia::BlendMode::Hue,
BlendMode::Saturation => tiny_skia::BlendMode::Saturation,
BlendMode::Color => tiny_skia::BlendMode::Color,
BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,
}
}
fn premultiplied_source_over(
source: image::Rgba<u8>,
destination: image::Rgba<u8>,
) -> image::Rgba<u8> {
let inverse_source_alpha = u16::from(u8::MAX - source[3]);
image::Rgba(std::array::from_fn(|channel| {
let retained = (u16::from(destination[channel]) * inverse_source_alpha + 127) / 255;
u16::from(source[channel])
.saturating_add(retained)
.min(u16::from(u8::MAX)) as u8
}))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct DevicePixelPoint {
pub(crate) x: i32,
pub(crate) y: i32,
}
impl DevicePixelPoint {
pub(crate) const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct DevicePixelVector {
pub(crate) x: f32,
pub(crate) y: f32,
}
impl DevicePixelVector {
pub(crate) const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
pub(crate) fn rgba_to_pixmap(image: &image::RgbaImage) -> Option<tiny_skia::Pixmap> {
let mut pixmap = tiny_skia::Pixmap::new(image.width(), image.height())?;
for (destination, source) in pixmap.pixels_mut().iter_mut().zip(image.pixels()) {
*destination =
tiny_skia::ColorU8::from_rgba(source[0], source[1], source[2], source[3]).premultiply();
}
Some(pixmap)
}
pub(crate) fn pixmap_to_rgba(pixmap: &tiny_skia::Pixmap) -> image::RgbaImage {
image::RgbaImage::from_fn(pixmap.width(), pixmap.height(), |x, y| {
let index = y as usize * pixmap.width() as usize + x as usize;
let color = pixmap.pixels()[index].demultiply();
image::Rgba([color.red(), color.green(), color.blue(), color.alpha()])
})
}
pub(crate) fn pixmap_to_premultiplied_rgba(pixmap: &tiny_skia::Pixmap) -> image::RgbaImage {
image::RgbaImage::from_fn(pixmap.width(), pixmap.height(), |x, y| {
let index = y as usize * pixmap.width() as usize + x as usize;
let color = pixmap.pixels()[index];
image::Rgba([color.red(), color.green(), color.blue(), color.alpha()])
})
}
pub(crate) fn pixmap_to_alpha_mask(pixmap: &tiny_skia::Pixmap) -> image::GrayImage {
image::GrayImage::from_fn(pixmap.width(), pixmap.height(), |x, y| {
let index = y as usize * pixmap.width() as usize + x as usize;
image::Luma([pixmap.pixels()[index].alpha()])
})
}
pub(crate) fn premultiply_rgba8(image: &image::RgbaImage) -> image::RgbaImage {
PremultipliedRgba8::from_straight(image).pixels
}
pub(crate) fn unpremultiply_rgba8(image: &image::RgbaImage) -> image::RgbaImage {
let mut unpremultiplied = image.clone();
for pixel in unpremultiplied.pixels_mut() {
let alpha = pixel[3];
for channel in &mut pixel.0[..3] {
*channel = unpremultiply_channel(*channel, alpha);
}
}
unpremultiplied
}
fn premultiply_channel(component: u8, alpha: u8) -> u8 {
let product = u32::from(component) * u32::from(alpha) + 128;
((product + (product >> 8)) >> 8) as u8
}
fn unpremultiply_channel(component: u8, alpha: u8) -> u8 {
if alpha == 0 {
return 0;
}
let denominator = u32::from(alpha);
let numerator = u32::from(component) * 255;
let quotient = numerator / denominator;
let remainder = numerator % denominator;
let doubled_remainder = remainder * 2;
let round_up =
doubled_remainder > denominator || (doubled_remainder == denominator && quotient % 2 == 1);
(quotient + u32::from(round_up)).min(255) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn premultiplication_rounds_every_byte_pair_to_nearest() {
for alpha in 0..=u8::MAX {
for component in 0..=u8::MAX {
let expected = (f32::from(component) * f32::from(alpha) / 255.0).round() as u8;
assert_eq!(premultiply_channel(component, alpha), expected);
}
}
}
#[test]
fn unpremultiplication_uses_nearest_even_ties() {
assert_eq!(unpremultiply_channel(0, 0), 0);
assert_eq!(unpremultiply_channel(1, 3), 85);
assert_eq!(unpremultiply_channel(2, 9), 57);
assert_eq!(unpremultiply_channel(5, 9), 142);
assert_eq!(unpremultiply_channel(4, 19), 54);
assert_eq!(unpremultiply_channel(2, 204), 2);
assert_eq!(unpremultiply_channel(11, 22), 128);
}
}