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
7use 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/// Controls how much filtering to be done when transforming images.
18#[derive(Copy, Clone, PartialEq, Debug)]
19pub 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)]
32pub 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
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/// 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)]
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 /// 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