1use crate::{scalar, Color4f, ColorSpace, TileMode};
2use skia_bindings as sb;
3
4#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
9#[repr(C)]
10pub struct Interpolation {
11 pub in_premul: interpolation::InPremul,
12 pub color_space: interpolation::ColorSpace,
13 pub hue_method: interpolation::HueMethod,
14}
15
16native_transmutable!(sb::SkGradient_Interpolation, Interpolation);
17
18pub mod interpolation {
19 use skia_bindings as sb;
20
21 pub type InPremul = sb::SkGradient_Interpolation_InPremul;
23 variant_name!(InPremul::Yes);
24
25 pub type ColorSpace = sb::SkGradient_Interpolation_ColorSpace;
29 variant_name!(ColorSpace::HSL);
30
31 pub type HueMethod = sb::SkGradient_Interpolation_HueMethod;
35 variant_name!(HueMethod::Shorter);
36}
37
38impl Default for Interpolation {
39 fn default() -> Self {
40 Self {
41 in_premul: interpolation::InPremul::No,
42 color_space: interpolation::ColorSpace::Destination,
43 hue_method: interpolation::HueMethod::Shorter,
44 }
45 }
46}
47
48impl Interpolation {
49 pub fn from_flags(flags: u32) -> Self {
51 Self {
52 in_premul: if flags & 1 != 0 {
53 interpolation::InPremul::Yes
54 } else {
55 interpolation::InPremul::No
56 },
57 color_space: interpolation::ColorSpace::Destination,
58 hue_method: interpolation::HueMethod::Shorter,
59 }
60 }
61}
62
63#[derive(Debug, Clone)]
68pub struct Colors<'a> {
69 colors: &'a [Color4f],
70 pos: Option<&'a [scalar]>,
71 color_space: Option<ColorSpace>,
72 tile_mode: TileMode,
73}
74
75impl<'a> Colors<'a> {
76 pub fn new(
84 colors: &'a [Color4f],
85 pos: Option<&'a [scalar]>,
86 tile_mode: TileMode,
87 color_space: impl Into<Option<ColorSpace>>,
88 ) -> Self {
89 assert!(pos.is_none_or(|pos| pos.len() == colors.len()));
91
92 Self {
93 colors,
94 pos,
95 color_space: color_space.into(),
96 tile_mode,
97 }
98 }
99
100 pub fn new_evenly_spaced(
102 colors: &'a [Color4f],
103 tile_mode: TileMode,
104 color_space: impl Into<Option<ColorSpace>>,
105 ) -> Self {
106 Self::new(colors, None, tile_mode, color_space)
107 }
108
109 pub fn colors(&self) -> &'a [Color4f] {
111 self.colors
112 }
113
114 pub fn positions(&self) -> Option<&'a [scalar]> {
116 self.pos
117 }
118
119 pub fn color_space(&self) -> Option<&ColorSpace> {
121 self.color_space.as_ref()
122 }
123
124 pub fn tile_mode(&self) -> TileMode {
126 self.tile_mode
127 }
128}
129
130#[derive(Debug, Clone)]
138pub struct Gradient<'a> {
139 colors: Colors<'a>,
140 interpolation: Interpolation,
141}
142
143impl<'a> Gradient<'a> {
144 pub fn new(colors: Colors<'a>, interpolation: impl Into<Interpolation>) -> Self {
145 Self {
146 colors,
147 interpolation: interpolation.into(),
148 }
149 }
150
151 pub fn colors(&self) -> &Colors<'a> {
152 &self.colors
153 }
154
155 pub fn interpolation(&self) -> &Interpolation {
156 &self.interpolation
157 }
158}
159
160pub mod shaders {
164 use super::{scalar, Gradient};
165 use crate::{prelude::*, Matrix, Point, Shader};
166 use skia_bindings as sb;
167 use std::ptr;
168
169 pub fn linear_gradient<'a>(
175 points: (impl Into<Point>, impl Into<Point>),
176 gradient: &Gradient<'_>,
177 local_matrix: impl Into<Option<&'a Matrix>>,
178 ) -> Option<Shader> {
179 let points = [points.0.into(), points.1.into()];
180 let local_matrix = local_matrix.into();
181 let colors = gradient.colors();
182 let interpolation = gradient.interpolation();
183 let positions = colors.positions();
184
185 Shader::from_ptr(unsafe {
186 sb::C_SkShaders_LinearGradient(
187 points.native().as_ptr(),
188 colors.colors().as_ptr() as *const _,
189 colors.colors().len(),
190 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
191 positions.map_or(0, |pos| pos.len()),
192 colors.tile_mode(),
193 colors.color_space().native_ptr_or_null() as *mut _,
194 interpolation.native(),
195 local_matrix.native_ptr_or_null(),
196 )
197 })
198 }
199
200 pub fn radial_gradient<'a>(
207 (center, radius): (impl Into<Point>, scalar),
208 gradient: &Gradient<'_>,
209 local_matrix: impl Into<Option<&'a Matrix>>,
210 ) -> Option<Shader> {
211 let center = center.into();
212 let local_matrix = local_matrix.into();
213 let colors = gradient.colors();
214 let interpolation = gradient.interpolation();
215 let positions = colors.positions();
216
217 Shader::from_ptr(unsafe {
218 sb::C_SkShaders_RadialGradient(
219 center.native(),
220 radius,
221 colors.colors().as_ptr() as *const _,
222 colors.colors().len(),
223 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
224 positions.map_or(0, |pos| pos.len()),
225 colors.tile_mode(),
226 colors.color_space().native_ptr_or_null() as *mut _,
227 interpolation.native(),
228 local_matrix.native_ptr_or_null(),
229 )
230 })
231 }
232
233 #[allow(clippy::too_many_arguments)]
245 pub fn two_point_conical_gradient<'a>(
246 (start, start_radius): (impl Into<Point>, scalar),
247 (end, end_radius): (impl Into<Point>, scalar),
248 gradient: &Gradient<'_>,
249 local_matrix: impl Into<Option<&'a Matrix>>,
250 ) -> Option<Shader> {
251 let start = start.into();
252 let end = end.into();
253 let local_matrix = local_matrix.into();
254 let colors = gradient.colors();
255 let interpolation = gradient.interpolation();
256 let positions = colors.positions();
257
258 Shader::from_ptr(unsafe {
259 sb::C_SkShaders_TwoPointConicalGradient(
260 start.native(),
261 start_radius,
262 end.native(),
263 end_radius,
264 colors.colors().as_ptr() as *const _,
265 colors.colors().len(),
266 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
267 positions.map_or(0, |pos| pos.len()),
268 colors.tile_mode(),
269 colors.color_space().native_ptr_or_null() as *mut _,
270 interpolation.native(),
271 local_matrix.native_ptr_or_null(),
272 )
273 })
274 }
275
276 pub fn sweep_gradient<'a>(
288 center: impl Into<Point>,
289 (start_angle, end_angle): (scalar, scalar),
290 gradient: &Gradient<'_>,
291 local_matrix: impl Into<Option<&'a Matrix>>,
292 ) -> Option<Shader> {
293 let center = center.into();
294 let local_matrix = local_matrix.into();
295 let colors = gradient.colors();
296 let interpolation = gradient.interpolation();
297 let positions = colors.positions();
298
299 Shader::from_ptr(unsafe {
300 sb::C_SkShaders_SweepGradient(
301 center.native(),
302 start_angle,
303 end_angle,
304 colors.colors().as_ptr() as *const _,
305 colors.colors().len(),
306 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
307 positions.map_or(0, |pos| pos.len()),
308 colors.tile_mode(),
309 colors.color_space().native_ptr_or_null() as *mut _,
310 interpolation.native(),
311 local_matrix.native_ptr_or_null(),
312 )
313 })
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320 use crate::{Color, Paint, Point, Rect};
321
322 #[test]
323 fn interpolation_from_flags() {
324 let interp_no_premul = Interpolation::from_flags(0);
325 assert_eq!(interp_no_premul.in_premul, interpolation::InPremul::No);
326
327 let interp_premul = Interpolation::from_flags(1);
328 assert_eq!(interp_premul.in_premul, interpolation::InPremul::Yes);
329 }
330
331 #[test]
332 #[should_panic]
333 fn colors_new_mismatched_positions() {
334 let colors = [Color::RED.into(), Color::BLUE.into()];
335 let positions = [0.0, 0.5, 1.0];
336 let _ = Colors::new(&colors, Some(&positions), TileMode::Clamp, None);
337 }
338
339 #[test]
340 fn linear_gradient_renders() {
341 let mut surface = crate::surfaces::raster_n32_premul((100, 100)).unwrap();
342 let canvas = surface.canvas();
343
344 let colors = [Color::RED.into(), Color::BLUE.into()];
345 let gradient_colors = Colors::new_evenly_spaced(&colors, TileMode::Clamp, None);
346 let gradient = Gradient::new(gradient_colors, Interpolation::default());
347
348 let shader = shaders::linear_gradient(
349 (Point::new(0.0, 0.0), Point::new(100.0, 0.0)),
350 &gradient,
351 None,
352 )
353 .unwrap();
354
355 let mut paint = Paint::default();
356 paint.set_shader(shader);
357
358 canvas.draw_rect(Rect::from_xywh(0.0, 0.0, 100.0, 100.0), &paint);
359
360 let image = surface.image_snapshot();
361 let pixel_left = image.peek_pixels().unwrap().get_color((10, 50));
362 let pixel_right = image.peek_pixels().unwrap().get_color((90, 50));
363
364 assert_ne!(pixel_left, pixel_right);
365 assert!(pixel_left.r() > pixel_right.r());
366 assert!(pixel_left.b() < pixel_right.b());
367 }
368}