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 let color_space = colors.color_space().cloned();
185
186 Shader::from_ptr(unsafe {
187 sb::C_SkShaders_LinearGradient(
188 points.native().as_ptr(),
189 colors.colors().native().as_ptr(),
190 colors.colors().len(),
191 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
192 positions.map_or(0, |pos| pos.len()),
193 colors.tile_mode(),
194 color_space.into_ptr_or_null(),
195 interpolation.native(),
196 local_matrix.native_ptr_or_null(),
197 )
198 })
199 }
200
201 pub fn radial_gradient<'a>(
208 (center, radius): (impl Into<Point>, scalar),
209 gradient: &Gradient<'_>,
210 local_matrix: impl Into<Option<&'a Matrix>>,
211 ) -> Option<Shader> {
212 let center = center.into();
213 let local_matrix = local_matrix.into();
214 let colors = gradient.colors();
215 let interpolation = gradient.interpolation();
216 let positions = colors.positions();
217 let color_space = colors.color_space().cloned();
218
219 Shader::from_ptr(unsafe {
220 sb::C_SkShaders_RadialGradient(
221 center.native(),
222 radius,
223 colors.colors().native().as_ptr(),
224 colors.colors().len(),
225 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
226 positions.map_or(0, |pos| pos.len()),
227 colors.tile_mode(),
228 color_space.into_ptr_or_null(),
229 interpolation.native(),
230 local_matrix.native_ptr_or_null(),
231 )
232 })
233 }
234
235 #[allow(clippy::too_many_arguments)]
247 pub fn two_point_conical_gradient<'a>(
248 (start, start_radius): (impl Into<Point>, scalar),
249 (end, end_radius): (impl Into<Point>, scalar),
250 gradient: &Gradient<'_>,
251 local_matrix: impl Into<Option<&'a Matrix>>,
252 ) -> Option<Shader> {
253 let start = start.into();
254 let end = end.into();
255 let local_matrix = local_matrix.into();
256 let colors = gradient.colors();
257 let interpolation = gradient.interpolation();
258 let positions = colors.positions();
259 let color_space = colors.color_space().cloned();
260
261 Shader::from_ptr(unsafe {
262 sb::C_SkShaders_TwoPointConicalGradient(
263 start.native(),
264 start_radius,
265 end.native(),
266 end_radius,
267 colors.colors().native().as_ptr(),
268 colors.colors().len(),
269 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
270 positions.map_or(0, |pos| pos.len()),
271 colors.tile_mode(),
272 color_space.into_ptr_or_null(),
273 interpolation.native(),
274 local_matrix.native_ptr_or_null(),
275 )
276 })
277 }
278
279 pub fn sweep_gradient<'a>(
291 center: impl Into<Point>,
292 (start_angle, end_angle): (scalar, scalar),
293 gradient: &Gradient<'_>,
294 local_matrix: impl Into<Option<&'a Matrix>>,
295 ) -> Option<Shader> {
296 let center = center.into();
297 let local_matrix = local_matrix.into();
298 let colors = gradient.colors();
299 let interpolation = gradient.interpolation();
300 let positions = colors.positions();
301 let color_space = colors.color_space().cloned();
302
303 Shader::from_ptr(unsafe {
304 sb::C_SkShaders_SweepGradient(
305 center.native(),
306 start_angle,
307 end_angle,
308 colors.colors().native().as_ptr(),
309 colors.colors().len(),
310 positions.map_or(ptr::null(), |pos| pos.as_ptr()),
311 positions.map_or(0, |pos| pos.len()),
312 colors.tile_mode(),
313 color_space.into_ptr_or_null(),
314 interpolation.native(),
315 local_matrix.native_ptr_or_null(),
316 )
317 })
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324 use crate::{
325 prelude::{NativeAccess, RefCount},
326 Color, ColorSpace, Paint, Point, Rect, Shader,
327 };
328
329 #[test]
330 fn interpolation_from_flags() {
331 let interp_no_premul = Interpolation::from_flags(0);
332 assert_eq!(interp_no_premul.in_premul, interpolation::InPremul::No);
333
334 let interp_premul = Interpolation::from_flags(1);
335 assert_eq!(interp_premul.in_premul, interpolation::InPremul::Yes);
336 }
337
338 #[test]
339 #[should_panic]
340 fn colors_new_mismatched_positions() {
341 let colors = [Color::RED.into(), Color::BLUE.into()];
342 let positions = [0.0, 0.5, 1.0];
343 let _ = Colors::new(&colors, Some(&positions), TileMode::Clamp, None);
344 }
345
346 #[test]
347 fn linear_gradient_renders() {
348 let mut surface = crate::surfaces::raster_n32_premul((100, 100)).unwrap();
349 let canvas = surface.canvas();
350
351 let colors = [Color::RED.into(), Color::BLUE.into()];
352 let gradient_colors = Colors::new_evenly_spaced(&colors, TileMode::Clamp, None);
353 let gradient = Gradient::new(gradient_colors, Interpolation::default());
354
355 let shader = shaders::linear_gradient(
356 (Point::new(0.0, 0.0), Point::new(100.0, 0.0)),
357 &gradient,
358 None,
359 )
360 .unwrap();
361
362 let mut paint = Paint::default();
363 paint.set_shader(shader);
364
365 canvas.draw_rect(Rect::from_xywh(0.0, 0.0, 100.0, 100.0), &paint);
366
367 let image = surface.image_snapshot();
368 let pixel_left = image.peek_pixels().unwrap().get_color((10, 50));
369 let pixel_right = image.peek_pixels().unwrap().get_color((90, 50));
370
371 assert_ne!(pixel_left, pixel_right);
372 assert!(pixel_left.r() > pixel_right.r());
373 assert!(pixel_left.b() < pixel_right.b());
374 }
375
376 #[test]
377 fn linear_gradient_with_explicit_colorspace_keeps_refcount_balanced() {
378 assert_refcount_balanced(|gradient| {
379 shaders::linear_gradient(
380 (Point::new(0.0, 0.0), Point::new(100.0, 0.0)),
381 gradient,
382 None,
383 )
384 });
385 }
386
387 #[test]
388 fn radial_gradient_with_explicit_colorspace_keeps_refcount_balanced() {
389 assert_refcount_balanced(|gradient| {
390 shaders::radial_gradient((Point::new(50.0, 50.0), 25.0), gradient, None)
391 });
392 }
393
394 #[test]
395 fn two_point_conical_gradient_with_explicit_colorspace_keeps_refcount_balanced() {
396 assert_refcount_balanced(|gradient| {
397 shaders::two_point_conical_gradient(
398 (Point::new(25.0, 50.0), 10.0),
399 (Point::new(75.0, 50.0), 40.0),
400 gradient,
401 None,
402 )
403 });
404 }
405
406 #[test]
407 fn sweep_gradient_with_explicit_colorspace_keeps_refcount_balanced() {
408 assert_refcount_balanced(|gradient| {
409 shaders::sweep_gradient(Point::new(50.0, 50.0), (0.0, 360.0), gradient, None)
410 });
411 }
412
413 fn test_color_space() -> ColorSpace {
414 ColorSpace::new_srgb().with_color_spin()
415 }
416
417 fn assert_refcount_balanced(build_shader: impl FnOnce(&Gradient<'_>) -> Option<Shader>) {
418 let colors = [Color::RED.into(), Color::BLUE.into()];
419 let color_space = test_color_space();
420 let gradient_colors =
421 Colors::new_evenly_spaced(&colors, TileMode::Clamp, Some(color_space.clone()));
422 let gradient = Gradient::new(gradient_colors, Interpolation::default());
423
424 let ref_cnt_before = color_space.native().ref_cnt();
425 let shader = build_shader(&gradient).unwrap();
426 drop(shader);
427 let ref_cnt_after = color_space.native().ref_cnt();
428
429 assert_eq!(ref_cnt_after, ref_cnt_before);
430 }
431}