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 | |
7 | use tiny_skia_path::NormalizedF32; |
8 | |
9 | use crate::{BlendMode, PixmapRef, Shader, SpreadMode, Transform}; |
10 | |
11 | use crate::pipeline; |
12 | use crate::pipeline::RasterPipelineBuilder; |
13 | |
14 | #[cfg (all(not(feature = "std" ), feature = "no-std-float" ))] |
15 | use tiny_skia_path::NoStdFloat; |
16 | |
17 | /// Controls how much filtering to be done when transforming images. |
18 | #[derive (Copy, Clone, PartialEq, Debug)] |
19 | pub enum FilterQuality { |
20 | /// Nearest-neighbor. Low quality, but fastest. |
21 | Nearest, |
22 | /// Bilinear. |
23 | Bilinear, |
24 | /// Bicubic. High quality, but slow. |
25 | Bicubic, |
26 | } |
27 | |
28 | /// Controls how a pixmap should be blended. |
29 | /// |
30 | /// Like `Paint`, but for `Pixmap`. |
31 | #[derive (Copy, Clone, PartialEq, Debug)] |
32 | pub struct PixmapPaint { |
33 | /// Pixmap opacity. |
34 | /// |
35 | /// Must be in 0..=1 range. |
36 | /// |
37 | /// Default: 1.0 |
38 | pub opacity: f32, |
39 | |
40 | /// Pixmap blending mode. |
41 | /// |
42 | /// Default: SourceOver |
43 | pub blend_mode: BlendMode, |
44 | |
45 | /// Specifies how much filtering to be done when transforming images. |
46 | /// |
47 | /// Default: Nearest |
48 | pub quality: FilterQuality, |
49 | } |
50 | |
51 | impl 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 | /// A pattern shader. |
62 | /// |
63 | /// Essentially a `SkImageShader`. |
64 | /// |
65 | /// Unlike Skia, we do not support FilterQuality::Medium, because it involves |
66 | /// mipmap generation, which adds too much complexity. |
67 | #[derive (Clone, PartialEq, Debug)] |
68 | pub 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 | |
76 | impl<'a> Pattern<'a> { |
77 | /// Creates a new pattern shader. |
78 | /// |
79 | /// `opacity` will be clamped to the 0..=1 range. |
80 | #[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 | // When the matrix is just an integer translate, bilerp == nearest neighbor. |
120 | quality = FilterQuality::Nearest; |
121 | } |
122 | } |
123 | } |
124 | |
125 | // TODO: minimizing scale via mipmap |
126 | |
127 | 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 => { /* The gather() stage will clamp for us. */ } |
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 | // Bicubic filtering naturally produces out of range values on both sides of [0,1]. |
164 | p.push(pipeline::Stage::Clamp0); |
165 | p.push(pipeline::Stage::ClampA); |
166 | } |
167 | } |
168 | |
169 | // Unlike Skia, we do not support global opacity and only Pattern allows it. |
170 | 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 | } |
183 | |