rsvg/surface_utils/
mod.rs1use std::alloc;
4use std::slice;
5
6pub mod iterators;
7pub mod shared_surface;
8pub mod srgb;
9
10#[cfg(target_endian = "little")]
13use rgb::alt::BGRA8;
14#[cfg(target_endian = "little")]
15#[allow(clippy::upper_case_acronyms)]
16pub type CairoARGB = BGRA8;
17
18#[cfg(target_endian = "big")]
19use rgb::alt::ARGB8;
20#[cfg(target_endian = "big")]
21#[allow(clippy::upper_case_acronyms)]
22pub type CairoARGB = ARGB8;
23
24use rgb::ColorComponentMap;
25
26#[allow(clippy::upper_case_acronyms)]
28pub trait AsCairoARGB {
29 fn as_cairo_argb(&self) -> &[CairoARGB];
31
32 fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB];
34}
35
36impl AsCairoARGB for [u32] {
41 fn as_cairo_argb(&self) -> &[CairoARGB] {
42 const LAYOUT_U32: alloc::Layout = alloc::Layout::new::<u32>();
43 const LAYOUT_ARGB: alloc::Layout = alloc::Layout::new::<CairoARGB>();
44 let _: [(); LAYOUT_U32.size()] = [(); LAYOUT_ARGB.size()];
45 let _: [(); 0] = [(); LAYOUT_U32.align() % LAYOUT_ARGB.align()];
46 unsafe { slice::from_raw_parts(self.as_ptr() as *const _, self.len()) }
47 }
48
49 fn as_cairo_argb_mut(&mut self) -> &mut [CairoARGB] {
50 unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut _, self.len()) }
51 }
52}
53
54#[derive(Debug, Clone, Copy, Eq, PartialEq)]
58pub enum EdgeMode {
59 Duplicate,
61 Wrap,
65 None,
67}
68
69pub trait ToPixel {
71 fn to_pixel(&self) -> Pixel;
72}
73
74pub trait ToCairoARGB {
76 fn to_cairo_argb(&self) -> CairoARGB;
77}
78
79impl ToPixel for CairoARGB {
80 #[inline]
81 fn to_pixel(&self) -> Pixel {
82 Pixel {
83 r: self.r,
84 g: self.g,
85 b: self.b,
86 a: self.a,
87 }
88 }
89}
90
91impl ToPixel for image::Rgba<u8> {
92 #[inline]
93 fn to_pixel(&self) -> Pixel {
94 Pixel {
95 r: self.0[0],
96 g: self.0[1],
97 b: self.0[2],
98 a: self.0[3],
99 }
100 }
101}
102
103impl ToCairoARGB for Pixel {
104 #[inline]
105 fn to_cairo_argb(&self) -> CairoARGB {
106 CairoARGB {
107 r: self.r,
108 g: self.g,
109 b: self.b,
110 a: self.a,
111 }
112 }
113}
114
115pub trait ImageSurfaceDataExt {
117 fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32);
119}
120
121pub type Pixel = rgb::RGBA8;
123
124pub trait PixelOps {
129 fn premultiply(self) -> Self;
130 fn unpremultiply(self) -> Self;
131 fn diff(&self, other: &Self) -> Self;
132 fn to_luminance_mask(&self) -> Self;
133 fn to_u32(&self) -> u32;
134 fn from_u32(x: u32) -> Self;
135}
136
137impl PixelOps for Pixel {
138 #[inline]
142 fn unpremultiply(self) -> Self {
143 if self.a == 0 {
144 Self {
145 r: 0,
146 g: 0,
147 b: 0,
148 a: 0,
149 }
150 } else {
151 let alpha = f32::from(self.a) / 255.0;
152 self.map_colors(|x| ((f32::from(x) / alpha) + 0.5) as u8)
153 }
154 }
155
156 #[inline]
158 fn premultiply(self) -> Self {
159 let a = self.a as u32;
160 self.map_colors(|x| (((x as u32) * a + 127) / 255) as u8)
161 }
162
163 #[inline]
164 fn diff(&self, other: &Pixel) -> Pixel {
165 self.iter()
166 .zip(other.iter())
167 .map(|(l, r)| (l as i32 - r as i32).unsigned_abs() as u8)
168 .collect()
169 }
170
171 #[inline]
192 fn to_luminance_mask(&self) -> Self {
193 let r = u32::from(self.r);
194 let g = u32::from(self.g);
195 let b = u32::from(self.b);
196
197 Self {
198 r: 0,
199 g: 0,
200 b: 0,
201 a: (((r * 14042 + g * 47240 + b * 4769) * 255) >> 24) as u8,
202 }
203 }
204
205 #[inline]
207 fn to_u32(&self) -> u32 {
208 (u32::from(self.a) << 24)
209 | (u32::from(self.r) << 16)
210 | (u32::from(self.g) << 8)
211 | u32::from(self.b)
212 }
213
214 #[inline]
216 fn from_u32(x: u32) -> Self {
217 Self {
218 r: ((x >> 16) & 0xFF) as u8,
219 g: ((x >> 8) & 0xFF) as u8,
220 b: (x & 0xFF) as u8,
221 a: ((x >> 24) & 0xFF) as u8,
222 }
223 }
224}
225
226impl<'a> ImageSurfaceDataExt for cairo::ImageSurfaceData<'a> {
227 #[inline]
228 fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
229 let this: &mut [u8] = &mut *self;
230 #[allow(clippy::cast_ptr_alignment)]
239 let this: &mut [u32] =
240 unsafe { slice::from_raw_parts_mut(this.as_mut_ptr() as *mut u32, this.len() / 4) };
241 this.set_pixel(stride, pixel, x, y);
242 }
243}
244impl ImageSurfaceDataExt for [u8] {
245 #[inline]
246 fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
247 let this = &mut self[y as usize * stride + x as usize * 4..];
248 this[..4].copy_from_slice(&pixel.to_u32().to_ne_bytes());
249 }
250}
251impl ImageSurfaceDataExt for [u32] {
252 #[inline]
253 fn set_pixel(&mut self, stride: usize, pixel: Pixel, x: u32, y: u32) {
254 self[(y as usize * stride + x as usize * 4) / 4] = pixel.to_u32();
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use proptest::prelude::*;
262
263 #[test]
264 fn pixel_diff() {
265 let a = Pixel::new(0x10, 0x20, 0xf0, 0x40);
266 assert_eq!(a, a.diff(&Pixel::default()));
267 let b = Pixel::new(0x50, 0xff, 0x20, 0x10);
268 assert_eq!(a.diff(&b), Pixel::new(0x40, 0xdf, 0xd0, 0x30));
269 }
270
271 fn premultiply_float(pixel: Pixel) -> Pixel {
273 let alpha = f64::from(pixel.a) / 255.0;
274 pixel.map_colors(|x| ((f64::from(x) * alpha) + 0.5) as u8)
275 }
276
277 prop_compose! {
278 fn arbitrary_pixel()(a: u8, r: u8, g: u8, b: u8) -> Pixel {
279 Pixel { r, g, b, a }
280 }
281 }
282
283 proptest! {
284 #[test]
285 fn pixel_premultiply(pixel in arbitrary_pixel()) {
286 prop_assert_eq!(pixel.premultiply(), premultiply_float(pixel));
287 }
288
289 #[test]
290 fn pixel_unpremultiply(pixel in arbitrary_pixel()) {
291 let roundtrip = pixel.premultiply().unpremultiply();
292 if pixel.a == 0 {
293 prop_assert_eq!(roundtrip, Pixel::default());
294 } else {
295 let tolerance = 0xff / pixel.a;
297 let diff = roundtrip.diff(&pixel);
298 prop_assert!(diff.r <= tolerance, "red component value differs by more than {}: {:?}", tolerance, roundtrip);
299 prop_assert!(diff.g <= tolerance, "green component value differs by more than {}: {:?}", tolerance, roundtrip);
300 prop_assert!(diff.b <= tolerance, "blue component value differs by more than {}: {:?}", tolerance, roundtrip);
301
302 prop_assert_eq!(pixel.a, roundtrip.a);
303 }
304 }
305 }
306}