use std::alloc;
use std::slice;
pub mod iterators;
pub mod shared_surface;
pub mod srgb;
#[cfg(target_endian = "little")]
use rgb::alt::BGRA8;
#[cfg(target_endian = "little")]
#[allow(clippy::upper_case_acronyms)]
pub type CairoARGB = BGRA8;
#[cfg(target_endian = "big")]
use rgb::alt::ARGB8;
#[cfg(target_endian = "big")]
#[allow(clippy::upper_case_acronyms)]
pub type CairoARGB = ARGB8;
use rgb::ColorComponentMap;
#[allow(clippy::upper_case_acronyms)]
pub trait AsCairoARGB {
fn as_cairo_argb(&self) -> &[CairoARGB];
fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB];
}
impl AsCairoARGB for [u32] {
fn as_cairo_argb(&self) -> &[CairoARGB] {
const LAYOUT_U32: alloc::Layout = alloc::Layout::new::<u32>();
const LAYOUT_ARGB: alloc::Layout = alloc::Layout::new::<CairoARGB>();
let _: [(); LAYOUT_U32.size()] = [(); LAYOUT_ARGB.size()];
let _: [(); 0] = [(); LAYOUT_U32.align() % LAYOUT_ARGB.align()];
unsafe { slice::from_raw_parts(self.as_ptr() as *const _, self.len()) }
}
fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB] {
unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()) }
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum EdgeMode {
Duplicate,
Wrap,
None,
}
pub trait ToPixel {
fn to_pixel(&self) -> Pixel;
}
pub trait ToCairoARGB {
fn to_cairo_argb(&self) -> CairoARGB;
}
impl ToPixel for CairoARGB {
#[inline]
fn to_pixel(&self) -> Pixel {
Pixel {
r: self.r,
g: self.g,
b: self.b,
a: self.a,
}
}
}
impl ToPixel for image::Rgba<u8> {
#[inline]
fn to_pixel(&self) -> Pixel {
Pixel {
r: self.0[0],
g: self.0[1],
b: self.0[2],
a: self.0[3],
}
}
}
impl ToCairoARGB for Pixel {
#[inline]
fn to_cairo_argb(&self) -> CairoARGB {
CairoARGB {
r: self.r,
g: self.g,
b: self.b,
a: self.a,
}
}
}
pub trait ImageSurfaceDataExt {
fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32);
}
pub type Pixel = rgb::RGBA8;
pub trait PixelOps {
fn premultiply(self) -> Self;
fn unpremultiply(self) -> Self;
fn diff(&self, other: &Self) -> Self;
fn to_luminance_mask(&self) -> Self;
fn to_u32(&self) -> u32;
fn from_u32(x: u32) -> Self;
}
impl PixelOps for Pixel {
#[inline]
fn unpremultiply(self) -> Self {
if self.a == 0 {
Self {
r: 0,
g: 0,
b: 0,
a: 0,
}
} else {
let alpha = f32::from(self.a) / 255.0;
self.map_colors(|x| ((f32::from(x) / alpha) + 0.5) as u8)
}
}
#[inline]
fn premultiply(self) -> Self {
let a = self.a as u32;
self.map_colors(|x| (((x as u32) * a + 127) / 255) as u8)
}
#[inline]
fn diff(&self, other: &Pixel) -> Pixel {
self.iter()
.zip(other.iter())
.map(|(l, r)| (l as i32 - r as i32).unsigned_abs() as u8)
.collect()
}
#[inline]
fn to_luminance_mask(&self) -> Self {
let r = u32::from(self.r);
let g = u32::from(self.g);
let b = u32::from(self.b);
Self {
r: 0,
g: 0,
b: 0,
a: (((r * 14042 + g * 47240 + b * 4769) * 255) >> 24) as u8,
}
}
#[inline]
fn to_u32(&self) -> u32 {
(u32::from(self.a) << 24)
| (u32::from(self.r) << 16)
| (u32::from(self.g) << 8)
| u32::from(self.b)
}
#[inline]
fn from_u32(x: u32) -> Self {
Self {
r: ((x >> 16) & 0xFF) as u8,
g: ((x >> 8) & 0xFF) as u8,
b: (x & 0xFF) as u8,
a: ((x >> 24) & 0xFF) as u8,
}
}
}
impl<'a> ImageSurfaceDataExt for cairo::ImageSurfaceData<'a> {
#[inline]
fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
let this: &mut [u8] = &mut *self;
#[allow(clippy::cast_ptr_alignment)]
let this: &mut [u32] =
unsafe { slice::from_raw_parts_mut(this.as_mut_ptr() as *mut u32, this.len() / 4) };
this.set_pixel(stride, pixel, x, y);
}
}
impl ImageSurfaceDataExt for [u8] {
#[inline]
fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
let this = &mut self[y as usize * stride + x as usize * 4..];
this[..4].copy_from_slice(&pixel.to_u32().to_ne_bytes());
}
}
impl ImageSurfaceDataExt for [u32] {
#[inline]
fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
self[(y as usize * stride + x as usize * 4) / 4] = pixel.to_u32();
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn pixel_diff() {
let a = Pixel::new(0x10, 0x20, 0xf0, 0x40);
assert_eq!(a, a.diff(&Pixel::default()));
let b = Pixel::new(0x50, 0xff, 0x20, 0x10);
assert_eq!(a.diff(&b), Pixel::new(0x40, 0xdf, 0xd0, 0x30));
}
fn premultiply_float(pixel: Pixel) -> Pixel {
let alpha = f64::from(pixel.a) / 255.0;
pixel.map_colors(|x| ((f64::from(x) * alpha) + 0.5) as u8)
}
prop_compose! {
fn arbitrary_pixel()(a: u8, r: u8, g: u8, b: u8) -> Pixel {
Pixel { r, g, b, a }
}
}
proptest! {
#[test]
fn pixel_premultiply(pixel in arbitrary_pixel()) {
prop_assert_eq!(pixel.premultiply(), premultiply_float(pixel));
}
#[test]
fn pixel_unpremultiply(pixel in arbitrary_pixel()) {
let roundtrip = pixel.premultiply().unpremultiply();
if pixel.a == 0 {
prop_assert_eq!(roundtrip, Pixel::default());
} else {
let tolerance = 0xff / pixel.a;
let diff = roundtrip.diff(&pixel);
prop_assert!(diff.r <= tolerance, "red component value differs by more than {}: {:?}", tolerance, roundtrip);
prop_assert!(diff.g <= tolerance, "green component value differs by more than {}: {:?}", tolerance, roundtrip);
prop_assert!(diff.b <= tolerance, "blue component value differs by more than {}: {:?}", tolerance, roundtrip);
prop_assert_eq!(pixel.a, roundtrip.a);
}
}
}
}