Skip to main content

dinamika_cpu/paint/
mod.rs

1//! Brushes: [`Paint`], shaders ([`Shader`]) with gradients and blend modes
2//! ([`BlendMode`]).
3//!
4//! The submodules split the responsibilities:
5//! - [`gradient`] — linear/radial/conic gradients and stop sampling;
6//! - [`pattern`] — a texture/[`Pattern`] shader from an image;
7//! - [`blend`] — blend modes and Porter–Duff / W3C compositing.
8//!
9//! # Known limitation: colors are computed in sRGB, not in linear space
10//!
11//! Both blending and gradient stop interpolation operate directly on the sRGB
12//! (gamma-encoded) components, without converting to linear light and back.
13//! This is a deliberate trade-off — it is what most 8-bit engines do by
14//! default — but it has visible consequences: gradients look slightly "dirtier"
15//! in the middle, and semi-transparent edges darken a little. A mathematically
16//! correct result would require decoding sRGB into linear space before the
17//! arithmetic and encoding it back afterward.
18
19use crate::color::Color;
20use crate::geometry::Point;
21
22mod blend;
23mod gradient;
24mod pattern;
25
26pub use blend::BlendMode;
27pub use gradient::{ConicGradient, GradientStop, LinearGradient, RadialGradient, SpreadMode};
28pub use pattern::{FilterQuality, Pattern};
29
30pub(crate) use blend::blend;
31
32/// A color source.
33#[derive(Clone, Debug)]
34pub enum Shader {
35    /// Solid color.
36    SolidColor(Color),
37    Linear(LinearGradient),
38    Radial(RadialGradient),
39    /// A conic (sweep) gradient by angle.
40    Conic(ConicGradient),
41    /// A texture/pattern from an image.
42    Pattern(Pattern),
43}
44
45impl Shader {
46    /// The source color at point `(x, y)` (the pixel center).
47    pub fn color_at(&self, x: f32, y: f32) -> Color {
48        match self {
49            Shader::SolidColor(c) => *c,
50            Shader::Linear(g) => g.color_at(Point::new(x, y)),
51            Shader::Radial(g) => g.color_at(Point::new(x, y)),
52            Shader::Conic(g) => g.color_at(Point::new(x, y)),
53            Shader::Pattern(p) => p.color_at(Point::new(x, y)),
54        }
55    }
56
57    /// Shades a horizontal run of `len` pixels — pixel centers
58    /// `(x + 0.5, y + 0.5)`, `(x + 1.5, y + 0.5)`, … — appending one [`Color`]
59    /// per pixel to `out`.
60    ///
61    /// This is the batched counterpart of [`Shader::color_at`]. Because every
62    /// shader's coordinate mapping is affine, the run is evaluated by mapping
63    /// the first pixel once and then advancing by a constant per-pixel delta,
64    /// instead of recomputing `inv_transform.map_point` for every pixel. `out`
65    /// is appended to (not cleared), so reuse a buffer across rows and clear it
66    /// yourself between runs.
67    pub(crate) fn shade_span(&self, x: usize, y: usize, len: usize, out: &mut Vec<Color>) {
68        match self {
69            Shader::SolidColor(c) => out.extend(std::iter::repeat_n(*c, len)),
70            Shader::Linear(g) => g.shade_span(x, y, len, out),
71            Shader::Radial(g) => g.shade_span(x, y, len, out),
72            Shader::Conic(g) => g.shade_span(x, y, len, out),
73            Shader::Pattern(p) => p.shade_span(x, y, len, out),
74        }
75    }
76}
77
78/// A fill description: the color source, blend mode and anti-aliasing.
79#[derive(Clone, Debug)]
80pub struct Paint {
81    pub shader: Shader,
82    pub blend_mode: BlendMode,
83    pub anti_alias: bool,
84    /// Overall transparency multiplier `0..=1`.
85    pub opacity: f32,
86}
87
88impl Default for Paint {
89    fn default() -> Self {
90        Paint {
91            shader: Shader::SolidColor(Color::BLACK),
92            blend_mode: BlendMode::SourceOver,
93            anti_alias: true,
94            opacity: 1.0,
95        }
96    }
97}
98
99impl Paint {
100    /// A brush with a solid color.
101    pub fn from_color(color: Color) -> Self {
102        Paint { shader: Shader::SolidColor(color), ..Paint::default() }
103    }
104
105    /// Sets a solid color.
106    pub fn set_color(&mut self, color: Color) -> &mut Self {
107        self.shader = Shader::SolidColor(color);
108        self
109    }
110
111    /// Sets the shader.
112    pub fn set_shader(&mut self, shader: Shader) -> &mut Self {
113        self.shader = shader;
114        self
115    }
116}