| // Copyright 2025 the Vello Authors | |
| // SPDX-License-Identifier: Apache-2.0 OR MIT | |
| //! A simple pixmap type. | |
| use alloc::vec; | |
| use alloc::vec::Vec; | |
| #[cfg(feature = "png")] | |
| use std::io::{BufRead, Seek}; | |
| use crate::fearless_simd::{Level, dispatch, mask8x16, prelude::*, u8x16, u8x32, u8x64, u16x16}; | |
| use crate::peniko::{ImageAlphaType, color::PremulRgba8}; | |
| use crate::util::{Div255Ext, narrow, unpremultiply, widen}; | |
| #[cfg(feature = "png")] | |
| extern crate std; | |
| /// A pixmap of premultiplied RGBA8 values backed by [`u8`][core::u8]. | |
| #[derive(Debug, Clone)] | |
| pub struct Pixmap { | |
| /// Width of the pixmap in pixels. | |
| width: u16, | |
| /// Height of the pixmap in pixels. | |
| height: u16, | |
| /// Buffer of the pixmap in RGBA8 format. | |
| buf: Vec<PremulRgba8>, | |
| /// Whether the pixmap may have non-opaque pixels. | |
| /// | |
| /// Note: This may become stale if pixels are modified via [`data_mut()`](Self::data_mut), | |
| /// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel). | |
| may_have_transparency: bool, | |
| } | |
| /// A mutable view into premultiplied RGBA8 pixmap data. | |
| #[derive(Debug)] | |
| pub struct PixmapMut<'a> { | |
| /// Width of the pixmap in pixels. | |
| width: u16, | |
| /// Height of the pixmap in pixels. | |
| height: u16, | |
| /// Buffer of the pixmap in RGBA8 format. | |
| buf: &'a mut [u8], | |
| /// Opacity metadata of the owning [`Pixmap`], when this view was created from one. | |
| may_have_transparency: Option<&'a mut bool>, | |
| } | |
| impl<'a> PixmapMut<'a> { | |
| /// Create a new mutable pixmap view. | |
| /// | |
| /// Returns `None` if `buf` is not exactly `width * height * 4` bytes long. | |
| pub fn new(width: u16, height: u16, buf: &'a mut [u8]) -> Option<Self> { | |
| if buf.len() == usize::from(width) * usize::from(height) * 4 { | |
| Some(Self { | |
| width, | |
| height, | |
| buf, | |
| may_have_transparency: None, | |
| }) | |
| } else { | |
| None | |
| } | |
| } | |
| /// Return the width of the pixmap. | |
| pub fn width(&self) -> u16 { | |
| self.width | |
| } | |
| /// Return the height of the pixmap. | |
| pub fn height(&self) -> u16 { | |
| self.height | |
| } | |
| /// Returns a mutable reference to the underlying data as premultiplied RGBA8 bytes. | |
| pub fn data_mut(&mut self) -> &mut [u8] { | |
| self.buf | |
| } | |
| /// Update the opacity hint of the owning [`Pixmap`], if one exists. | |
| #[doc(hidden)] | |
| pub fn set_may_have_transparency(&mut self, may_have_transparency: bool) { | |
| if let Some(flag) = self.may_have_transparency.as_deref_mut() { | |
| *flag = may_have_transparency; | |
| } | |
| } | |
| } | |
| impl<'a> From<&'a mut Pixmap> for PixmapMut<'a> { | |
| fn from(pixmap: &'a mut Pixmap) -> Self { | |
| pixmap.as_mut() | |
| } | |
| } | |
| impl Pixmap { | |
| /// Create a new pixmap with the given width and height in pixels. | |
| /// | |
| /// All pixels are initialized to transparent black. | |
| pub fn new(width: u16, height: u16) -> Self { | |
| let buf = vec![PremulRgba8::from_u32(0); width as usize * height as usize]; | |
| Self { | |
| width, | |
| height, | |
| buf, | |
| may_have_transparency: true, | |
| } | |
| } | |
| /// Create a new pixmap from the given buffer of bytes, representing pixel data. | |
| /// | |
| /// When passing premultiplied pixels, the data must be correctly premultiplied, i.e. each RGB | |
| /// component must be less than or equal to its pixel's alpha component. | |
| /// | |
| /// # Panics | |
| /// | |
| /// - Panics if `data` is not exactly `width * height * 4` bytes long. | |
| /// - Panics if the capacity of the vector is not a multiple of 4. | |
| pub fn from_parts( | |
| mut data: Vec<u8>, | |
| width: u16, | |
| height: u16, | |
| pixel_metadata: PixelMetadata, | |
| ) -> Self { | |
| let may_have_transparency = if pixel_metadata.may_have_transparency | |
| && pixel_metadata.alpha_type == ImageAlphaType::Alpha | |
| { | |
| // If there might be transparency and the data is not premultiplied yet, we need to | |
| // iterate over all pixels anyway. Rechecking the alpha values only adds little | |
| // overhead (around 5-10% from my benchmarks), and lets us downgrade a conservative | |
| // transparency hint to fully opaque. | |
| premultiply_rgba8(&mut data) | |
| } else { | |
| // If the data is already premultiplied, we want to avoid reloading all pixels from | |
| // memory just to _maybe_ downgrade the transparency hint, so we avoid doing that | |
| // and always return the hint directly. | |
| pixel_metadata.may_have_transparency | |
| }; | |
| let data: Vec<PremulRgba8> = bytemuck::try_cast_vec(data) | |
| .map_err(|(error, _data)| error) | |
| .expect("The capacity of the vector needs to be divisible by 4."); | |
| assert_eq!( | |
| data.len(), | |
| usize::from(width) * usize::from(height), | |
| "Expected `data` to have length of exactly `width * height`" | |
| ); | |
| Self { | |
| width, | |
| height, | |
| buf: data, | |
| may_have_transparency, | |
| } | |
| } | |
| /// Resizes the pixmap container to the given width and height; this does not resize the | |
| /// contained image. | |
| /// | |
| /// If the pixmap buffer has to grow to fit the new size, those pixels are set to transparent | |
| /// black. If the pixmap buffer is larger than required, the buffer is truncated and its | |
| /// reserved capacity is unchanged. | |
| pub fn resize(&mut self, width: u16, height: u16) { | |
| let new_len = usize::from(width) * usize::from(height); | |
| // If we're growing, new pixels are transparent black | |
| if new_len > self.buf.len() { | |
| self.may_have_transparency = true; | |
| } | |
| self.width = width; | |
| self.height = height; | |
| self.buf.resize(new_len, PremulRgba8::from_u32(0)); | |
| } | |
| /// Shrink the capacity of the pixmap buffer to fit the pixmap's current size. | |
| pub fn shrink_to_fit(&mut self) { | |
| self.buf.shrink_to_fit(); | |
| } | |
| /// The reserved capacity (in pixels) of this pixmap. | |
| /// | |
| /// When calling [`Pixmap::resize`] with a `width * height` smaller than this value, the pixmap | |
| /// does not need to reallocate. | |
| pub fn capacity(&self) -> usize { | |
| self.buf.capacity() | |
| } | |
| /// Return the width of the pixmap. | |
| pub fn width(&self) -> u16 { | |
| self.width | |
| } | |
| /// Return the height of the pixmap. | |
| pub fn height(&self) -> u16 { | |
| self.height | |
| } | |
| /// Returns whether the pixmap may have non-opaque pixels. | |
| /// | |
| /// This value is computed at construction time. It may become stale if pixels are | |
| /// modified directly via [`data_mut()`](Self::data_mut), | |
| /// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel). | |
| /// | |
| /// Use [`set_may_have_transparency()`](Self::set_may_have_transparency) to manually update the flag, | |
| /// or [`recompute_may_have_transparency()`](Self::recompute_may_have_transparency) to recalculate it | |
| /// by scanning all pixels. | |
| pub fn may_have_transparency(&self) -> bool { | |
| self.may_have_transparency | |
| } | |
| /// Manually set the `may_have_transparency` flag. | |
| /// | |
| /// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or | |
| /// [`set_pixel()`](Self::set_pixel) when you know whether the image has | |
| /// non-opaque pixels. | |
| pub fn set_may_have_transparency(&mut self, may_have_transparency: bool) { | |
| self.may_have_transparency = may_have_transparency; | |
| } | |
| /// Recalculate `may_have_transparency` by scanning all pixels. | |
| /// | |
| /// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or | |
| /// [`set_pixel()`](Self::set_pixel) when you need accurate opacity information. | |
| pub fn recompute_may_have_transparency(&mut self) { | |
| self.may_have_transparency = self.buf.iter().any(|pixel| pixel.a != 255); | |
| } | |
| /// Apply an alpha value to the whole pixmap. | |
| pub fn multiply_alpha(&mut self, alpha: u8) { | |
| #[expect( | |
| clippy::cast_possible_truncation, | |
| reason = "cannot overflow in this case" | |
| )] | |
| let multiply = |component| ((u16::from(alpha) * u16::from(component)) / 255) as u8; | |
| for pixel in self.data_mut() { | |
| *pixel = PremulRgba8 { | |
| r: multiply(pixel.r), | |
| g: multiply(pixel.g), | |
| b: multiply(pixel.b), | |
| a: multiply(pixel.a), | |
| }; | |
| } | |
| // If we applied a non-opaque alpha, the image now has transparency | |
| if alpha != 255 { | |
| self.may_have_transparency = true; | |
| } | |
| } | |
| /// Create a pixmap from a PNG file. | |
| #[cfg(feature = "png")] | |
| pub fn from_png(data: impl BufRead + Seek) -> Result<Self, png::DecodingError> { | |
| let mut decoder = png::Decoder::new(data); | |
| decoder.set_transformations( | |
| png::Transformations::normalize_to_color8() | png::Transformations::ALPHA, | |
| ); | |
| let mut reader = decoder.read_info()?; | |
| let mut pixmap = { | |
| let info = reader.info(); | |
| let width: u16 = info | |
| .width | |
| .try_into() | |
| .map_err(|_| png::DecodingError::LimitsExceeded)?; | |
| let height: u16 = info | |
| .height | |
| .try_into() | |
| .map_err(|_| png::DecodingError::LimitsExceeded)?; | |
| Self::new(width, height) | |
| }; | |
| // Note `reader.info()` returns the pre-transformation color type output, whereas | |
| // `reader.output_color_type()` takes the transformation into account. | |
| let (color_type, bit_depth) = reader.output_color_type(); | |
| debug_assert_eq!( | |
| bit_depth, | |
| png::BitDepth::Eight, | |
| "normalize_to_color8 means the bit depth is always 8." | |
| ); | |
| match color_type { | |
| png::ColorType::Rgb | png::ColorType::Grayscale => { | |
| unreachable!("We set a transformation to always convert to alpha") | |
| } | |
| png::ColorType::Indexed => { | |
| unreachable!("Transformation should have expanded indexed images") | |
| } | |
| png::ColorType::Rgba => { | |
| debug_assert_eq!( | |
| Some(pixmap.data_as_u8_slice().len()), | |
| reader.output_buffer_size(), | |
| "The pixmap buffer should have the same number of bytes as the image." | |
| ); | |
| reader.next_frame(pixmap.data_as_u8_slice_mut())?; | |
| } | |
| png::ColorType::GrayscaleAlpha => { | |
| debug_assert_eq!( | |
| Some(pixmap.data().len() * 2), | |
| reader.output_buffer_size(), | |
| "The pixmap buffer should have twice the number of bytes of the grayscale image." | |
| ); | |
| let mut grayscale_data = vec![0; reader.output_buffer_size().unwrap_or_default()]; | |
| reader.next_frame(&mut grayscale_data)?; | |
| for (grayscale_pixel, pixmap_pixel) in | |
| grayscale_data.chunks_exact(2).zip(pixmap.data_mut()) | |
| { | |
| let [gray, alpha] = grayscale_pixel.try_into().unwrap(); | |
| *pixmap_pixel = PremulRgba8 { | |
| r: gray, | |
| g: gray, | |
| b: gray, | |
| a: alpha, | |
| }; | |
| } | |
| } | |
| }; | |
| pixmap.may_have_transparency = premultiply_rgba8(pixmap.data_as_u8_slice_mut()); | |
| Ok(pixmap) | |
| } | |
| /// Return the current content of the pixmap as a PNG. | |
| #[cfg(feature = "png")] | |
| pub fn into_png(self) -> Result<Vec<u8>, png::EncodingError> { | |
| let width = u32::from(self.width); | |
| let height = u32::from(self.height); | |
| let pixels = self.try_take_rgb8(ImageAlphaType::Alpha); | |
| let mut data = Vec::new(); | |
| let mut encoder = png::Encoder::new(&mut data, width, height); | |
| let (color_type, pixels) = match pixels { | |
| Pixels::Rgb8(pixels) => (png::ColorType::Rgb, pixels), | |
| Pixels::Rgba8(pixels) => (png::ColorType::Rgba, pixels), | |
| }; | |
| encoder.set_color(color_type); | |
| encoder.set_depth(png::BitDepth::Eight); | |
| let mut writer = encoder.write_header()?; | |
| writer.write_image_data(&pixels)?; | |
| writer.finish().map(|_| data) | |
| } | |
| /// Returns a reference to the underlying data as premultiplied RGBA8. | |
| /// | |
| /// The pixels are in row-major order. | |
| pub fn data(&self) -> &[PremulRgba8] { | |
| &self.buf | |
| } | |
| // TODO: Now that we have `as_mut`, maybe we don't need the | |
| // mutable methods. If we add a `PixmapRef` we can also remove the | |
| // non-mutable ones. | |
| /// Returns a mutable reference to the underlying data as premultiplied RGBA8. | |
| /// | |
| /// The pixels are in row-major order. | |
| pub fn data_mut(&mut self) -> &mut [PremulRgba8] { | |
| &mut self.buf | |
| } | |
| /// Returns a reference to the underlying data as premultiplied RGBA8. | |
| /// | |
| /// The pixels are in row-major order. Each pixel consists of four bytes in the order | |
| /// `[r, g, b, a]`. | |
| pub fn data_as_u8_slice(&self) -> &[u8] { | |
| bytemuck::cast_slice(&self.buf) | |
| } | |
| /// Returns a mutable reference to the underlying data as premultiplied RGBA8. | |
| /// | |
| /// The pixels are in row-major order. Each pixel consists of four bytes in the order | |
| /// `[r, g, b, a]`. | |
| pub fn data_as_u8_slice_mut(&mut self) -> &mut [u8] { | |
| bytemuck::cast_slice_mut(&mut self.buf) | |
| } | |
| /// Return a mutable view into this pixmap's pixel data. | |
| pub fn as_mut(&mut self) -> PixmapMut<'_> { | |
| PixmapMut { | |
| width: self.width, | |
| height: self.height, | |
| buf: bytemuck::cast_slice_mut(&mut self.buf), | |
| may_have_transparency: Some(&mut self.may_have_transparency), | |
| } | |
| } | |
| /// Sample a pixel from the pixmap. | |
| /// | |
| /// The pixel data is [premultiplied RGBA8][PremulRgba8]. | |
| #[inline(always)] | |
| pub fn sample(&self, x: u16, y: u16) -> PremulRgba8 { | |
| let idx = self.width as usize * y as usize + x as usize; | |
| self.buf[idx] | |
| } | |
| /// Sample a pixel from a custom-calculated index. This index should be calculated assuming that | |
| /// the data is stored in row-major order. | |
| #[inline(always)] | |
| pub fn sample_idx(&self, idx: u32) -> PremulRgba8 { | |
| self.buf[idx as usize] | |
| } | |
| /// Set a pixel in the pixmap at the given coordinates. | |
| /// | |
| /// The pixel data should be [premultiplied RGBA8][PremulRgba8]. The coordinate system has | |
| /// its origin at the top-left corner, with `x` increasing to the right and `y` increasing | |
| /// downward. | |
| #[inline(always)] | |
| pub fn set_pixel(&mut self, x: u16, y: u16, pixel: PremulRgba8) { | |
| let idx = self.width as usize * y as usize + x as usize; | |
| self.buf[idx] = pixel; | |
| } | |
| /// Consume the pixmap and return its raw RGBA8 pixel data with the given alpha representation. | |
| /// | |
| /// If both, premultiplied and unpremultiplied RGBA are acceptable formats, it is | |
| /// recommended to choose [`ImageAlphaType::AlphaPremultiplied`], as the data can | |
| /// be returned as is without any additional post-processing. | |
| pub fn take_rgba8(mut self, alpha_type: ImageAlphaType) -> Vec<u8> { | |
| if self.may_have_transparency && alpha_type == ImageAlphaType::Alpha { | |
| unpremultiply_rgba8(bytemuck::cast_slice_mut(&mut self.buf)); | |
| } | |
| bytemuck::cast_vec(self.buf) | |
| } | |
| /// Consume the pixmap and attempt to return its raw data as RGB8 pixel data. | |
| /// | |
| /// In case this is not possible (due to the pixmap containing non-opaque pixels), this method | |
| /// will fall back to returning the data as RGBA8 with the requested alpha representation. | |
| pub fn try_take_rgb8(mut self, alpha_type: ImageAlphaType) -> Pixels { | |
| let may_have_transparency = | |
| if self.may_have_transparency && alpha_type == ImageAlphaType::Alpha { | |
| unpremultiply_rgba8(bytemuck::cast_slice_mut(&mut self.buf)) | |
| } else { | |
| // TODO: Consider still doing a linear scan to redetermine whether the pixmap is opaque. | |
| self.may_have_transparency | |
| }; | |
| let mut data = bytemuck::cast_vec(self.buf); | |
| if may_have_transparency { | |
| Pixels::Rgba8(data) | |
| } else { | |
| rgba_to_rgb(&mut data); | |
| Pixels::Rgb8(data) | |
| } | |
| } | |
| } | |
| /// The result of attempting to extract RGB8 data from a [`Pixmap`]. | |
| #[derive(Clone, Debug, PartialEq, Eq)] | |
| pub enum Pixels { | |
| /// Three bytes per pixel in red, green, blue order. | |
| Rgb8(Vec<u8>), | |
| /// Four bytes per pixel in red, green, blue, alpha order. | |
| Rgba8(Vec<u8>), | |
| } | |
| /// Metadata about the pixels of an image. | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| pub struct PixelMetadata { | |
| /// Whether the pixels may be non-opaque. | |
| /// | |
| /// If unsure, always set this to `true`. Setting this to `false` is a strong guarantee that | |
| /// every pixel in the image **is guaranteed** to be opaque. Setting this to `false` mistakenly | |
| /// can lead to wrong rendering. | |
| pub may_have_transparency: bool, | |
| /// How the alpha channel is represented. | |
| pub alpha_type: ImageAlphaType, | |
| } | |
| impl PixelMetadata { | |
| /// Create a new pixel metadata description. | |
| pub const fn new(alpha_type: ImageAlphaType, may_have_transparency: bool) -> Self { | |
| Self { | |
| may_have_transparency, | |
| alpha_type, | |
| } | |
| } | |
| } | |
| impl Default for PixelMetadata { | |
| fn default() -> Self { | |
| Self::new(ImageAlphaType::AlphaPremultiplied, true) | |
| } | |
| } | |
| /// Turn the RGBA buffer into an RGB buffer, assuming that all pixels are fully | |
| /// opaque. | |
| fn rgba_to_rgb(data: &mut Vec<u8>) { | |
| let level = Level::try_detect().unwrap_or(Level::baseline()); | |
| dispatch!(level, simd => rgba_to_rgb_impl(simd, data)); | |
| } | |
| #[inline(always)] | |
| fn rgba_to_rgb_impl<S: Simd>(simd: S, data: &mut Vec<u8>) { | |
| const SWIZZLE_0: [u8; 16] = [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20]; | |
| const SWIZZLE_1: [u8; 16] = [5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24, 25]; | |
| const SWIZZLE_2: [u8; 16] = [ | |
| 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24, 25, 26, 28, 29, 30, | |
| ]; | |
| debug_assert!( | |
| data.len().is_multiple_of(4), | |
| "RGBA data length must be divisible by four" | |
| ); | |
| let indices_0 = u8x32::from_fn(simd, |lane| SWIZZLE_0[lane.min(15)]); | |
| let indices_1 = u8x32::from_fn(simd, |lane| SWIZZLE_1[lane.min(15)]); | |
| let indices_2 = u8x32::from_fn(simd, |lane| SWIZZLE_2[lane.min(15)]); | |
| let pixel_count = data.len() / 4; | |
| let block_count = pixel_count / 16; | |
| for block in 0..block_count { | |
| let src = block * 64; | |
| let dst = block * 48; | |
| let rgba_0123 = u8x64::from_slice(simd, &data[src..src + 64]); | |
| let (rgba_01, rgba_23) = rgba_0123.split(); | |
| let (_, rgba_1) = rgba_01.split(); | |
| let (rgba_2, _) = rgba_23.split(); | |
| let rgba_12 = rgba_1.combine(rgba_2); | |
| let (rgb_0, _) = rgba_01.swizzle_dyn(indices_0).split(); | |
| let (rgb_1, _) = rgba_12.swizzle_dyn(indices_1).split(); | |
| let (rgb_2, _) = rgba_23.swizzle_dyn(indices_2).split(); | |
| rgb_0.store_slice(&mut data[dst..dst + 16]); | |
| rgb_1.store_slice(&mut data[dst + 16..dst + 32]); | |
| rgb_2.store_slice(&mut data[dst + 32..dst + 48]); | |
| } | |
| for pixel in block_count * 16..pixel_count { | |
| let src = pixel * 4; | |
| let dst = pixel * 3; | |
| let r = data[src]; | |
| let g = data[src + 1]; | |
| let b = data[src + 2]; | |
| data[dst] = r; | |
| data[dst + 1] = g; | |
| data[dst + 2] = b; | |
| } | |
| data.truncate(pixel_count * 3); | |
| } | |
| /// Premultiplies each RGBA8 pixel in `data`. | |
| /// | |
| /// Returns `true` if at least one pixel is not fully opaque. | |
| pub fn premultiply_rgba8(data: &mut [u8]) -> bool { | |
| // Unfortunately we need to construct a custom level here and cannot use the one | |
| // from the Vello CPU / Vello GPU context. This does mean we are not testing | |
| // all possible combinations in CI, but the used intrinsics are very simple and | |
| // also used in other parts of the pipeline, so risk is very low. | |
| let level = Level::try_detect().unwrap_or(Level::baseline()); | |
| dispatch!(level, simd => premultiply_rgba8_impl(simd, data)) | |
| } | |
| /// Unpremultiplies each RGBA8 pixel in `data`. | |
| /// | |
| /// Returns `true` if at least one pixel is not fully opaque. | |
| pub fn unpremultiply_rgba8(data: &mut [u8]) -> bool { | |
| let level = Level::try_detect().unwrap_or(Level::baseline()); | |
| dispatch!(level, simd => unpremultiply_rgba8_impl(simd, data)) | |
| } | |
| #[inline(always)] | |
| fn unpremultiply_rgba8_impl<S: Simd>(simd: S, data: &mut [u8]) -> bool { | |
| let (body, tail) = data.as_chunks_mut::<64>(); | |
| let mut transparency = mask8x16::splat(simd, false); | |
| for chunk in body { | |
| let [r, g, b, a] = simd.load_four_interleaved_u8x16(chunk); | |
| transparency |= !a.simd_eq(255); | |
| let reciprocal = u16x16::from_fn(simd, |lane| unpremultiply::reciprocal(a[lane])); | |
| let r = unpremultiply::simd(simd, r, reciprocal); | |
| let g = unpremultiply::simd(simd, g, reciprocal); | |
| let b = unpremultiply::simd(simd, b, reciprocal); | |
| simd.store_four_interleaved_u8x16([r, g, b, a], chunk); | |
| } | |
| let mut may_have_transparency = transparency.any_true(); | |
| for pixel in tail.chunks_exact_mut(4) { | |
| may_have_transparency |= pixel[3] != 255; | |
| let reciprocal = unpremultiply::reciprocal(pixel[3]); | |
| for component in &mut pixel[..3] { | |
| *component = unpremultiply::scalar(*component, reciprocal); | |
| } | |
| } | |
| may_have_transparency | |
| } | |
| #[inline(always)] | |
| fn premultiply_rgba8_impl<S: Simd>(simd: S, data: &mut [u8]) -> bool { | |
| let (body, tail) = data.as_chunks_mut::<64>(); | |
| let mut transparency = mask8x16::splat(simd, false); | |
| for chunk in body { | |
| let [r, g, b, a] = simd.load_four_interleaved_u8x16(chunk); | |
| transparency |= !a.simd_eq(255); | |
| let premultiply = { | |
| #[inline(always)] | |
| |component: u8x16<S>| narrow((widen(component) * widen(a)).div_255()) | |
| }; | |
| simd.store_four_interleaved_u8x16( | |
| [premultiply(r), premultiply(g), premultiply(b), a], | |
| chunk, | |
| ); | |
| } | |
| let mut may_have_transparency = transparency.any_true(); | |
| for pixel in tail.chunks_exact_mut(4) { | |
| let alpha = u16::from(pixel[3]); | |
| may_have_transparency |= alpha != 255; | |
| let premultiply = |component| ((u16::from(component) * alpha + 255) >> 8) as u8; | |
| pixel[0] = premultiply(pixel[0]); | |
| pixel[1] = premultiply(pixel[1]); | |
| pixel[2] = premultiply(pixel[2]); | |
| } | |
| may_have_transparency | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use alloc::vec; | |
| use alloc::vec::Vec; | |
| use super::{PixelMetadata, Pixels, Pixmap}; | |
| use crate::peniko::ImageAlphaType; | |
| #[test] | |
| fn straight_alpha_is_premultiplied_in_body_and_tail() { | |
| let pixmap = Pixmap::from_parts( | |
| vec![ | |
| // SIMD body | |
| 200, 100, 50, 128, 128, 64, 32, 128, 255, 128, 64, 64, 255, 100, 1, 0, 64, 32, 16, | |
| 192, 10, 20, 30, 255, 240, 120, 60, 128, 80, 40, 20, 64, 100, 50, 25, 128, 32, 16, | |
| 8, 192, 200, 150, 100, 64, 3, 2, 1, 128, 254, 253, 252, 128, 1, 2, 3, 64, 127, 63, | |
| 31, 192, 9, 8, 7, 255, // Scalar tail | |
| 80, 40, 20, 64, | |
| ], | |
| 17, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::Alpha, true), | |
| ); | |
| assert!(pixmap.may_have_transparency()); | |
| assert_eq!( | |
| pixmap.data_as_u8_slice(), | |
| [ | |
| // SIMD body | |
| 100, 50, 25, 128, 64, 32, 16, 128, 64, 32, 16, 64, 0, 0, 0, 0, 48, 24, 12, 192, 10, | |
| 20, 30, 255, 120, 60, 30, 128, 20, 10, 5, 64, 50, 25, 13, 128, 24, 12, 6, 192, 50, | |
| 38, 25, 64, 2, 1, 1, 128, 127, 127, 126, 128, 1, 1, 1, 64, 96, 48, 24, 192, 9, 8, | |
| 7, 255, // Scalar tail | |
| 20, 10, 5, 64, | |
| ] | |
| ); | |
| } | |
| #[test] | |
| fn straight_alpha_is_premultiplied_with_only_tail() { | |
| let pixmap = Pixmap::from_parts( | |
| vec![200, 100, 50, 128, 9, 8, 7, 255], | |
| 2, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::Alpha, true), | |
| ); | |
| assert!(pixmap.may_have_transparency()); | |
| assert_eq!(pixmap.data_as_u8_slice(), [100, 50, 25, 128, 9, 8, 7, 255]); | |
| } | |
| #[test] | |
| fn straight_opaque_alpha_clears_transparency_hint_in_body_and_tail() { | |
| let data = vec![ | |
| // SIMD body | |
| 200, 100, 50, 255, 1, 2, 3, 255, 4, 5, 6, 255, 7, 8, 9, 255, 10, 11, 12, 255, 13, 14, | |
| 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255, 25, 26, 27, 255, 28, 29, | |
| 30, 255, 31, 32, 33, 255, 34, 35, 36, 255, 37, 38, 39, 255, 40, 41, 42, 255, 43, 44, | |
| 45, 255, // Scalar tail | |
| 80, 40, 20, 255, | |
| ]; | |
| let pixmap = Pixmap::from_parts( | |
| data.clone(), | |
| 17, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::Alpha, true), | |
| ); | |
| assert!(!pixmap.may_have_transparency()); | |
| assert_eq!(pixmap.data_as_u8_slice(), data); | |
| } | |
| #[test] | |
| fn straight_opaque_alpha_clears_transparency_hint_with_only_tail() { | |
| let data = vec![1, 2, 3, 255]; | |
| let pixmap = Pixmap::from_parts( | |
| data.clone(), | |
| 1, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::Alpha, true), | |
| ); | |
| assert!(!pixmap.may_have_transparency()); | |
| assert_eq!(pixmap.data_as_u8_slice(), data); | |
| } | |
| #[test] | |
| fn opaque_pixmap_compacts_to_rgb() { | |
| let rgba: Vec<u8> = (0_u8..33) | |
| .flat_map(|pixel| { | |
| [ | |
| pixel.wrapping_mul(3), | |
| pixel.wrapping_mul(5), | |
| pixel.wrapping_mul(7), | |
| 255, | |
| ] | |
| }) | |
| .collect(); | |
| let expected: Vec<u8> = rgba | |
| .chunks_exact(4) | |
| .flat_map(|pixel| pixel[..3].iter().copied()) | |
| .collect(); | |
| let pixmap = Pixmap::from_parts( | |
| rgba, | |
| 33, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::AlphaPremultiplied, false), | |
| ); | |
| let pixels = pixmap.try_take_rgb8(ImageAlphaType::Alpha); | |
| assert_eq!(pixels, Pixels::Rgb8(expected)); | |
| } | |
| #[test] | |
| fn transparent_pixmap_falls_back_to_rgba() { | |
| let pixmap = Pixmap::from_parts( | |
| vec![64, 32, 16, 128], | |
| 1, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::AlphaPremultiplied, true), | |
| ); | |
| let pixels = pixmap.try_take_rgb8(ImageAlphaType::Alpha); | |
| assert_eq!(pixels, Pixels::Rgba8(vec![128, 64, 32, 128])); | |
| } | |
| #[test] | |
| fn transparent_pixmap_falls_back_to_premultiplied_rgba() { | |
| let data = vec![64, 32, 16, 128]; | |
| let pixmap = Pixmap::from_parts( | |
| data.clone(), | |
| 1, | |
| 1, | |
| PixelMetadata::new(ImageAlphaType::AlphaPremultiplied, true), | |
| ); | |
| let pixels = pixmap.try_take_rgb8(ImageAlphaType::AlphaPremultiplied); | |
| assert_eq!(pixels, Pixels::Rgba8(data)); | |
| } | |
| } |