blob: e97d41309266649ead7c3e8e0ddc45b6949b3138 [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Gradient rendering example scenes.
//! Scenes demonstrating gradient rendering with different extend modes. Taken from Vello Classic
//! test scenes:
//! - `GradientExtendScene`:
//! - `gradient_extend` method from `https://github.com/linebender/vello/blob/0f3ef03a823eb10b0d7a60164e286cde77ffa222/examples/scenes/src/test_scenes.rs#L815`
//! - `RadialScene`:
//! - `two_point_radial` method from `https://github.com/linebender/vello/blob/0f3ef03a823eb10b0d7a60164e286cde77ffa222/examples/scenes/src/test_scenes.rs#L882`
use crate::{ExampleScene, RenderingContext};
use smallvec::smallvec;
use vello_common::color::palette::css::{BLACK, BLUE, LIME, RED, WHITE, YELLOW};
use vello_common::kurbo::{Affine, Ellipse, Point, Rect, Shape, Stroke};
use vello_common::peniko::{Color, ColorStop, ColorStops, Extend, Gradient, color::DynamicColor};
use vello_common::peniko::{LinearGradientPosition, RadialGradientPosition, SweepGradientPosition};
/// Gradient scene state
#[derive(Debug, Default)]
pub struct GradientExtendScene {}
impl GradientExtendScene {
/// Create a new gradient extend scene
pub fn new() -> Self {
Self {}
}
}
impl ExampleScene for GradientExtendScene {
fn render(&mut self, ctx: &mut impl RenderingContext, root_transform: Affine) {
enum Kind {
Linear,
Radial,
Sweep,
}
/// Helper function to create color stops
fn create_color_stops(colors: &[Color]) -> ColorStops {
ColorStops(smallvec![
ColorStop {
offset: 0.0,
color: DynamicColor::from_alpha_color(colors[0]),
},
ColorStop {
offset: 0.5,
color: DynamicColor::from_alpha_color(colors[1]),
},
ColorStop {
offset: 1.0,
color: DynamicColor::from_alpha_color(colors[2]),
},
])
}
/// Helper function to create a square with a specific gradient type and extend mode
fn square(ctx: &mut impl RenderingContext, kind: Kind, transform: Affine, extend: Extend) {
let colors = [RED, LIME, BLUE];
let width = 300.0;
let height = 300.0;
let gradient = match kind {
Kind::Linear => {
let start_x = width * 0.35;
let start_y = height * 0.5;
let end_x = width * 0.65;
let end_y = height * 0.5;
Gradient {
kind: LinearGradientPosition {
start: Point::new(start_x, start_y),
end: Point::new(end_x, end_y),
}
.into(),
stops: create_color_stops(&colors),
extend,
..Default::default()
}
}
Kind::Radial => {
let center_x = width * 0.5;
let center_y = height * 0.5;
#[expect(
clippy::cast_possible_truncation,
reason = "Width is always positive and bounded"
)]
let radius = (width * 0.25) as f32;
Gradient {
kind: RadialGradientPosition {
start_center: Point::new(center_x, center_y),
start_radius: radius * 0.25,
end_center: Point::new(center_x, center_y),
end_radius: radius,
}
.into(),
stops: create_color_stops(&colors),
extend,
..Default::default()
}
}
Kind::Sweep => {
let center_x = width * 0.5;
let center_y = height * 0.5;
Gradient {
kind: SweepGradientPosition {
center: Point::new(center_x, center_y),
start_angle: 30.0_f32.to_radians(),
end_angle: 150.0_f32.to_radians(),
}
.into(),
stops: create_color_stops(&colors),
extend,
..Default::default()
}
}
};
ctx.set_transform(transform);
ctx.set_paint(gradient);
ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));
}
let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
for (x, extend) in extend_modes.iter().enumerate() {
for (y, kind) in [Kind::Linear, Kind::Radial, Kind::Sweep]
.into_iter()
.enumerate()
{
let transform = root_transform
* Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0));
square(ctx, kind, transform, *extend);
}
}
}
}
/// Two-point radial gradient scene
#[derive(Debug, Default)]
pub struct RadialScene;
impl RadialScene {
/// Create a new radial scene
pub fn new() -> Self {
Self {}
}
}
impl ExampleScene for RadialScene {
fn render(&mut self, ctx: &mut impl RenderingContext, root_transform: Affine) {
/// Helper function to create color stops
fn create_color_stops(colors: &[Color]) -> ColorStops {
ColorStops(smallvec![
ColorStop {
offset: 0.0,
color: DynamicColor::from_alpha_color(colors[0]),
},
ColorStop {
offset: 0.5,
color: DynamicColor::from_alpha_color(colors[1]),
},
ColorStop {
offset: 1.0,
color: DynamicColor::from_alpha_color(colors[2]),
},
])
}
/// Helper function to create a two-point radial gradient rectangle
fn make(
ctx: &mut impl RenderingContext,
x0: f64,
y0: f64,
r0: f32,
x1: f64,
y1: f64,
r1: f32,
transform: Affine,
extend: Extend,
) {
let colors = [RED, YELLOW, Color::from_rgb8(6, 85, 186)];
let width = 400.0;
let height = 200.0;
ctx.set_transform(transform);
ctx.set_paint(WHITE);
ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));
let gradient = Gradient {
kind: RadialGradientPosition {
start_center: Point::new(x0, y0),
start_radius: r0,
end_center: Point::new(x1, y1),
end_radius: r1,
}
.into(),
stops: create_color_stops(&colors),
extend,
..Default::default()
};
ctx.set_paint(gradient);
ctx.fill_rect(&Rect::new(0.0, 0.0, width, height));
// Draw stroke circles showing the gradient extents
let r0 = r0 as f64 - 1.0;
let r1 = r1 as f64 - 1.0;
ctx.set_paint(BLACK);
ctx.set_stroke(Stroke::new(1.0));
ctx.stroke_path(&Ellipse::new((x0, y0), (r0, r0), 0.0).to_path(0.1));
ctx.stroke_path(&Ellipse::new((x1, y1), (r1, r1), 0.0).to_path(0.1));
}
// These demonstrate radial gradient patterns similar to the examples shown
// at <https://learn.microsoft.com/en-us/typography/opentype/spec/colr#radial-gradients>
// Row 1: Basic two-point radial gradient
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
.iter()
.enumerate()
{
let y = 100.0;
let x0 = 140.0;
let x1 = x0 + 140.0;
let r0 = 20.0;
let r1 = 50.0;
make(
ctx,
x0,
y,
r0,
x1,
y,
r1,
root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 20.0)),
*mode,
);
}
// Row 2: Reversed two-point radial gradient
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
.iter()
.enumerate()
{
let y = 100.0;
let x0: f64 = 140.0;
let x1 = x0 + 140.0;
let r0 = 20.0;
let r1 = 50.0;
make(
ctx,
x1,
y,
r1,
x0,
y,
r0,
root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 240.0)),
*mode,
);
}
// Row 3: Equal radii gradient
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
.iter()
.enumerate()
{
let y = 100.0;
let x0 = 140.0;
let x1 = x0 + 140.0;
let r0 = 50.0;
let r1 = 50.0;
make(
ctx,
x0,
y,
r0,
x1,
y,
r1,
root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 460.0)),
*mode,
);
}
// Row 4: Overlapping circles
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
.iter()
.enumerate()
{
let x0 = 140.0;
let y0 = 125.0;
let r0 = 20.0;
let x1 = 190.0;
let y1 = 100.0;
let r1 = 95.0;
make(
ctx,
x0,
y0,
r0,
x1,
y1,
r1,
root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 680.0)),
*mode,
);
}
// Row 5: Touching circles
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
.iter()
.enumerate()
{
let x0 = 140.0;
let y0 = 125.0;
let r0: f32 = 20.0;
let x1 = 190.0;
let y1 = 100.0;
let r1: f32 = 96.0;
// Shift p0 so the outer edges of both circles touch
let direction = Point::new(x0, y0) - Point::new(x1, y1);
let normalized_direction = direction.normalize();
let p0 = Point::new(x1, y1) + (normalized_direction * (r1 - r0) as f64);
make(
ctx,
p0.x,
p0.y,
r0,
x1,
y1,
r1,
root_transform * Affine::translate((i as f64 * 420.0 + 20.0, 900.0)),
*mode,
);
}
}
}