diff --git a/src/pipeline/lowp.rs b/src/pipeline/lowp.rs index fc1a6c5..f926ea2 100644 --- a/src/pipeline/lowp.rs +++ b/src/pipeline/lowp.rs @@ -126,9 +126,7 @@ pub const STAGES: &[StageFn; super::STAGES_COUNT] = &[ repeat_x1, gradient, evenly_spaced_2_stop_gradient, - // TODO: Can be implemented for lowp as well. The implementation is very similar to its highp - // variant. - null_fn, // XYToUnitAngle + xy_to_unit_angle, xy_to_radius, null_fn, // XYTo2PtConicalFocalOnCircle null_fn, // XYTo2PtConicalWellBehaved @@ -141,7 +139,7 @@ pub const STAGES: &[StageFn; super::STAGES_COUNT] = &[ null_fn, // Alter2PtConicalCompensateFocal null_fn, // Alter2PtConicalUnswap null_fn, // NegateX - null_fn, // ApplyConcentricScaleBias + apply_concentric_scale_bias, null_fn, // GammaExpand2 null_fn, // GammaExpandDestination2 null_fn, // GammaCompress2 @@ -646,6 +644,35 @@ fn evenly_spaced_2_stop_gradient(p: &mut Pipeline) { p.next_stage(); } + +fn xy_to_unit_angle(p: &mut Pipeline) { + let x = join(&p.r, &p.g); + let y = join(&p.b, &p.a); + let (x_abs, y_abs) = (x.abs(), y.abs()); + + let slope = x_abs.min(y_abs) / x_abs.max(y_abs); + let s = slope * slope; + // Use a 7th degree polynomial to approximate atan. + // This was generated using sollya.gforge.inria.fr. + // A float optimized polynomial was generated using the following command. + // P1 = fpminimax((1/(2*Pi))*atan(x),[|1,3,5,7|],[|24...|],[2^(-40),1],relative); + let phi = slope + * (f32x16::splat(0.15912117063999176025390625) + + s * (f32x16::splat(-5.185396969318389892578125e-2) + + s * (f32x16::splat(2.476101927459239959716796875e-2) + + s * (f32x16::splat(-7.0547382347285747528076171875e-3))))); + let phi = x_abs.cmp_lt(y_abs).blend(f32x16::splat(0.25) - phi, phi); + let phi = x + .cmp_lt(f32x16::splat(0.0)) + .blend(f32x16::splat(0.5) - phi, phi); + let phi = y + .cmp_lt(f32x16::splat(0.0)) + .blend(f32x16::splat(1.0) - phi, phi); + let phi = phi.cmp_ne(phi).blend(f32x16::splat(0.0), phi); + split(&phi, &mut p.r, &mut p.g); + p.next_stage(); +} + fn xy_to_radius(p: &mut Pipeline) { let x = join(&p.r, &p.g); let y = join(&p.b, &p.a); @@ -656,6 +683,17 @@ fn xy_to_radius(p: &mut Pipeline) { p.next_stage(); } + +fn apply_concentric_scale_bias(p: &mut Pipeline) { + let ctx = &p.ctx.two_point_conical_gradient; + + let t = join(&p.r, &p.g); + let t = mad(t, f32x16::splat(ctx.p0), f32x16::splat(ctx.p1)); + split(&t, &mut p.r, &mut p.g); + + p.next_stage(); +} + // We are using u16 for index, not u32 as Skia, to simplify the code a bit. // The gradient creation code will not allow that many stops anyway. fn gradient_lookup( diff --git a/src/wide/f32x16_t.rs b/src/wide/f32x16_t.rs index 3cd76a1..6716a35 100644 --- a/src/wide/f32x16_t.rs +++ b/src/wide/f32x16_t.rs @@ -54,6 +54,22 @@ impl f32x16 { ) } + pub fn min(self, rhs: Self) -> Self { + Self(self.0.min(rhs.0), self.1.min(rhs.1)) + } + + pub fn max(self, rhs: Self) -> Self { + Self(self.0.max(rhs.0), self.1.max(rhs.1)) + } + + pub fn cmp_ne(self, rhs: Self) -> Self { + Self(self.0.cmp_ne(rhs.0), self.1.cmp_ne(rhs.1)) + } + + pub fn cmp_lt(self, rhs: Self) -> Self { + Self(self.0.cmp_lt(rhs.0), self.1.cmp_lt(rhs.1)) + } + pub fn cmp_gt(self, rhs: &Self) -> Self { Self(self.0.cmp_gt(rhs.0), self.1.cmp_gt(rhs.1)) } @@ -136,3 +152,11 @@ impl core::ops::Mul for f32x16 { Self(self.0 * rhs.0, self.1 * rhs.1) } } + +impl core::ops::Div for f32x16 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + Self(self.0 / rhs.0, self.1 / rhs.1) + } +} diff --git a/tests/images/gradients/concentric-radial.png b/tests/images/gradients/concentric-radial-hq.png similarity index 100% rename from tests/images/gradients/concentric-radial.png rename to tests/images/gradients/concentric-radial-hq.png diff --git a/tests/images/gradients/concentric-radial-lq.png b/tests/images/gradients/concentric-radial-lq.png new file mode 100644 index 0000000..c9cdf22 Binary files /dev/null and b/tests/images/gradients/concentric-radial-lq.png differ diff --git a/tests/images/gradients/sweep-gradient-full.png b/tests/images/gradients/sweep-gradient-full-hq.png similarity index 100% rename from tests/images/gradients/sweep-gradient-full.png rename to tests/images/gradients/sweep-gradient-full-hq.png diff --git a/tests/images/gradients/sweep-gradient-full-lq.png b/tests/images/gradients/sweep-gradient-full-lq.png new file mode 100644 index 0000000..4366607 Binary files /dev/null and b/tests/images/gradients/sweep-gradient-full-lq.png differ diff --git a/tests/images/gradients/sweep-gradient.png b/tests/images/gradients/sweep-gradient-hq.png similarity index 100% rename from tests/images/gradients/sweep-gradient.png rename to tests/images/gradients/sweep-gradient-hq.png diff --git a/tests/images/gradients/sweep-gradient-lq.png b/tests/images/gradients/sweep-gradient-lq.png new file mode 100644 index 0000000..7958545 Binary files /dev/null and b/tests/images/gradients/sweep-gradient-lq.png differ diff --git a/tests/integration/gradients.rs b/tests/integration/gradients.rs index 87d7af0..4c6239d 100644 --- a/tests/integration/gradients.rs +++ b/tests/integration/gradients.rs @@ -469,7 +469,7 @@ fn strip_gradient() { } #[test] -fn concentric_radial() { +fn concentric_radial_lq() { // Same center, non-zero start radius (concentric gradient) let mut paint = Paint::default(); paint.anti_alias = false; @@ -491,7 +491,35 @@ fn concentric_radial() { let mut pixmap = Pixmap::new(200, 200).unwrap(); pixmap.fill_path(&path, &paint, FillRule::Winding, Transform::identity(), None); - let expected = Pixmap::load_png("tests/images/gradients/concentric-radial.png").unwrap(); + let expected = Pixmap::load_png("tests/images/gradients/concentric-radial-lq.png").unwrap(); + assert_eq!(pixmap, expected); +} + +#[test] +fn concentric_radial_hq() { + // Same center, non-zero start radius (concentric gradient) + let mut paint = Paint::default(); + paint.anti_alias = false; + paint.force_hq_pipeline = true; + paint.shader = RadialGradient::new( + Point::from_xy(100.0, 100.0), + 30.0, + Point::from_xy(100.0, 100.0), + 90.0, + vec![ + GradientStop::new(0.0, Color::from_rgba8(50, 127, 150, 200)), + GradientStop::new(1.0, Color::from_rgba8(220, 140, 75, 180)), + ], + SpreadMode::Pad, + Transform::identity(), + ).unwrap(); + + let path = PathBuilder::from_rect(Rect::from_ltrb(10.0, 10.0, 190.0, 190.0).unwrap()); + + let mut pixmap = Pixmap::new(200, 200).unwrap(); + pixmap.fill_path(&path, &paint, FillRule::Winding, Transform::identity(), None); + + let expected = Pixmap::load_png("tests/images/gradients/concentric-radial-hq.png").unwrap(); assert_eq!(pixmap, expected); } @@ -527,9 +555,74 @@ fn conical_smaller_radial() { } #[test] -fn sweep_gradient() { +fn sweep_gradient_lq() { + let mut paint = Paint::default(); + paint.anti_alias = false; + paint.shader = SweepGradient::new( + Point::from_xy(100.0, 100.0), + 135.0, + 225.0, + vec![ + GradientStop::new(0.0, Color::from_rgba8(50, 127, 150, 200)), + GradientStop::new(1.0, Color::from_rgba8(220, 140, 75, 180)), + ], + SpreadMode::Pad, + Transform::identity(), + ) + .unwrap(); + + let path = PathBuilder::from_rect(Rect::from_ltrb(10.0, 10.0, 190.0, 190.0).unwrap()); + + let mut pixmap = Pixmap::new(200, 200).unwrap(); + pixmap.fill_path( + &path, + &paint, + FillRule::Winding, + Transform::identity(), + None, + ); + + let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient-lq.png").unwrap(); + assert_eq!(pixmap, expected); +} + +#[test] +fn sweep_gradient_full_lq() { + let mut paint = Paint::default(); + paint.anti_alias = false; + paint.shader = SweepGradient::new( + Point::from_xy(100.0, 100.0), + 0.0, + 360.0, + vec![ + GradientStop::new(0.0, Color::from_rgba8(50, 127, 150, 200)), + GradientStop::new(1.0, Color::from_rgba8(220, 140, 75, 180)), + ], + SpreadMode::Pad, + Transform::identity(), + ) + .unwrap(); + + let path = PathBuilder::from_rect(Rect::from_ltrb(10.0, 10.0, 190.0, 190.0).unwrap()); + + let mut pixmap = Pixmap::new(200, 200).unwrap(); + pixmap.fill_path( + &path, + &paint, + FillRule::Winding, + Transform::identity(), + None, + ); + + let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient-full-lq.png").unwrap(); + assert_eq!(pixmap, expected); +} + +#[test] +fn sweep_gradient_hq() { let mut paint = Paint::default(); paint.anti_alias = false; + paint.force_hq_pipeline = true; paint.shader = SweepGradient::new( Point::from_xy(100.0, 100.0), 135.0, @@ -554,14 +647,15 @@ fn sweep_gradient() { None, ); - let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient.png").unwrap(); + let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient-hq.png").unwrap(); assert_eq!(pixmap, expected); } #[test] -fn sweep_gradient_full() { +fn sweep_gradient_full_hq() { let mut paint = Paint::default(); paint.anti_alias = false; + paint.force_hq_pipeline = true; paint.shader = SweepGradient::new( Point::from_xy(100.0, 100.0), 0.0, @@ -586,6 +680,6 @@ fn sweep_gradient_full() { None, ); - let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient-full.png").unwrap(); + let expected = Pixmap::load_png("tests/images/gradients/sweep-gradient-full-hq.png").unwrap(); assert_eq!(pixmap, expected); }