Skip to main content

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;
8#[cfg(feature = "png")]
9use std::io::{BufRead, Seek};
10
11use crate::peniko::color::{PremulRgba8, Rgba8};
12
13#[cfg(feature = "png")]
14extern crate std;
15
16/// A pixmap of premultiplied RGBA8 values backed by [`u8`][core::u8].
17#[derive(Debug, Clone)]
18pub struct Pixmap {
19    /// Width of the pixmap in pixels.  
20    width: u16,
21    /// Height of the pixmap in pixels.
22    height: u16,
23    /// Buffer of the pixmap in RGBA8 format.
24    buf: Vec<PremulRgba8>,
25    /// Whether the pixmap may have non-opaque pixels.
26    ///
27    /// Note: This may become stale if pixels are modified via [`data_mut()`](Self::data_mut),
28    /// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
29    may_have_transparency: bool,
30}
31
32/// A mutable view into premultiplied RGBA8 pixmap data.
33#[derive(Debug)]
34pub struct PixmapMut<'a> {
35    /// Width of the pixmap in pixels.
36    width: u16,
37    /// Height of the pixmap in pixels.
38    height: u16,
39    /// Buffer of the pixmap in RGBA8 format.
40    buf: &'a mut [u8],
41}
42
43impl<'a> PixmapMut<'a> {
44    /// Create a new mutable pixmap view.
45    ///
46    /// Returns `None` if `buf` is not exactly `width * height * 4` bytes long.
47    pub fn new(width: u16, height: u16, buf: &'a mut [u8]) -> Option<Self> {
48        if buf.len() == usize::from(width) * usize::from(height) * 4 {
49            Some(Self { width, height, buf })
50        } else {
51            None
52        }
53    }
54
55    /// Return the width of the pixmap.
56    pub fn width(&self) -> u16 {
57        self.width
58    }
59
60    /// Return the height of the pixmap.
61    pub fn height(&self) -> u16 {
62        self.height
63    }
64
65    /// Returns a mutable reference to the underlying data as premultiplied RGBA8 bytes.
66    pub fn data_mut(&mut self) -> &mut [u8] {
67        self.buf
68    }
69}
70
71impl<'a> From<&'a mut Pixmap> for PixmapMut<'a> {
72    fn from(pixmap: &'a mut Pixmap) -> Self {
73        pixmap.as_mut()
74    }
75}
76
77impl Pixmap {
78    /// Create a new pixmap with the given width and height in pixels.
79    ///
80    /// All pixels are initialized to transparent black.
81    pub fn new(width: u16, height: u16) -> Self {
82        let buf = vec![PremulRgba8::from_u32(0); width as usize * height as usize];
83        Self {
84            width,
85            height,
86            buf,
87            may_have_transparency: true,
88        }
89    }
90
91    /// Create a new pixmap with the given premultiplied RGBA8 data.
92    ///
93    /// The `data` vector must be of length `width * height` exactly.
94    ///
95    /// The pixels are in row-major order.
96    ///
97    /// This assumes the image may have transparent pixels. Use
98    /// [`from_parts_with_opacity`](Self::from_parts_with_opacity) if you already
99    /// know the opacity status to enable optimizations.
100    ///
101    /// # Panics
102    ///
103    /// Panics if the `data` vector is not of length `width * height`.
104    pub fn from_parts(data: Vec<PremulRgba8>, width: u16, height: u16) -> Self {
105        Self::from_parts_with_opacity(data, width, height, true)
106    }
107
108    /// Create a new pixmap with the given premultiplied RGBA8 data and precomputed opacity flag.
109    ///
110    /// The `data` vector must be of length `width * height` exactly.
111    ///
112    /// The pixels are in row-major order.
113    ///
114    /// Use this when you've already determined whether the data contains
115    /// non-opaque pixels to avoid redundant scanning.
116    ///
117    /// # Panics
118    ///
119    /// Panics if the `data` vector is not of length `width * height`.
120    pub fn from_parts_with_opacity(
121        data: Vec<PremulRgba8>,
122        width: u16,
123        height: u16,
124        may_have_transparency: bool,
125    ) -> Self {
126        assert_eq!(
127            data.len(),
128            usize::from(width) * usize::from(height),
129            "Expected `data` to have length of exactly `width * height`"
130        );
131        Self {
132            width,
133            height,
134            buf: data,
135            may_have_transparency,
136        }
137    }
138
139    /// Resizes the pixmap container to the given width and height; this does not resize the
140    /// contained image.
141    ///
142    /// If the pixmap buffer has to grow to fit the new size, those pixels are set to transparent
143    /// black. If the pixmap buffer is larger than required, the buffer is truncated and its
144    /// reserved capacity is unchanged.
145    pub fn resize(&mut self, width: u16, height: u16) {
146        let new_len = usize::from(width) * usize::from(height);
147        // If we're growing, new pixels are transparent black
148        if new_len > self.buf.len() {
149            self.may_have_transparency = true;
150        }
151        self.width = width;
152        self.height = height;
153        self.buf.resize(new_len, PremulRgba8::from_u32(0));
154    }
155
156    /// Shrink the capacity of the pixmap buffer to fit the pixmap's current size.
157    pub fn shrink_to_fit(&mut self) {
158        self.buf.shrink_to_fit();
159    }
160
161    /// The reserved capacity (in pixels) of this pixmap.
162    ///
163    /// When calling [`Pixmap::resize`] with a `width * height` smaller than this value, the pixmap
164    /// does not need to reallocate.
165    pub fn capacity(&self) -> usize {
166        self.buf.capacity()
167    }
168
169    /// Return the width of the pixmap.
170    pub fn width(&self) -> u16 {
171        self.width
172    }
173
174    /// Return the height of the pixmap.
175    pub fn height(&self) -> u16 {
176        self.height
177    }
178
179    /// Returns whether the pixmap may have non-opaque pixels.
180    ///
181    /// This value is computed at construction time. It may become stale if pixels are
182    /// modified directly via [`data_mut()`](Self::data_mut),
183    /// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
184    ///
185    /// Use [`set_may_have_transparency()`](Self::set_may_have_transparency) to manually update the flag,
186    /// or [`recompute_may_have_transparency()`](Self::recompute_may_have_transparency) to recalculate it
187    /// by scanning all pixels.
188    pub fn may_have_transparency(&self) -> bool {
189        self.may_have_transparency
190    }
191
192    /// Manually set the `may_have_transparency` flag.
193    ///
194    /// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
195    /// [`set_pixel()`](Self::set_pixel) when you know whether the image has
196    /// non-opaque pixels.
197    pub fn set_may_have_transparency(&mut self, may_have_transparency: bool) {
198        self.may_have_transparency = may_have_transparency;
199    }
200
201    /// Recalculate `may_have_transparency` by scanning all pixels.
202    ///
203    /// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
204    /// [`set_pixel()`](Self::set_pixel) when you need accurate opacity information.
205    pub fn recompute_may_have_transparency(&mut self) {
206        self.may_have_transparency = self.buf.iter().any(|pixel| pixel.a != 255);
207    }
208
209    /// Apply an alpha value to the whole pixmap.
210    pub fn multiply_alpha(&mut self, alpha: u8) {
211        #[expect(
212            clippy::cast_possible_truncation,
213            reason = "cannot overflow in this case"
214        )]
215        let multiply = |component| ((u16::from(alpha) * u16::from(component)) / 255) as u8;
216
217        for pixel in self.data_mut() {
218            *pixel = PremulRgba8 {
219                r: multiply(pixel.r),
220                g: multiply(pixel.g),
221                b: multiply(pixel.b),
222                a: multiply(pixel.a),
223            };
224        }
225
226        // If we applied a non-opaque alpha, the image now has transparency
227        if alpha != 255 {
228            self.may_have_transparency = true;
229        }
230    }
231
232    /// Create a pixmap from a PNG file.
233    #[cfg(feature = "png")]
234    pub fn from_png(data: impl BufRead + Seek) -> Result<Self, png::DecodingError> {
235        let mut decoder = png::Decoder::new(data);
236        decoder.set_transformations(
237            png::Transformations::normalize_to_color8() | png::Transformations::ALPHA,
238        );
239
240        let mut reader = decoder.read_info()?;
241        let mut pixmap = {
242            let info = reader.info();
243            let width: u16 = info
244                .width
245                .try_into()
246                .map_err(|_| png::DecodingError::LimitsExceeded)?;
247            let height: u16 = info
248                .height
249                .try_into()
250                .map_err(|_| png::DecodingError::LimitsExceeded)?;
251            Self::new(width, height)
252        };
253
254        // Note `reader.info()` returns the pre-transformation color type output, whereas
255        // `reader.output_color_type()` takes the transformation into account.
256        let (color_type, bit_depth) = reader.output_color_type();
257        debug_assert_eq!(
258            bit_depth,
259            png::BitDepth::Eight,
260            "normalize_to_color8 means the bit depth is always 8."
261        );
262
263        match color_type {
264            png::ColorType::Rgb | png::ColorType::Grayscale => {
265                unreachable!("We set a transformation to always convert to alpha")
266            }
267            png::ColorType::Indexed => {
268                unreachable!("Transformation should have expanded indexed images")
269            }
270            png::ColorType::Rgba => {
271                debug_assert_eq!(
272                    Some(pixmap.data_as_u8_slice().len()),
273                    reader.output_buffer_size(),
274                    "The pixmap buffer should have the same number of bytes as the image."
275                );
276                reader.next_frame(pixmap.data_as_u8_slice_mut())?;
277            }
278            png::ColorType::GrayscaleAlpha => {
279                debug_assert_eq!(
280                    Some(pixmap.data().len() * 2),
281                    reader.output_buffer_size(),
282                    "The pixmap buffer should have twice the number of bytes of the grayscale image."
283                );
284                let mut grayscale_data = vec![0; reader.output_buffer_size().unwrap_or_default()];
285                reader.next_frame(&mut grayscale_data)?;
286
287                for (grayscale_pixel, pixmap_pixel) in
288                    grayscale_data.chunks_exact(2).zip(pixmap.data_mut())
289                {
290                    let [gray, alpha] = grayscale_pixel.try_into().unwrap();
291                    *pixmap_pixel = PremulRgba8 {
292                        r: gray,
293                        g: gray,
294                        b: gray,
295                        a: alpha,
296                    };
297                }
298            }
299        };
300
301        let mut may_have_transparency = false;
302        for pixel in pixmap.data_mut() {
303            let alpha = pixel.a;
304            if alpha != 255 {
305                may_have_transparency = true;
306            }
307            let alpha_u16 = u16::from(alpha);
308            #[expect(
309                clippy::cast_possible_truncation,
310                reason = "Overflow should be impossible."
311            )]
312            let premultiply = |e: u8| ((u16::from(e) * alpha_u16) / 255) as u8;
313            pixel.r = premultiply(pixel.r);
314            pixel.g = premultiply(pixel.g);
315            pixel.b = premultiply(pixel.b);
316        }
317        pixmap.may_have_transparency = may_have_transparency;
318
319        Ok(pixmap)
320    }
321
322    /// Return the current content of the pixmap as a PNG.
323    #[cfg(feature = "png")]
324    pub fn into_png(self) -> Result<Vec<u8>, png::EncodingError> {
325        let mut data = Vec::new();
326        let mut encoder = png::Encoder::new(&mut data, self.width as u32, self.height as u32);
327        encoder.set_color(png::ColorType::Rgba);
328        encoder.set_depth(png::BitDepth::Eight);
329        let mut writer = encoder.write_header()?;
330        writer.write_image_data(bytemuck::cast_slice(&self.take_unpremultiplied()))?;
331        writer.finish().map(|_| data)
332    }
333
334    /// Returns a reference to the underlying data as premultiplied RGBA8.
335    ///
336    /// The pixels are in row-major order.
337    pub fn data(&self) -> &[PremulRgba8] {
338        &self.buf
339    }
340
341    // TODO: Now that we have `as_mut`, maybe we don't need the
342    // mutable methods. If we add a `PixmapRef` we can also remove the
343    // non-mutable ones.
344
345    /// Returns a mutable reference to the underlying data as premultiplied RGBA8.
346    ///
347    /// The pixels are in row-major order.
348    pub fn data_mut(&mut self) -> &mut [PremulRgba8] {
349        &mut self.buf
350    }
351
352    /// Returns a reference to the underlying data as premultiplied RGBA8.
353    ///
354    /// The pixels are in row-major order. Each pixel consists of four bytes in the order
355    /// `[r, g, b, a]`.
356    pub fn data_as_u8_slice(&self) -> &[u8] {
357        bytemuck::cast_slice(&self.buf)
358    }
359
360    /// Returns a mutable reference to the underlying data as premultiplied RGBA8.
361    ///
362    /// The pixels are in row-major order. Each pixel consists of four bytes in the order
363    /// `[r, g, b, a]`.
364    pub fn data_as_u8_slice_mut(&mut self) -> &mut [u8] {
365        bytemuck::cast_slice_mut(&mut self.buf)
366    }
367
368    /// Return a mutable view into this pixmap's pixel data.
369    pub fn as_mut(&mut self) -> PixmapMut<'_> {
370        PixmapMut {
371            width: self.width,
372            height: self.height,
373            buf: bytemuck::cast_slice_mut(&mut self.buf),
374        }
375    }
376
377    /// Sample a pixel from the pixmap.
378    ///
379    /// The pixel data is [premultiplied RGBA8][PremulRgba8].
380    #[inline(always)]
381    pub fn sample(&self, x: u16, y: u16) -> PremulRgba8 {
382        let idx = self.width as usize * y as usize + x as usize;
383        self.buf[idx]
384    }
385
386    /// Sample a pixel from a custom-calculated index. This index should be calculated assuming that
387    /// the data is stored in row-major order.
388    #[inline(always)]
389    pub fn sample_idx(&self, idx: u32) -> PremulRgba8 {
390        self.buf[idx as usize]
391    }
392
393    /// Set a pixel in the pixmap at the given coordinates.
394    ///
395    /// The pixel data should be [premultiplied RGBA8][PremulRgba8]. The coordinate system has
396    /// its origin at the top-left corner, with `x` increasing to the right and `y` increasing
397    /// downward.
398    #[inline(always)]
399    pub fn set_pixel(&mut self, x: u16, y: u16, pixel: PremulRgba8) {
400        let idx = self.width as usize * y as usize + x as usize;
401        self.buf[idx] = pixel;
402    }
403
404    /// Consume the pixmap, returning the data as the underlying [`Vec`] of premultiplied RGBA8.
405    ///
406    /// The pixels are in row-major order.
407    pub fn take(self) -> Vec<PremulRgba8> {
408        self.buf
409    }
410
411    /// Consume the pixmap, returning the data as (unpremultiplied) RGBA8.
412    ///
413    /// Not fast, but useful for saving to PNG etc.
414    ///
415    /// The pixels are in row-major order.
416    pub fn take_unpremultiplied(self) -> Vec<Rgba8> {
417        self.buf
418            .into_iter()
419            .map(|PremulRgba8 { r, g, b, a }| {
420                let alpha = 255.0 / f32::from(a);
421                if a != 0 {
422                    #[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
423                    let unpremultiply = |component| (f32::from(component) * alpha + 0.5) as u8;
424                    Rgba8 {
425                        r: unpremultiply(r),
426                        g: unpremultiply(g),
427                        b: unpremultiply(b),
428                        a,
429                    }
430                } else {
431                    Rgba8 { r, g, b, a }
432                }
433            })
434            .collect()
435    }
436}