vello_common/
paint.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Types for paints.
5
6use crate::pixmap::Pixmap;
7use alloc::sync::Arc;
8use peniko::{
9    Gradient, ImageQuality,
10    color::{AlphaColor, PremulRgba8, Srgb},
11};
12
13/// A paint that needs to be resolved via its index.
14// In the future, we might add additional flags, that's why we have
15// this thin wrapper around u32, so we can change the underlying
16// representation without breaking the API.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct IndexedPaint(u32);
19
20impl IndexedPaint {
21    /// Create a new indexed paint from an index.
22    pub fn new(index: usize) -> Self {
23        Self(u32::try_from(index).expect("exceeded the maximum number of paints"))
24    }
25
26    /// Return the index of the paint.
27    pub fn index(&self) -> usize {
28        usize::try_from(self.0).unwrap()
29    }
30}
31
32/// A paint that is used internally by a rendering frontend to store how a wide tile command
33/// should be painted. There are only two types of paint:
34///
35/// 1) Simple solid colors, which are stored in premultiplied representation so that
36///    each wide tile doesn't have to recompute it.
37/// 2) Indexed paints, which can represent any arbitrary, more complex paint that is
38///    determined by the frontend. The intended way of using this is to store a vector
39///    of paints and store its index inside `IndexedPaint`.
40#[derive(Debug, Clone, PartialEq)]
41pub enum Paint {
42    /// A premultiplied RGBA8 color.
43    Solid(PremulColor),
44    /// A paint that needs to be resolved via an index.
45    Indexed(IndexedPaint),
46}
47
48impl From<AlphaColor<Srgb>> for Paint {
49    fn from(value: AlphaColor<Srgb>) -> Self {
50        Self::Solid(PremulColor::from_alpha_color(value))
51    }
52}
53
54/// Opaque image handle
55#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
56pub struct ImageId(u32);
57
58impl ImageId {
59    // TODO: make this private in future
60    /// Create a new image id from a u32.
61    pub fn new(value: u32) -> Self {
62        Self(value)
63    }
64
65    /// Return the image id as a u32.
66    pub fn as_u32(&self) -> u32 {
67        self.0
68    }
69}
70
71/// Bitmap source used by `Image`.
72#[derive(Debug, Clone)]
73pub enum ImageSource {
74    /// Pixmap pixels travel with the scene packet.
75    Pixmap(Arc<Pixmap>),
76    /// Pixmap pixels were registered earlier; this is just a handle.
77    OpaqueId(ImageId),
78}
79
80/// An image.
81#[derive(Debug, Clone)]
82pub struct Image {
83    /// The underlying pixmap of the image.
84    pub source: ImageSource,
85    /// Extend mode in the horizontal direction.
86    pub x_extend: peniko::Extend,
87    /// Extend mode in the vertical direction.
88    pub y_extend: peniko::Extend,
89    /// Hint for desired rendering quality.
90    pub quality: ImageQuality,
91}
92
93impl Image {
94    /// Convert a [`peniko::Image`] to an [`Image`].
95    ///
96    /// This is a somewhat lossy conversion, as the image data data is transformed to
97    /// [premultiplied RGBA8](`PremulRgba8`).
98    ///
99    /// # Panics
100    ///
101    /// This panics if `image` has a `width` or `height` greater than `u16::MAX`.
102    pub fn from_peniko_image(image: &peniko::Image) -> Self {
103        // TODO: how do we deal with `peniko::ImageFormat` growing? See also
104        // <https://github.com/linebender/vello/pull/996#discussion_r2080510863>.
105        if image.format != peniko::ImageFormat::Rgba8 {
106            unimplemented!("Unsupported image format: {:?}", image.format);
107        }
108
109        assert!(
110            image.width <= u16::MAX as u32 && image.height <= u16::MAX as u32,
111            "The image is too big. Its width and height can be no larger than {} pixels.",
112            u16::MAX,
113        );
114        let width = image.width.try_into().unwrap();
115        let height = image.height.try_into().unwrap();
116
117        #[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
118        let global_alpha = u16::from((image.alpha * 255. + 0.5) as u8);
119
120        #[expect(clippy::cast_possible_truncation, reason = "This cannot overflow.")]
121        let pixels = image
122            .data
123            .data()
124            .chunks_exact(4)
125            .map(|rgba| {
126                let alpha = ((u16::from(rgba[3]) * global_alpha) / 255) as u8;
127                let multiply = |component| ((u16::from(alpha) * u16::from(component)) / 255) as u8;
128                PremulRgba8 {
129                    r: multiply(rgba[0]),
130                    g: multiply(rgba[1]),
131                    b: multiply(rgba[2]),
132                    a: alpha,
133                }
134            })
135            .collect();
136        let pixmap = Pixmap::from_parts(pixels, width, height);
137
138        Self {
139            source: ImageSource::Pixmap(Arc::new(pixmap)),
140            x_extend: image.x_extend,
141            y_extend: image.y_extend,
142            quality: image.quality,
143        }
144    }
145}
146
147/// A premultiplied color.
148#[derive(Debug, Clone, PartialEq, Copy)]
149pub struct PremulColor {
150    premul_u8: PremulRgba8,
151    premul_f32: peniko::color::PremulColor<Srgb>,
152}
153
154impl PremulColor {
155    /// Create a new premultiplied color.
156    pub fn from_alpha_color(color: AlphaColor<Srgb>) -> Self {
157        Self::from_premul_color(color.premultiply())
158    }
159
160    /// Create a new premultiplied color from `peniko::PremulColor`.
161    pub fn from_premul_color(color: peniko::color::PremulColor<Srgb>) -> Self {
162        Self {
163            premul_u8: color.to_rgba8(),
164            premul_f32: color,
165        }
166    }
167
168    /// Return the color as a premultiplied RGBA8 color.
169    pub fn as_premul_rgba8(&self) -> PremulRgba8 {
170        self.premul_u8
171    }
172
173    /// Return the color as a premultiplied RGBAF32 color.
174    pub fn as_premul_f32(&self) -> peniko::color::PremulColor<Srgb> {
175        self.premul_f32
176    }
177
178    /// Return whether the color is opaque (i.e. doesn't have transparency).
179    pub fn is_opaque(&self) -> bool {
180        self.premul_f32.components[3] == 1.0
181    }
182}
183
184/// A kind of paint that can be used for filling and stroking shapes.
185#[derive(Debug, Clone)]
186pub enum PaintType {
187    /// A solid color.
188    Solid(AlphaColor<Srgb>),
189    /// A gradient.
190    Gradient(Gradient),
191    /// An image.
192    Image(Image),
193}
194
195impl From<AlphaColor<Srgb>> for PaintType {
196    fn from(value: AlphaColor<Srgb>) -> Self {
197        Self::Solid(value)
198    }
199}
200
201impl From<Gradient> for PaintType {
202    fn from(value: Gradient) -> Self {
203        Self::Gradient(value)
204    }
205}
206
207impl From<Image> for PaintType {
208    fn from(value: Image) -> Self {
209        Self::Image(value)
210    }
211}