tiny_skia/shaders/
pattern.rs1use tiny_skia_path::NormalizedF32;
8
9use crate::{BlendMode, PixmapRef, Shader, SpreadMode, Transform};
10
11use crate::pipeline;
12use crate::pipeline::RasterPipelineBuilder;
13
14#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
15use tiny_skia_path::NoStdFloat;
16
17#[derive(Copy, Clone, PartialEq, Debug)]
19pub enum FilterQuality {
20    Nearest,
22    Bilinear,
24    Bicubic,
26}
27
28#[derive(Copy, Clone, PartialEq, Debug)]
32pub struct PixmapPaint {
33    pub opacity: f32,
39
40    pub blend_mode: BlendMode,
44
45    pub quality: FilterQuality,
49}
50
51impl Default for PixmapPaint {
52    fn default() -> Self {
53        PixmapPaint {
54            opacity: 1.0,
55            blend_mode: BlendMode::default(),
56            quality: FilterQuality::Nearest,
57        }
58    }
59}
60
61#[derive(Clone, PartialEq, Debug)]
68pub struct Pattern<'a> {
69    pub(crate) pixmap: PixmapRef<'a>,
70    quality: FilterQuality,
71    spread_mode: SpreadMode,
72    pub(crate) opacity: NormalizedF32,
73    pub(crate) transform: Transform,
74}
75
76impl<'a> Pattern<'a> {
77    #[allow(clippy::new_ret_no_self)]
81    pub fn new(
82        pixmap: PixmapRef<'a>,
83        spread_mode: SpreadMode,
84        quality: FilterQuality,
85        opacity: f32,
86        transform: Transform,
87    ) -> Shader {
88        Shader::Pattern(Pattern {
89            pixmap,
90            spread_mode,
91            quality,
92            opacity: NormalizedF32::new_clamped(opacity),
93            transform,
94        })
95    }
96
97    pub(crate) fn push_stages(&self, p: &mut RasterPipelineBuilder) -> bool {
98        let ts = match self.transform.invert() {
99            Some(v) => v,
100            None => {
101                log::warn!("failed to invert a pattern transform. Nothing will be rendered");
102                return false;
103            }
104        };
105
106        p.push(pipeline::Stage::SeedShader);
107
108        p.push_transform(ts);
109
110        let mut quality = self.quality;
111
112        if ts.is_identity() || ts.is_translate() {
113            quality = FilterQuality::Nearest;
114        }
115
116        if quality == FilterQuality::Bilinear {
117            if ts.is_translate() {
118                if ts.tx == ts.tx.trunc() && ts.ty == ts.ty.trunc() {
119                    quality = FilterQuality::Nearest;
121                }
122            }
123        }
124
125        match quality {
128            FilterQuality::Nearest => {
129                p.ctx.limit_x = pipeline::TileCtx {
130                    scale: self.pixmap.width() as f32,
131                    inv_scale: 1.0 / self.pixmap.width() as f32,
132                };
133
134                p.ctx.limit_y = pipeline::TileCtx {
135                    scale: self.pixmap.height() as f32,
136                    inv_scale: 1.0 / self.pixmap.height() as f32,
137                };
138
139                match self.spread_mode {
140                    SpreadMode::Pad => { }
141                    SpreadMode::Repeat => p.push(pipeline::Stage::Repeat),
142                    SpreadMode::Reflect => p.push(pipeline::Stage::Reflect),
143                }
144
145                p.push(pipeline::Stage::Gather);
146            }
147            FilterQuality::Bilinear => {
148                p.ctx.sampler = pipeline::SamplerCtx {
149                    spread_mode: self.spread_mode,
150                    inv_width: 1.0 / self.pixmap.width() as f32,
151                    inv_height: 1.0 / self.pixmap.height() as f32,
152                };
153                p.push(pipeline::Stage::Bilinear);
154            }
155            FilterQuality::Bicubic => {
156                p.ctx.sampler = pipeline::SamplerCtx {
157                    spread_mode: self.spread_mode,
158                    inv_width: 1.0 / self.pixmap.width() as f32,
159                    inv_height: 1.0 / self.pixmap.height() as f32,
160                };
161                p.push(pipeline::Stage::Bicubic);
162
163                p.push(pipeline::Stage::Clamp0);
165                p.push(pipeline::Stage::ClampA);
166            }
167        }
168
169        if self.opacity != NormalizedF32::ONE {
171            debug_assert_eq!(
172                core::mem::size_of_val(&self.opacity),
173                4,
174                "alpha must be f32"
175            );
176            p.ctx.current_coverage = self.opacity.get();
177            p.push(pipeline::Stage::Scale1Float);
178        }
179
180        true
181    }
182}