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    /// `into_premultiplied_rgba` compacts rows and converts channels and alpha in place.
87    pub(crate) fn into_premultiplied_rgba(self) -> Vec<u8> {
88        let Self { layout, mut pixels } = self;
89        let row = layout.size[0] as usize * 4;
90        for y in 0..layout.size[1] as usize {
91            let from = y * layout.row_bytes as usize;
92            pixels.copy_within(from..from + row, y * row);
93            normalize_row(&mut pixels[y * row..(y + 1) * row], layout);
94        }
95        pixels.truncate(row * layout.size[1] as usize);
96        pixels
97    }
98}
99
100fn normalize_row(row: &mut [u8], layout: PixelLayout) {
101    for pixel in row.as_chunks_mut::<4>().0 {
102        if layout.format == PixelFormat::Bgra8 {
103            pixel.swap(0, 2);
104        }
105        if layout.alpha == AlphaType::Straight {
106            premultiply(pixel);
107        }
108    }
109}
110
111fn premultiply(pixel: &mut [u8]) {
112    let alpha = u16::from(pixel[3]);
113    for channel in &mut pixel[..3] {
114        *channel = ((u16::from(*channel) * alpha + 127) / 255) as u8;
115    }
116}
117
118/// ImageError reports an invalid image layout or a texture the renderer cannot sample.
119#[derive(Clone, Debug, PartialEq, Eq)]
120pub enum ImageError {
121    /// InvalidLayout means dimensions, stride or buffer length do not describe an image.
122    InvalidLayout,
123
124    /// TooLarge means the image exceeds this device's texture limits.
125    TooLarge,
126
127    /// IncompatibleTexture means the texture cannot be sampled as a premultiplied sRGB image.
128    IncompatibleTexture,
129
130    /// WrongDevice means a native texture belongs to another GPU device.
131    WrongDevice,
132
133    /// UnsupportedBackend means this wgpu backend cannot import the native resource.
134    UnsupportedBackend,
135}
136
137impl std::fmt::Display for ImageError {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{self:?}")
140    }
141}
142
143impl std::error::Error for ImageError {}