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        // Hoist the loop-invariant range/size values into locals so the inner loop
389        // touches no `self` fields per pixel. The per-pixel sequence (coordinates →
390        // calculate → scale/clamp → lut get → next) is left byte-for-byte identical.
391        let d1 = self.d1;
392        let d2 = self.d2;
393        let dd = (d2 - d1).max(1);
394        let color_size = self.color_function.size() as i32;
395        let color_max = color_size - 1;
396        self.interpolator.begin(x as f64 + 0.5, y as f64 + 0.5, len);
397        for pixel in span.iter_mut().take(len as usize) {
398            let mut ix = 0i32;
399            let mut iy = 0i32;
400            self.interpolator.coordinates(&mut ix, &mut iy);
401            let d = self.gradient_function.calculate(
402                ix >> DOWNSCALE_SHIFT,
403                iy >> DOWNSCALE_SHIFT,
404                d2,
405            );
406            let d = (((d - d1) * color_size) / dd).clamp(0, color_max);
407            *pixel = self.color_function.get(d as usize);
408            self.interpolator.next();
409        }
410    }
411}
412
413// ============================================================================
414// Tests
415// ============================================================================
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420    use crate::color::Rgba8;
421    use crate::gradient_lut::{GradientLinearColor, GradientLut};
422    use crate::trans_affine::TransAffine;
423
424    // ===== Gradient function tests =====
425
426    #[test]
427    fn test_gradient_x() {
428        let g = GradientX;
429        assert_eq!(g.calculate(100, 200, 500), 100);
430        assert_eq!(g.calculate(-50, 200, 500), -50);
431    }
432
433    #[test]
434    fn test_gradient_y() {
435        let g = GradientY;
436        assert_eq!(g.calculate(100, 200, 500), 200);
437        assert_eq!(g.calculate(100, -50, 500), -50);
438    }
439
440    #[test]
441    fn test_gradient_radial() {
442        let g = GradientRadial;
443        // (3,4) → sqrt(9+16) = 5
444        assert_eq!(g.calculate(3, 4, 100), 5);
445        // Origin
446        assert_eq!(g.calculate(0, 0, 100), 0);
447    }
448
449    #[test]
450    fn test_gradient_radial_d() {
451        let g = GradientRadialD;
452        // (3,4) → sqrt(25) = 5
453        assert_eq!(g.calculate(3, 4, 100), 5);
454    }
455
456    #[test]
457    fn test_gradient_diamond() {
458        let g = GradientDiamond;
459        assert_eq!(g.calculate(3, 5, 100), 5);
460        assert_eq!(g.calculate(7, 5, 100), 7);
461        assert_eq!(g.calculate(-3, 5, 100), 5);
462        assert_eq!(g.calculate(3, -8, 100), 8);
463    }
464
465    #[test]
466    fn test_gradient_xy() {
467        let g = GradientXY;
468        assert_eq!(g.calculate(10, 20, 100), 2); // 200/100 = 2
469        assert_eq!(g.calculate(-10, 20, 100), 2);
470    }
471
472    #[test]
473    fn test_gradient_sqrt_xy() {
474        let g = GradientSqrtXY;
475        // sqrt(100 * 100) = 100
476        assert_eq!(g.calculate(100, 100, 500), 100);
477    }
478
479    #[test]
480    fn test_gradient_conic() {
481        let g = GradientConic;
482        // At (1,0): atan2(0,1) = 0 → 0
483        assert_eq!(g.calculate(1, 0, 100), 0);
484        // At (0,1): atan2(1,0) = pi/2 → d/2 = 50
485        assert_eq!(g.calculate(0, 1, 100), 50);
486        // At (-1,0): atan2(0,-1) = pi → d = 100
487        assert_eq!(g.calculate(-1, 0, 100), 100);
488    }
489
490    #[test]
491    fn test_gradient_radial_focus_default() {
492        let g = GradientRadialFocus::new_default();
493        assert_eq!(g.radius(), 100.0);
494        assert_eq!(g.focus_x(), 0.0);
495        assert_eq!(g.focus_y(), 0.0);
496    }
497
498    #[test]
499    fn test_gradient_radial_focus_centered() {
500        let g = GradientRadialFocus::new(100.0, 0.0, 0.0);
501        // At center (0,0), distance should be 0
502        assert_eq!(g.calculate(0, 0, 1600), 0);
503        // At (1600,0) which is 100*16 (the radius in subpixel), should be ~1600
504        let d = g.calculate(1600, 0, 1600);
505        assert!((d - 1600).abs() <= 2, "d={}", d);
506    }
507
508    #[test]
509    fn test_gradient_radial_focus_init() {
510        let mut g = GradientRadialFocus::new_default();
511        g.init(50.0, 10.0, 5.0);
512        assert_eq!(g.radius(), 50.0);
513        // focus_x/y are rounded to gradient subpixel
514        assert!((g.focus_x() - 10.0).abs() < 0.1);
515        assert!((g.focus_y() - 5.0).abs() < 0.1);
516    }
517
518    // ===== Adaptor tests =====
519
520    #[test]
521    fn test_gradient_repeat_adaptor() {
522        let g = GradientRepeatAdaptor::new(GradientX);
523        // 150 % 100 = 50
524        assert_eq!(g.calculate(150, 0, 100), 50);
525        // -50 % 100 → (-50 % 100) + 100 = 50
526        assert_eq!(g.calculate(-50, 0, 100), 50);
527        // 250 % 100 = 50
528        assert_eq!(g.calculate(250, 0, 100), 50);
529    }
530
531    #[test]
532    fn test_gradient_reflect_adaptor() {
533        let g = GradientReflectAdaptor::new(GradientX);
534        // 50 → 50 (within [0,d))
535        assert_eq!(g.calculate(50, 0, 100), 50);
536        // 150 → 150 % 200 = 150, >= 100 → 200 - 150 = 50
537        assert_eq!(g.calculate(150, 0, 100), 50);
538        // 250 → 250 % 200 = 50, < 100 → 50
539        assert_eq!(g.calculate(250, 0, 100), 50);
540    }
541
542    // ===== SpanGradient tests =====
543
544    #[test]
545    fn test_span_gradient_linear_x() {
546        let trans = TransAffine::new();
547        let interp = SpanInterpolatorLinear::new(trans);
548        let gc = GradientLinearColor::new(
549            Rgba8::new(0, 0, 0, 255),
550            Rgba8::new(255, 255, 255, 255),
551            256,
552        );
553        let mut sg = SpanGradient::new(interp, GradientX, &gc, 0.0, 100.0);
554
555        let mut span = vec![Rgba8::default(); 10];
556        sg.generate(&mut span, 0, 0, 10);
557
558        // Gradient should progress from dark to lighter
559        assert!(span[0].r < span[9].r, "s0={} s9={}", span[0].r, span[9].r);
560    }
561
562    #[test]
563    fn test_span_gradient_d1_d2() {
564        let trans = TransAffine::new();
565        let interp = SpanInterpolatorLinear::new(trans);
566        let gc = GradientLinearColor::new(
567            Rgba8::new(0, 0, 0, 255),
568            Rgba8::new(255, 255, 255, 255),
569            256,
570        );
571        let sg = SpanGradient::new(interp, GradientX, &gc, 10.0, 200.0);
572        assert!((sg.d1() - 10.0).abs() < 0.1);
573        assert!((sg.d2() - 200.0).abs() < 0.1);
574    }
575
576    #[test]
577    fn test_span_gradient_with_lut() {
578        let trans = TransAffine::new();
579        let interp = SpanInterpolatorLinear::new(trans);
580        let mut lut = GradientLut::new_default();
581        lut.add_color(0.0, Rgba8::new(255, 0, 0, 255));
582        lut.add_color(1.0, Rgba8::new(0, 0, 255, 255));
583        lut.build_lut();
584
585        let mut sg = SpanGradient::new(interp, GradientX, &lut, 0.0, 100.0);
586
587        let mut span = vec![Rgba8::default(); 5];
588        sg.generate(&mut span, 0, 0, 5);
589
590        // First pixel should be reddish (near start of gradient)
591        assert!(span[0].r > 200, "r={}", span[0].r);
592    }
593
594    #[test]
595    fn test_span_gradient_clamping() {
596        // Test that out-of-range values are clamped to [0, size-1]
597        let trans = TransAffine::new();
598        let interp = SpanInterpolatorLinear::new(trans);
599        let gc = GradientLinearColor::new(
600            Rgba8::new(0, 0, 0, 255),
601            Rgba8::new(255, 255, 255, 255),
602            256,
603        );
604        // d1=90, d2=100 — most x values will be < d1 (clamped to 0)
605        let mut sg = SpanGradient::new(interp, GradientX, &gc, 90.0, 100.0);
606
607        let mut span = vec![Rgba8::default(); 5];
608        sg.generate(&mut span, 0, 0, 5);
609        // All should be black (clamped to index 0)
610        for c in &span {
611            assert_eq!(c.r, 0, "Expected black, got r={}", c.r);
612        }
613    }
614
615    #[test]
616    fn test_span_gradient_constants() {
617        assert_eq!(GRADIENT_SUBPIXEL_SHIFT, 4);
618        assert_eq!(GRADIENT_SUBPIXEL_SCALE, 16);
619        assert_eq!(GRADIENT_SUBPIXEL_MASK, 15);
620    }
621}