Skip to main content

tiny_skia/shaders/
mod.rs

1// Copyright 2006 The Android Open Source Project
2// Copyright 2020 Yevhenii Reizner
3//
4// Use of this source code is governed by a BSD-style license that can be
5// found in the LICENSE file.
6
7mod gradient;
8mod linear_gradient;
9mod pattern;
10mod radial_gradient;
11mod sweep_gradient;
12
13use tiny_skia_path::{NormalizedF32, Scalar};
14
15pub use gradient::GradientStop;
16pub use linear_gradient::LinearGradient;
17pub use pattern::{FilterQuality, Pattern, PixmapPaint};
18pub use radial_gradient::RadialGradient;
19pub use sweep_gradient::SweepGradient;
20
21use crate::{Color, ColorSpace, Transform};
22
23use crate::pipeline::RasterPipelineBuilder;
24
25/// A shader spreading mode.
26#[derive(Copy, Clone, Default, PartialEq, Debug)]
27pub enum SpreadMode {
28    /// Replicate the edge color if the shader draws outside of its
29    /// original bounds.
30    #[default]
31    Pad,
32
33    /// Repeat the shader's image horizontally and vertically, alternating
34    /// mirror images so that adjacent images always seam.
35    Reflect,
36
37    /// Repeat the shader's image horizontally and vertically.
38    Repeat,
39}
40
41/// A shader specifies the source color(s) for what is being drawn.
42///
43/// If a paint has no shader, then the paint's color is used. If the paint has a
44/// shader, then the shader's color(s) are use instead, but they are
45/// modulated by the paint's alpha. This makes it easy to create a shader
46/// once (e.g. bitmap tiling or gradient) and then change its transparency
47/// without having to modify the original shader. Only the paint's alpha needs
48/// to be modified.
49#[derive(Clone, PartialEq, Debug)]
50pub enum Shader<'a> {
51    /// A solid color shader.
52    SolidColor(Color),
53    /// A linear gradient shader.
54    LinearGradient(LinearGradient),
55    /// A radial gradient shader.
56    RadialGradient(RadialGradient),
57    /// A sweep gradient shader.
58    SweepGradient(SweepGradient),
59    /// A pattern shader.
60    Pattern(Pattern<'a>),
61}
62
63impl Shader<'_> {
64    /// Checks if the shader is guaranteed to produce only opaque colors.
65    pub fn is_opaque(&self) -> bool {
66        match self {
67            Shader::SolidColor(c) => c.is_opaque(),
68            Shader::LinearGradient(g) => g.is_opaque(),
69            // A radial gradient may have points that are "undefined" so we just assume that it is
70            // not opaque.
71            Shader::RadialGradient(_) => false,
72            Shader::SweepGradient(g) => g.is_opaque(),
73            Shader::Pattern(_) => false,
74        }
75    }
76
77    // Unlike Skia, we do not have is_constant, because we don't have Color shaders.
78
79    /// If this returns false, then we draw nothing (do not fall back to shader context)
80    #[must_use]
81    pub(crate) fn push_stages(&self, cs: ColorSpace, p: &mut RasterPipelineBuilder) -> bool {
82        match self {
83            Shader::SolidColor(color) => {
84                let color = cs.expand_color(*color).premultiply();
85                p.push_uniform_color(color);
86                true
87            }
88            Shader::LinearGradient(g) => g.push_stages(cs, p),
89            Shader::RadialGradient(g) => g.push_stages(cs, p),
90            Shader::SweepGradient(g) => g.push_stages(cs, p),
91            Shader::Pattern(patt) => patt.push_stages(cs, p),
92        }
93    }
94
95    /// Transforms the shader.
96    pub fn transform(&mut self, ts: Transform) {
97        match self {
98            Shader::SolidColor(_) => {}
99            Shader::LinearGradient(g) => {
100                g.base.transform = g.base.transform.post_concat(ts);
101            }
102            Shader::RadialGradient(g) => {
103                g.base.transform = g.base.transform.post_concat(ts);
104            }
105            Shader::SweepGradient(g) => {
106                g.base.transform = g.base.transform.post_concat(ts);
107            }
108            Shader::Pattern(p) => {
109                p.transform = p.transform.post_concat(ts);
110            }
111        }
112    }
113
114    /// Shifts shader's opacity.
115    ///
116    /// `opacity` will be clamped to the 0..=1 range.
117    ///
118    /// This is roughly the same as Skia's `SkPaint::setAlpha`.
119    ///
120    /// Unlike Skia, we do not support global alpha/opacity, which is in Skia
121    /// is set via the alpha channel of the `SkPaint::fColor4f`.
122    /// Instead, you can shift the opacity of the shader to whatever value you need.
123    ///
124    /// - For `SolidColor` this function will multiply `color.alpha` by `opacity`.
125    /// - For gradients this function will multiply all colors by `opacity`.
126    /// - For `Pattern` this function will multiply `Patter::opacity` by `opacity`.
127    pub fn apply_opacity(&mut self, opacity: f32) {
128        match self {
129            Shader::SolidColor(ref mut c) => {
130                c.apply_opacity(opacity);
131            }
132            Shader::LinearGradient(g) => {
133                g.base.apply_opacity(opacity);
134            }
135            Shader::RadialGradient(g) => {
136                g.base.apply_opacity(opacity);
137            }
138            Shader::SweepGradient(g) => {
139                g.base.apply_opacity(opacity);
140            }
141            Shader::Pattern(ref mut p) => {
142                p.opacity = NormalizedF32::new(p.opacity.get() * opacity.bound(0.0, 1.0)).unwrap();
143            }
144        }
145    }
146}