Skip to main content

agg_rust/
span_gradient.rs

1//! Gradient span generator and gradient functions.
2//!
3//! Port of `agg_span_gradient.h` — provides gradient shape functions
4//! (linear, radial, diamond, conic, etc.) and the main `SpanGradient`
5//! generator that combines an interpolator, gradient function, and color
6//! function to produce gradient-colored spans.
7
8use crate::basics::{iround, uround};
9use crate::gradient_lut::ColorFunction;
10use crate::math::fast_sqrt;
11use crate::renderer_scanline::SpanGenerator;
12use crate::span_interpolator_linear::{SpanInterpolatorLinear, SUBPIXEL_SHIFT};
13
14// ============================================================================
15// Constants
16// ============================================================================
17
18pub const GRADIENT_SUBPIXEL_SHIFT: i32 = 4;
19pub const GRADIENT_SUBPIXEL_SCALE: i32 = 1 << GRADIENT_SUBPIXEL_SHIFT;
20pub const GRADIENT_SUBPIXEL_MASK: i32 = GRADIENT_SUBPIXEL_SCALE - 1;
21
22/// Downscale shift from interpolator subpixel to gradient subpixel.
23const DOWNSCALE_SHIFT: i32 = SUBPIXEL_SHIFT as i32 - GRADIENT_SUBPIXEL_SHIFT;
24
25// ============================================================================
26// GradientFunction trait
27// ============================================================================
28
29/// Trait for gradient shape functions.
30///
31/// Maps a 2D point `(x, y)` to a scalar distance value. The `d` parameter
32/// is the gradient diameter/range (used by some gradient types like XY, conic).
33pub trait GradientFunction {
34    fn calculate(&self, x: i32, y: i32, d: i32) -> i32;
35}
36
37// ============================================================================
38// Gradient functions — zero-sized types for stateless gradients
39// ============================================================================
40
41/// Linear gradient along the X axis.
42///
43/// Port of C++ `gradient_x`. Simply returns `x`.
44pub struct GradientX;
45
46impl GradientFunction for GradientX {
47    #[inline]
48    fn calculate(&self, x: i32, _y: i32, _d: i32) -> i32 {
49        x
50    }
51}
52
53/// Linear gradient along the Y axis.
54///
55/// Port of C++ `gradient_y`. Simply returns `y`.
56pub struct GradientY;
57
58impl GradientFunction for GradientY {
59    #[inline]
60    fn calculate(&self, _x: i32, y: i32, _d: i32) -> i32 {
61        y
62    }
63}
64
65/// Radial gradient using fast integer square root.
66///
67/// Port of C++ `gradient_radial` / `gradient_circle`.
68pub struct GradientRadial;
69
70impl GradientFunction for GradientRadial {
71    #[inline]
72    fn calculate(&self, x: i32, y: i32, _d: i32) -> i32 {
73        fast_sqrt((x * x + y * y) as u32) as i32
74    }
75}
76
77/// Radial gradient using f64 square root (higher precision).
78///
79/// Port of C++ `gradient_radial_d`.
80pub struct GradientRadialD;
81
82impl GradientFunction for GradientRadialD {
83    #[inline]
84    fn calculate(&self, x: i32, y: i32, _d: i32) -> i32 {
85        uround(((x as f64) * (x as f64) + (y as f64) * (y as f64)).sqrt()) as i32
86    }
87}
88
89/// Diamond-shaped gradient: max(|x|, |y|).
90///
91/// Port of C++ `gradient_diamond`.
92pub struct GradientDiamond;
93
94impl GradientFunction for GradientDiamond {
95    #[inline]
96    fn calculate(&self, x: i32, y: i32, _d: i32) -> i32 {
97        let ax = x.abs();
98        let ay = y.abs();
99        if ax > ay {
100            ax
101        } else {
102            ay
103        }
104    }
105}
106
107/// XY gradient: |x|*|y| / d.
108///
109/// Port of C++ `gradient_xy`.
110pub struct GradientXY;
111
112impl GradientFunction for GradientXY {
113    #[inline]
114    fn calculate(&self, x: i32, y: i32, d: i32) -> i32 {
115        x.abs() * y.abs() / d
116    }
117}
118
119/// Square-root XY gradient: sqrt(|x|*|y|).
120///
121/// Port of C++ `gradient_sqrt_xy`.
122pub struct GradientSqrtXY;
123
124impl GradientFunction for GradientSqrtXY {
125    #[inline]
126    fn calculate(&self, x: i32, y: i32, _d: i32) -> i32 {
127        fast_sqrt((x.abs() * y.abs()) as u32) as i32
128    }
129}
130
131/// Conic (angular) gradient: |atan2(y,x)| * d / pi.
132///
133/// Port of C++ `gradient_conic`.
134pub struct GradientConic;
135
136impl GradientFunction for GradientConic {
137    #[inline]
138    fn calculate(&self, x: i32, y: i32, d: i32) -> i32 {
139        uround((y as f64).atan2(x as f64).abs() * (d as f64) / std::f64::consts::PI) as i32
140    }
141}
142
143// ============================================================================
144// Radial gradient with focal point
145// ============================================================================
146
147/// Radial gradient with an off-center focal point.
148///
149/// Port of C++ `gradient_radial_focus`. Has mutable state for the
150/// precomputed invariant values.
151pub struct GradientRadialFocus {
152    r: i32,
153    fx: i32,
154    fy: i32,
155    r2: f64,
156    fx2: f64,
157    fy2: f64,
158    mul: f64,
159}
160
161impl GradientRadialFocus {
162    pub fn new_default() -> Self {
163        let mut s = Self {
164            r: 100 * GRADIENT_SUBPIXEL_SCALE,
165            fx: 0,
166            fy: 0,
167            r2: 0.0,
168            fx2: 0.0,
169            fy2: 0.0,
170            mul: 0.0,
171        };
172        s.update_values();
173        s
174    }
175
176    pub fn new(r: f64, fx: f64, fy: f64) -> Self {
177        let mut s = Self {
178            r: iround(r * GRADIENT_SUBPIXEL_SCALE as f64),
179            fx: iround(fx * GRADIENT_SUBPIXEL_SCALE as f64),
180            fy: iround(fy * GRADIENT_SUBPIXEL_SCALE as f64),
181            r2: 0.0,
182            fx2: 0.0,
183            fy2: 0.0,
184            mul: 0.0,
185        };
186        s.update_values();
187        s
188    }
189
190    pub fn init(&mut self, r: f64, fx: f64, fy: f64) {
191        self.r = iround(r * GRADIENT_SUBPIXEL_SCALE as f64);
192        self.fx = iround(fx * GRADIENT_SUBPIXEL_SCALE as f64);
193        self.fy = iround(fy * GRADIENT_SUBPIXEL_SCALE as f64);
194        self.update_values();
195    }
196
197    pub fn radius(&self) -> f64 {
198        self.r as f64 / GRADIENT_SUBPIXEL_SCALE as f64
199    }
200
201    pub fn focus_x(&self) -> f64 {
202        self.fx as f64 / GRADIENT_SUBPIXEL_SCALE as f64
203    }
204
205    pub fn focus_y(&self) -> f64 {
206        self.fy as f64 / GRADIENT_SUBPIXEL_SCALE as f64
207    }
208
209    fn update_values(&mut self) {
210        // Calculate the invariant values. In case the focal center
211        // lies exactly on the gradient circle the divisor degenerates
212        // into zero. In this case we just move the focal center by
213        // one subpixel unit possibly in the direction to the origin (0,0)
214        // and calculate the values again.
215        self.r2 = (self.r as f64) * (self.r as f64);
216        self.fx2 = (self.fx as f64) * (self.fx as f64);
217        self.fy2 = (self.fy as f64) * (self.fy as f64);
218        let mut d = self.r2 - (self.fx2 + self.fy2);
219        if d == 0.0 {
220            if self.fx != 0 {
221                if self.fx < 0 {
222                    self.fx += 1;
223                } else {
224                    self.fx -= 1;
225                }
226            }
227            if self.fy != 0 {
228                if self.fy < 0 {
229                    self.fy += 1;
230                } else {
231                    self.fy -= 1;
232                }
233            }
234            self.fx2 = (self.fx as f64) * (self.fx as f64);
235            self.fy2 = (self.fy as f64) * (self.fy as f64);
236            d = self.r2 - (self.fx2 + self.fy2);
237        }
238        self.mul = self.r as f64 / d;
239    }
240}
241
242impl GradientFunction for GradientRadialFocus {
243    fn calculate(&self, x: i32, y: i32, _d: i32) -> i32 {
244        let dx = x as f64 - self.fx as f64;
245        let dy = y as f64 - self.fy as f64;
246        let d2 = dx * self.fy as f64 - dy * self.fx as f64;
247        let d3 = self.r2 * (dx * dx + dy * dy) - d2 * d2;
248        iround((dx * self.fx as f64 + dy * self.fy as f64 + d3.abs().sqrt()) * self.mul)
249    }
250}
251
252// ============================================================================
253// Gradient adaptors — repeat and reflect
254// ============================================================================
255
256/// Repeating gradient adaptor — wraps gradient values with modulo.
257///
258/// Port of C++ `gradient_repeat_adaptor<GradientF>`.
259pub struct GradientRepeatAdaptor<G> {
260    gradient: G,
261}
262
263impl<G: GradientFunction> GradientRepeatAdaptor<G> {
264    pub fn new(gradient: G) -> Self {
265        Self { gradient }
266    }
267}
268
269impl<G: GradientFunction> GradientFunction for GradientRepeatAdaptor<G> {
270    #[inline]
271    fn calculate(&self, x: i32, y: i32, d: i32) -> i32 {
272        let mut ret = self.gradient.calculate(x, y, d) % d;
273        if ret < 0 {
274            ret += d;
275        }
276        ret
277    }
278}
279
280/// Reflecting gradient adaptor — mirrors gradient values at boundaries.
281///
282/// Port of C++ `gradient_reflect_adaptor<GradientF>`.
283pub struct GradientReflectAdaptor<G> {
284    gradient: G,
285}
286
287impl<G: GradientFunction> GradientReflectAdaptor<G> {
288    pub fn new(gradient: G) -> Self {
289        Self { gradient }
290    }
291}
292
293impl<G: GradientFunction> GradientFunction for GradientReflectAdaptor<G> {
294    #[inline]
295    fn calculate(&self, x: i32, y: i32, d: i32) -> i32 {
296        let d2 = d << 1;
297        let mut ret = self.gradient.calculate(x, y, d) % d2;
298        if ret < 0 {
299            ret += d2;
300        }
301        if ret >= d {
302            ret = d2 - ret;
303        }
304        ret
305    }
306}
307
308// ============================================================================
309// SpanGradient — the main gradient span generator
310// ============================================================================
311
312/// Main gradient span generator.
313///
314/// Combines an interpolator (for coordinate transformation), a gradient
315/// function (for shape), and a color function (for color lookup) to
316/// produce gradient-colored pixel spans.
317///
318/// Port of C++ `span_gradient<ColorT, Interpolator, GradientF, ColorF>`.
319pub struct SpanGradient<'a, G, F> {
320    interpolator: SpanInterpolatorLinear,
321    gradient_function: G,
322    color_function: &'a F,
323    d1: i32,
324    d2: i32,
325}
326
327impl<'a, G: GradientFunction, F: ColorFunction> SpanGradient<'a, G, F> {
328    pub fn new(
329        interpolator: SpanInterpolatorLinear,
330        gradient_function: G,
331        color_function: &'a F,
332        d1: f64,
333        d2: f64,
334    ) -> Self {
335        Self {
336            interpolator,
337            gradient_function,
338            color_function,
339            d1: iround(d1 * GRADIENT_SUBPIXEL_SCALE as f64),
340            d2: iround(d2 * GRADIENT_SUBPIXEL_SCALE as f64),
341        }
342    }
343
344    pub fn interpolator(&self) -> &SpanInterpolatorLinear {
345        &self.interpolator
346    }
347
348    pub fn interpolator_mut(&mut self) -> &mut SpanInterpolatorLinear {
349        &mut self.interpolator
350    }
351
352    pub fn gradient_function(&self) -> &G {
353        &self.gradient_function
354    }
355
356    pub fn color_function(&self) -> &F {
357        self.color_function
358    }
359
360    pub fn d1(&self) -> f64 {
361        self.d1 as f64 / GRADIENT_SUBPIXEL_SCALE as f64
362    }
363
364    pub fn d2(&self) -> f64 {
365        self.d2 as f64 / GRADIENT_SUBPIXEL_SCALE as f64
366    }
367
368    pub fn set_d1(&mut self, v: f64) {
369        self.d1 = iround(v * GRADIENT_SUBPIXEL_SCALE as f64);
370    }
371
372    pub fn set_d2(&mut self, v: f64) {
373        self.d2 = iround(v * GRADIENT_SUBPIXEL_SCALE as f64);
374    }
375}
376
377impl<'a, G, F> SpanGenerator for SpanGradient<'a, G, F>
378where
379    G: GradientFunction,
380    F: ColorFunction,
381    F::Color: Copy,
382{
383    type Color = F::Color;
384
385    fn prepare(&mut self) {}
386
387    fn generate(&mut self, span: &mut [F::Color], x: i32, y: i32, len: u32) {
388        let dd = (self.d2 - self.d1).max(1);
389        self.interpolator.begin(x as f64 + 0.5, y as f64 + 0.5, len);
390        let color_size = self.color_function.size() as i32;
391        for pixel in span.iter_mut().take(len as usize) {
392            let mut ix = 0i32;
393            let mut iy = 0i32;
394            self.interpolator.coordinates(&mut ix, &mut iy);
395            let d = self.gradient_function.calculate(
396                ix >> DOWNSCALE_SHIFT,
397                iy >> DOWNSCALE_SHIFT,
398                self.d2,
399            );
400            let d = (((d - self.d1) * color_size) / dd).clamp(0, color_size - 1);
401            *pixel = self.color_function.get(d as usize);
402            self.interpolator.next();
403        }
404    }
405}
406
407// ============================================================================
408// Tests
409// ============================================================================
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414    use crate::color::Rgba8;
415    use crate::gradient_lut::{GradientLinearColor, GradientLut};
416    use crate::trans_affine::TransAffine;
417
418    // ===== Gradient function tests =====
419
420    #[test]
421    fn test_gradient_x() {
422        let g = GradientX;
423        assert_eq!(g.calculate(100, 200, 500), 100);
424        assert_eq!(g.calculate(-50, 200, 500), -50);
425    }
426
427    #[test]
428    fn test_gradient_y() {
429        let g = GradientY;
430        assert_eq!(g.calculate(100, 200, 500), 200);
431        assert_eq!(g.calculate(100, -50, 500), -50);
432    }
433
434    #[test]
435    fn test_gradient_radial() {
436        let g = GradientRadial;
437        // (3,4) → sqrt(9+16) = 5
438        assert_eq!(g.calculate(3, 4, 100), 5);
439        // Origin
440        assert_eq!(g.calculate(0, 0, 100), 0);
441    }
442
443    #[test]
444    fn test_gradient_radial_d() {
445        let g = GradientRadialD;
446        // (3,4) → sqrt(25) = 5
447        assert_eq!(g.calculate(3, 4, 100), 5);
448    }
449
450    #[test]
451    fn test_gradient_diamond() {
452        let g = GradientDiamond;
453        assert_eq!(g.calculate(3, 5, 100), 5);
454        assert_eq!(g.calculate(7, 5, 100), 7);
455        assert_eq!(g.calculate(-3, 5, 100), 5);
456        assert_eq!(g.calculate(3, -8, 100), 8);
457    }
458
459    #[test]
460    fn test_gradient_xy() {
461        let g = GradientXY;
462        assert_eq!(g.calculate(10, 20, 100), 2); // 200/100 = 2
463        assert_eq!(g.calculate(-10, 20, 100), 2);
464    }
465
466    #[test]
467    fn test_gradient_sqrt_xy() {
468        let g = GradientSqrtXY;
469        // sqrt(100 * 100) = 100
470        assert_eq!(g.calculate(100, 100, 500), 100);
471    }
472
473    #[test]
474    fn test_gradient_conic() {
475        let g = GradientConic;
476        // At (1,0): atan2(0,1) = 0 → 0
477        assert_eq!(g.calculate(1, 0, 100), 0);
478        // At (0,1): atan2(1,0) = pi/2 → d/2 = 50
479        assert_eq!(g.calculate(0, 1, 100), 50);
480        // At (-1,0): atan2(0,-1) = pi → d = 100
481        assert_eq!(g.calculate(-1, 0, 100), 100);
482    }
483
484    #[test]
485    fn test_gradient_radial_focus_default() {
486        let g = GradientRadialFocus::new_default();
487        assert_eq!(g.radius(), 100.0);
488        assert_eq!(g.focus_x(), 0.0);
489        assert_eq!(g.focus_y(), 0.0);
490    }
491
492    #[test]
493    fn test_gradient_radial_focus_centered() {
494        let g = GradientRadialFocus::new(100.0, 0.0, 0.0);
495        // At center (0,0), distance should be 0
496        assert_eq!(g.calculate(0, 0, 1600), 0);
497        // At (1600,0) which is 100*16 (the radius in subpixel), should be ~1600
498        let d = g.calculate(1600, 0, 1600);
499        assert!((d - 1600).abs() <= 2, "d={}", d);
500    }
501
502    #[test]
503    fn test_gradient_radial_focus_init() {
504        let mut g = GradientRadialFocus::new_default();
505        g.init(50.0, 10.0, 5.0);
506        assert_eq!(g.radius(), 50.0);
507        // focus_x/y are rounded to gradient subpixel
508        assert!((g.focus_x() - 10.0).abs() < 0.1);
509        assert!((g.focus_y() - 5.0).abs() < 0.1);
510    }
511
512    // ===== Adaptor tests =====
513
514    #[test]
515    fn test_gradient_repeat_adaptor() {
516        let g = GradientRepeatAdaptor::new(GradientX);
517        // 150 % 100 = 50
518        assert_eq!(g.calculate(150, 0, 100), 50);
519        // -50 % 100 → (-50 % 100) + 100 = 50
520        assert_eq!(g.calculate(-50, 0, 100), 50);
521        // 250 % 100 = 50
522        assert_eq!(g.calculate(250, 0, 100), 50);
523    }
524
525    #[test]
526    fn test_gradient_reflect_adaptor() {
527        let g = GradientReflectAdaptor::new(GradientX);
528        // 50 → 50 (within [0,d))
529        assert_eq!(g.calculate(50, 0, 100), 50);
530        // 150 → 150 % 200 = 150, >= 100 → 200 - 150 = 50
531        assert_eq!(g.calculate(150, 0, 100), 50);
532        // 250 → 250 % 200 = 50, < 100 → 50
533        assert_eq!(g.calculate(250, 0, 100), 50);
534    }
535
536    // ===== SpanGradient tests =====
537
538    #[test]
539    fn test_span_gradient_linear_x() {
540        let trans = TransAffine::new();
541        let interp = SpanInterpolatorLinear::new(trans);
542        let gc = GradientLinearColor::new(
543            Rgba8::new(0, 0, 0, 255),
544            Rgba8::new(255, 255, 255, 255),
545            256,
546        );
547        let mut sg = SpanGradient::new(interp, GradientX, &gc, 0.0, 100.0);
548
549        let mut span = vec![Rgba8::default(); 10];
550        sg.generate(&mut span, 0, 0, 10);
551
552        // Gradient should progress from dark to lighter
553        assert!(span[0].r < span[9].r, "s0={} s9={}", span[0].r, span[9].r);
554    }
555
556    #[test]
557    fn test_span_gradient_d1_d2() {
558        let trans = TransAffine::new();
559        let interp = SpanInterpolatorLinear::new(trans);
560        let gc = GradientLinearColor::new(
561            Rgba8::new(0, 0, 0, 255),
562            Rgba8::new(255, 255, 255, 255),
563            256,
564        );
565        let sg = SpanGradient::new(interp, GradientX, &gc, 10.0, 200.0);
566        assert!((sg.d1() - 10.0).abs() < 0.1);
567        assert!((sg.d2() - 200.0).abs() < 0.1);
568    }
569
570    #[test]
571    fn test_span_gradient_with_lut() {
572        let trans = TransAffine::new();
573        let interp = SpanInterpolatorLinear::new(trans);
574        let mut lut = GradientLut::new_default();
575        lut.add_color(0.0, Rgba8::new(255, 0, 0, 255));
576        lut.add_color(1.0, Rgba8::new(0, 0, 255, 255));
577        lut.build_lut();
578
579        let mut sg = SpanGradient::new(interp, GradientX, &lut, 0.0, 100.0);
580
581        let mut span = vec![Rgba8::default(); 5];
582        sg.generate(&mut span, 0, 0, 5);
583
584        // First pixel should be reddish (near start of gradient)
585        assert!(span[0].r > 200, "r={}", span[0].r);
586    }
587
588    #[test]
589    fn test_span_gradient_clamping() {
590        // Test that out-of-range values are clamped to [0, size-1]
591        let trans = TransAffine::new();
592        let interp = SpanInterpolatorLinear::new(trans);
593        let gc = GradientLinearColor::new(
594            Rgba8::new(0, 0, 0, 255),
595            Rgba8::new(255, 255, 255, 255),
596            256,
597        );
598        // d1=90, d2=100 — most x values will be < d1 (clamped to 0)
599        let mut sg = SpanGradient::new(interp, GradientX, &gc, 90.0, 100.0);
600
601        let mut span = vec![Rgba8::default(); 5];
602        sg.generate(&mut span, 0, 0, 5);
603        // All should be black (clamped to index 0)
604        for c in &span {
605            assert_eq!(c.r, 0, "Expected black, got r={}", c.r);
606        }
607    }
608
609    #[test]
610    fn test_span_gradient_constants() {
611        assert_eq!(GRADIENT_SUBPIXEL_SHIFT, 4);
612        assert_eq!(GRADIENT_SUBPIXEL_SCALE, 16);
613        assert_eq!(GRADIENT_SUBPIXEL_MASK, 15);
614    }
615}