blob: a62d355e125d4d6018703c80054bd908245a0e1d [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! A simple pixmap type.
use alloc::vec;
use alloc::vec::Vec;
/// A pixmap backed by u8.
#[derive(Debug, Clone)]
pub struct Pixmap {
/// Width of the pixmap in pixels.
pub width: u16,
/// Height of the pixmap in pixels.
pub height: u16,
/// Buffer of the pixmap in RGBA format.
pub buf: Vec<u8>,
}
impl Pixmap {
/// Create a new pixmap with the given width and height in pixels.
pub fn new(width: u16, height: u16) -> Self {
let buf = vec![0; width as usize * height as usize * 4];
Self { width, height, buf }
}
/// 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
}
#[allow(
clippy::cast_possible_truncation,
reason = "cannot overflow in this case"
)]
/// Apply an alpha value to the whole pixmap.
pub fn multiply_alpha(&mut self, alpha: u8) {
for comp in self.data_mut() {
*comp = ((alpha as u16 * *comp as u16) / 255) as u8;
}
}
/// Create a pixmap from a PNG file.
#[cfg(feature = "png")]
#[allow(
clippy::cast_possible_truncation,
reason = "cannot overflow in this case"
)]
pub fn from_png(data: &[u8]) -> Result<Self, png::DecodingError> {
let mut decoder = png::Decoder::new(data);
decoder.set_transformations(png::Transformations::ALPHA);
let mut reader = decoder.read_info()?;
let mut img_data = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut img_data)?;
let decoded_data = match info.color_type {
// We set a transformation to always convert to alpha.
png::ColorType::Rgb => unreachable!(),
png::ColorType::Grayscale => unreachable!(),
// I believe the above transformation also expands indexed images.
png::ColorType::Indexed => unreachable!(),
png::ColorType::Rgba => img_data,
png::ColorType::GrayscaleAlpha => {
let mut rgba_data = Vec::with_capacity(img_data.len() * 2);
for slice in img_data.chunks(2) {
let gray = slice[0];
let alpha = slice[1];
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(gray);
rgba_data.push(alpha);
}
rgba_data
}
};
let premultiplied = decoded_data
.chunks_exact(4)
.flat_map(|d| {
let alpha = d[3] as u16;
let premultiply = |e: u8| ((e as u16 * alpha) / 255) as u8;
if alpha == 0 {
[0, 0, 0, 0]
} else {
[
premultiply(d[0]),
premultiply(d[1]),
premultiply(d[2]),
d[3],
]
}
})
.collect::<Vec<_>>();
Ok(Self {
width: info.width as u16,
height: info.height as u16,
buf: premultiplied,
})
}
/// Returns a reference to the underlying data as premultiplied RGBA8.
pub fn data(&self) -> &[u8] {
&self.buf
}
/// Returns a mutable reference to the underlying data as premultiplied RGBA8.
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.buf
}
/// Sample a pixel from the pixmap.
#[inline(always)]
pub fn sample(&self, x: u16, y: u16) -> &[u8] {
let idx = 4 * (self.width as usize * y as usize + x as usize);
&self.buf[idx..][..4]
}
/// Convert from premultiplied to separate alpha.
///
/// Not fast, but useful for saving to PNG etc.
#[allow(
clippy::cast_possible_truncation,
reason = "cannot overflow in this case"
)]
pub fn unpremultiply(&mut self) {
for rgba in self.buf.chunks_exact_mut(4) {
let alpha = 255.0 / rgba[3] as f32;
if rgba[3] != 0 {
rgba[0] = (rgba[0] as f32 * alpha + 0.5) as u8;
rgba[1] = (rgba[1] as f32 * alpha + 0.5) as u8;
rgba[2] = (rgba[2] as f32 * alpha + 0.5) as u8;
}
}
}
}