Skip to main content

valo_renderer/
pixels.rs

1//! CPU pixel descriptions accepted at the upload boundary.
2
3/// PixelFormat is the channel order of four eight-bit image components.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum PixelFormat {
6    /// Rgba8 stores red, green, blue and alpha in that order.
7    Rgba8,
8
9    /// Bgra8 stores blue, green, red and alpha in that order.
10    Bgra8,
11}
12
13/// AlphaType describes whether a pixel's color components include its alpha.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum AlphaType {
16    /// Premultiplied stores color multiplied by alpha.
17    Premultiplied,
18
19    /// Straight stores color independently of alpha.
20    Straight,
21}
22
23/// PixelLayout describes top-to-bottom rows of sRGB image samples.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct PixelLayout {
26    /// `size` is the image width and height in pixels.
27    pub size: [u32; 2],
28
29    /// `row_bytes` includes any padding between adjacent rows.
30    pub row_bytes: u32,
31
32    /// `format` is the channel order within each pixel.
33    pub format: PixelFormat,
34
35    /// `alpha` describes the color/alpha relationship.
36    pub alpha: AlphaType,
37}
38
39impl PixelLayout {
40    /// `packed` describes rows with no padding in the given format and alpha.
41    pub fn packed(size: [u32; 2], format: PixelFormat, alpha: AlphaType) -> Self {
42        Self {
43            size,
44            row_bytes: size[0].saturating_mul(4),
45            format,
46            alpha,
47        }
48    }
49
50    /// `byte_len` checks dimensions and row stride before computing storage size.
51    pub fn byte_len(self) -> Result<usize, ImageError> {
52        let width = self.size[0]
53            .checked_mul(4)
54            .ok_or(ImageError::InvalidLayout)?;
55        if self.size.contains(&0) || self.row_bytes < width {
56            return Err(ImageError::InvalidLayout);
57        }
58        usize::try_from(u64::from(self.row_bytes) * u64::from(self.size[1]))
59            .map_err(|_| ImageError::InvalidLayout)
60    }
61}
62
63/// PixelBuffer owns decoded CPU samples and their row layout.
64///
65/// Uploading consumes the allocation, normalizing channels and alpha in place before transfer,
66/// so a decoder hands over whatever layout it produced without an extra copy.
67pub struct PixelBuffer {
68    layout: PixelLayout,
69    pixels: Vec<u8>,
70}
71
72impl PixelBuffer {
73    /// `new` takes ownership of an existing CPU allocation without copying its contents.
74    pub fn new(layout: PixelLayout, pixels: Vec<u8>) -> Result<Self, ImageError> {
75        if pixels.len() != layout.byte_len()? {
76            return Err(ImageError::InvalidLayout);
77        }
78        Ok(Self { layout, pixels })
79    }
80
81    /// `layout` returns the dimensions and stride the samples follow.
82    pub fn layout(&self) -> PixelLayout {
83        self.layout
84    }
85
86    /// `bytes` returns the samples, rows `layout().row_bytes` apart.
87    pub fn bytes(&self) -> &[u8] {
88        &self.pixels
89    }
90
91    /// `into_bytes` returns the samples' allocation, rows `layout().row_bytes` apart.
92    pub fn into_bytes(self) -> Vec<u8> {
93        self.pixels
94    }
95
96    /// `into_premultiplied_rgba` compacts rows and converts channels and alpha in place.
97    pub(crate) fn into_premultiplied_rgba(self) -> Vec<u8> {
98        let Self { layout, mut pixels } = self;
99        let row = layout.size[0] as usize * 4;
100        for y in 0..layout.size[1] as usize {
101            let from = y * layout.row_bytes as usize;
102            pixels.copy_within(from..from + row, y * row);
103            normalize_row(&mut pixels[y * row..(y + 1) * row], layout);
104        }
105        pixels.truncate(row * layout.size[1] as usize);
106        pixels
107    }
108}
109
110fn normalize_row(row: &mut [u8], layout: PixelLayout) {
111    for pixel in row.as_chunks_mut::<4>().0 {
112        if layout.format == PixelFormat::Bgra8 {
113            pixel.swap(0, 2);
114        }
115        if layout.alpha == AlphaType::Straight {
116            premultiply(pixel);
117        }
118    }
119}
120
121fn premultiply(pixel: &mut [u8]) {
122    let alpha = u16::from(pixel[3]);
123    for channel in &mut pixel[..3] {
124        *channel = ((u16::from(*channel) * alpha + 127) / 255) as u8;
125    }
126}
127
128/// ImageError reports an invalid image layout or a texture the renderer cannot sample.
129#[derive(Clone, Debug, PartialEq, Eq)]
130pub enum ImageError {
131    /// InvalidLayout means dimensions, stride or buffer length do not describe an image.
132    InvalidLayout,
133
134    /// TooLarge means the image exceeds this device's texture limits.
135    TooLarge,
136
137    /// IncompatibleTexture means the texture cannot be sampled as a premultiplied sRGB image.
138    IncompatibleTexture,
139
140    /// WrongDevice means a native texture belongs to another GPU device.
141    WrongDevice,
142
143    /// UnsupportedBackend means this wgpu backend cannot import the native resource.
144    UnsupportedBackend,
145}
146
147impl std::fmt::Display for ImageError {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "{self:?}")
150    }
151}
152
153impl std::error::Error for ImageError {}