vello_common/
pixmap.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A simple pixmap type.
5
6use alloc::vec;
7use alloc::vec::Vec;
8use peniko::color::Rgba8;
9
10use crate::peniko::color::PremulRgba8;
11
12#[cfg(feature = "png")]
13extern crate std;
14
15/// A pixmap of premultiplied RGBA8 values backed by [`u8`][core::u8].
16#[derive(Debug, Clone)]
17pub struct Pixmap {
18    /// Width of the pixmap in pixels.  
19    width: u16,
20    /// Height of the pixmap in pixels.
21    height: u16,
22    /// Buffer of the pixmap in RGBA8 format.
23    buf: Vec<PremulRgba8>,
24}
25
26impl Pixmap {
27    /// Create a new pixmap with the given width and height in pixels.
28    pub fn new(width: u16, height: u16) -> Self {
29        let buf = vec![PremulRgba8::from_u32(0); width as usize * height as usize];
30        Self { width, height, buf }
31    }
32
33    /// Create a new pixmap with the given premultiplied RGBA8 data.
34    ///
35    /// The `data` vector must be of length `width * height` exactly.
36    ///
37    /// The pixels are in row-major order.
38    ///
39    /// # Panics
40    ///
41    /// Panics if the `data` vector is not of length `width * height`.
42    pub fn from_parts(data: Vec<PremulRgba8>, width: u16, height: u16) -> Self {
43        assert_eq!(
44            data.len(),
45            usize::from(width) * usize::from(height),
46            "Expected `data` to have length of exactly `width * height`"
47        );
48        Self {
49            width,
50            height,
51            buf: data,
52        }
53    }
54
55    /// Resizes the pixmap container to the given width and height; this does not resize the
56    /// contained image.
57    ///
58    /// If the pixmap buffer has to grow to fit the new size, those pixels are set to transparent
59    /// black. If the pixmap buffer is larger than required, the buffer is truncated and its
60    /// reserved capacity is unchanged.
61    pub fn resize(&mut self, width: u16, height: u16) {
62        self.width = width;
63        self.height = height;
64        self.buf.resize(
65            usize::from(width) * usize::from(height),
66            PremulRgba8::from_u32(0),
67        );
68    }
69
70    /// Shrink the capacity of the pixmap buffer to fit the pixmap's current size.
71    pub fn shrink_to_fit(&mut self) {
72        self.buf.shrink_to_fit();
73    }
74
75    /// The reserved capacity (in pixels) of this pixmap.
76    ///
77    /// When calling [`Pixmap::resize`] with a `width * height` smaller than this value, the pixmap
78    /// does not need to reallocate.
79    pub fn capacity(&self) -> usize {
80        self.buf.capacity()
81    }
82
83    /// Return the width of the pixmap.
84    pub fn width(&self) -> u16 {
85        self.width
86    }
87
88    /// Return the height of the pixmap.
89    pub fn height(&self) -> u16 {
90        self.height
91    }
92
93    /// Apply an alpha value to the whole pixmap.
94    pub fn multiply_alpha(&mut self, alpha: u8) {
95        #[expect(
96            clippy::cast_possible_truncation,
97            reason = "cannot overflow in this case"
98        )]
99        let multiply = |component| ((u16::from(alpha) * u16::from(component)) / 255) as u8;
100
101        for pixel in self.data_mut() {
102            *pixel = PremulRgba8 {
103                r: multiply(pixel.r),
104                g: multiply(pixel.g),
105                b: multiply(pixel.b),
106                a: multiply(pixel.a),
107            };
108        }
109    }
110
111    /// Create a pixmap from a PNG file.
112    #[cfg(feature = "png")]
113    pub fn from_png(data: impl std::io::Read) -> Result<Self, png::DecodingError> {
114        let mut decoder = png::Decoder::new(data);
115        decoder.set_transformations(
116            png::Transformations::normalize_to_color8() | png::Transformations::ALPHA,
117        );
118
119        let mut reader = decoder.read_info()?;
120        let mut pixmap = {
121            let info = reader.info();
122            let width: u16 = info
123                .width
124                .try_into()
125                .map_err(|_| png::DecodingError::LimitsExceeded)?;
126            let height: u16 = info
127                .height
128                .try_into()
129                .map_err(|_| png::DecodingError::LimitsExceeded)?;
130            Self::new(width, height)
131        };
132
133        // Note `reader.info()` returns the pre-transformation color type output, whereas
134        // `reader.output_color_type()` takes the transformation into account.
135        let (color_type, bit_depth) = reader.output_color_type();
136        debug_assert_eq!(
137            bit_depth,
138            png::BitDepth::Eight,
139            "normalize_to_color8 means the bit depth is always 8."
140        );
141
142        match color_type {
143            png::ColorType::Rgb | png::ColorType::Grayscale => {
144                unreachable!("We set a transformation to always convert to alpha")
145            }
146            png::ColorType::Indexed => {
147                unreachable!("Transformation should have expanded indexed images")
148            }
149            png::ColorType::Rgba => {
150                debug_assert_eq!(
151                    pixmap.data_as_u8_slice().len(),
152                    reader.output_buffer_size(),
153                    "The pixmap buffer should have the same number of bytes as the image."
154                );
155                reader.next_frame(pixmap.data_as_u8_slice_mut())?;
156            }
157            png::ColorType::GrayscaleAlpha => {
158                debug_assert_eq!(
159                    pixmap.data().len() * 2,
160                    reader.output_buffer_size(),
161                    "The pixmap buffer should have twice the number of bytes of the grayscale image."
162                );
163                let mut grayscale_data = vec![0; reader.output_buffer_size()];
164                reader.next_frame(&mut grayscale_data)?;
165
166                for (grayscale_pixel, pixmap_pixel) in
167                    grayscale_data.chunks_exact(2).zip(pixmap.data_mut())
168                {
169                    let [gray, alpha] = grayscale_pixel.try_into().unwrap();
170                    *pixmap_pixel = PremulRgba8 {
171                        r: gray,
172                        g: gray,
173                        b: gray,
174                        a: alpha,
175                    };
176                }
177            }
178        };
179
180        for pixel in pixmap.data_mut() {
181            let alpha = u16::from(pixel.a);
182            #[expect(
183                clippy::cast_possible_truncation,
184                reason = "Overflow should be impossible."
185            )]
186            let premultiply = |e: u8| ((u16::from(e) * alpha) / 255) as u8;
187            pixel.r = premultiply(pixel.r);
188            pixel.g = premultiply(pixel.g);
189            pixel.b = premultiply(pixel.b);
190        }
191
192        Ok(pixmap)
193    }
194
195    /// Return the current content of the pixmap as a PNG.
196    #[cfg(feature = "png")]
197    pub fn into_png(self) -> Result<Vec<u8>, png::EncodingError> {
198        let mut data = Vec::new();
199        let mut encoder = png::Encoder::new(&mut data, self.width as u32, self.height as u32);
200        encoder.set_color(png::ColorType::Rgba);
201        encoder.set_depth(png::BitDepth::Eight);
202        let mut writer = encoder.write_header()?;
203        writer.write_image_data(bytemuck::cast_slice(&self.take_unpremultiplied()))?;
204        writer.finish().map(|_| data)
205    }
206
207    /// Returns a reference to the underlying data as premultiplied RGBA8.
208    ///
209    /// The pixels are in row-major order.
210    pub fn data(&self) -> &[PremulRgba8] {
211        &self.buf
212    }
213
214    /// Returns a mutable reference to the underlying data as premultiplied RGBA8.
215    ///
216    /// The pixels are in row-major order.
217    pub fn data_mut(&mut self) -> &mut [PremulRgba8] {
218        &mut self.buf
219    }
220
221    /// Returns a reference to the underlying data as premultiplied RGBA8.
222    ///
223    /// The pixels are in row-major order. Each pixel consists of four bytes in the order
224    /// `[r, g, b, a]`.
225    pub fn data_as_u8_slice(&self) -> &[u8] {
226        bytemuck::cast_slice(&self.buf)
227    }
228
229    /// Returns a mutable reference to the underlying data as premultiplied RGBA8.
230    ///
231    /// The pixels are in row-major order. Each pixel consists of four bytes in the order
232    /// `[r, g, b, a]`.
233    pub fn data_as_u8_slice_mut(&mut self) -> &mut [u8] {
234        bytemuck::cast_slice_mut(&mut self.buf)
235    }
236
237    /// Sample a pixel from the pixmap.
238    ///
239    /// The pixel data is [premultiplied RGBA8][PremulRgba8].
240    #[inline(always)]
241    pub fn sample(&self, x: u16, y: u16) -> PremulRgba8 {
242        let idx = self.width as usize * y as usize + x as usize;
243        self.buf[idx]
244    }
245
246    /// Sample a pixel from a custom-calculated index. This index should be calculated assuming that
247    /// the data is stored in row-major order.
248    #[inline(always)]
249    pub fn sample_idx(&self, idx: u32) -> PremulRgba8 {
250        self.buf[idx as usize]
251    }
252
253    /// Consume the pixmap, returning the data as the underlying [`Vec`] of premultiplied RGBA8.
254    ///
255    /// The pixels are in row-major order.
256    pub fn take(self) -> Vec<PremulRgba8> {
257        self.buf
258    }
259
260    /// Consume the pixmap, returning the data as (unpremultiplied) RGBA8.
261    ///
262    /// Not fast, but useful for saving to PNG etc.
263    ///
264    /// The pixels are in row-major order.
265    pub fn take_unpremultiplied(self) -> Vec<Rgba8> {
266        self.buf
267            .into_iter()
268            .map(|PremulRgba8 { r, g, b, a }| {
269                let alpha = 255.0 / f32::from(a);
270                if a != 0 {
271                    #[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
272                    let unpremultiply = |component| (f32::from(component) * alpha + 0.5) as u8;
273                    Rgba8 {
274                        r: unpremultiply(r),
275                        g: unpremultiply(g),
276                        b: unpremultiply(b),
277                        a,
278                    }
279                } else {
280                    Rgba8 { r, g, b, a }
281                }
282            })
283            .collect()
284    }
285}