mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-24 02:16:32 +11:00
8cfe903f7e
Using vector graphics for the image doesn't make a huge amount of sense. The flower photo is by Raph and happily licensed to anyone who wants to use it.
629 lines
18 KiB
Rust
629 lines
18 KiB
Rust
use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet};
|
|
use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect};
|
|
use vello::peniko::*;
|
|
use vello::*;
|
|
|
|
const FLOWER_IMAGE: &[u8] = include_bytes!("../../assets/splash-flower.jpg");
|
|
|
|
macro_rules! scene {
|
|
($name: ident) => {
|
|
scene!($name: false)
|
|
};
|
|
($name: ident: animated) => {
|
|
scene!($name: true)
|
|
};
|
|
($name: ident: $animated: literal) => {
|
|
ExampleScene {
|
|
config: SceneConfig {
|
|
animated: $animated,
|
|
name: stringify!($name).to_owned(),
|
|
},
|
|
function: Box::new($name),
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn test_scenes() -> SceneSet {
|
|
// For WASM below, must be mutable
|
|
#[allow(unused_mut)]
|
|
let mut scenes = vec![
|
|
scene!(funky_paths),
|
|
scene!(cardioid_and_friends),
|
|
scene!(animated_text: animated),
|
|
scene!(brush_transform: animated),
|
|
scene!(blend_grid),
|
|
scene!(conflation_artifacts),
|
|
scene!(labyrinth),
|
|
scene!(base_color_test: animated),
|
|
];
|
|
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
|
|
scenes.push(ExampleScene {
|
|
config: SceneConfig {
|
|
animated: false,
|
|
name: "included_tiger".to_owned(),
|
|
},
|
|
function: Box::new(included_tiger()),
|
|
});
|
|
|
|
SceneSet { scenes }
|
|
}
|
|
|
|
// Scenes
|
|
|
|
fn funky_paths(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|
use PathEl::*;
|
|
let missing_movetos = [
|
|
LineTo((100.0, 100.0).into()),
|
|
LineTo((100.0, 200.0).into()),
|
|
ClosePath,
|
|
LineTo((0.0, 400.0).into()),
|
|
LineTo((100.0, 400.0).into()),
|
|
];
|
|
let only_movetos = [MoveTo((0.0, 0.0).into()), MoveTo((100.0, 100.0).into())];
|
|
let empty: [PathEl; 0] = [];
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((100.0, 100.0)),
|
|
Color::rgb8(0, 0, 255),
|
|
None,
|
|
&missing_movetos,
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgb8(0, 0, 255),
|
|
None,
|
|
&empty,
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgb8(0, 0, 255),
|
|
None,
|
|
&only_movetos,
|
|
);
|
|
sb.stroke(
|
|
&Stroke::new(8.0),
|
|
Affine::translate((100.0, 100.0)),
|
|
Color::rgb8(0, 255, 255),
|
|
None,
|
|
&missing_movetos,
|
|
);
|
|
}
|
|
|
|
fn cardioid_and_friends(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|
render_cardioid(sb);
|
|
render_clip_test(sb);
|
|
render_alpha_test(sb);
|
|
//render_tiger(sb, false);
|
|
}
|
|
|
|
fn animated_text(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|
// Uses the static array address as a cache key for expedience. Real code
|
|
// should use a better strategy.
|
|
let piet_logo = params
|
|
.images
|
|
.from_bytes(FLOWER_IMAGE.as_ptr() as usize, FLOWER_IMAGE)
|
|
.unwrap();
|
|
|
|
use PathEl::*;
|
|
let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0));
|
|
let star = [
|
|
MoveTo((50.0, 0.0).into()),
|
|
LineTo((21.0, 90.0).into()),
|
|
LineTo((98.0, 35.0).into()),
|
|
LineTo((2.0, 35.0).into()),
|
|
LineTo((79.0, 90.0).into()),
|
|
ClosePath,
|
|
];
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
&Brush::Solid(Color::rgb8(128, 128, 128)),
|
|
None,
|
|
&rect,
|
|
);
|
|
let text_size = 60.0 + 40.0 * (params.time as f32).sin();
|
|
let s = "\u{1f600}hello vello text!";
|
|
params.text.add(
|
|
sb,
|
|
None,
|
|
text_size,
|
|
None,
|
|
Affine::translate((110.0, 600.0)),
|
|
s,
|
|
);
|
|
params.text.add_run(
|
|
sb,
|
|
None,
|
|
text_size,
|
|
Color::WHITE,
|
|
Affine::translate((110.0, 700.0)),
|
|
// Add a skew to simulate an oblique font.
|
|
Some(Affine::skew(20f64.to_radians().tan(), 0.0)),
|
|
&Stroke::new(1.0),
|
|
s,
|
|
);
|
|
let t = ((params.time).sin() * 0.5 + 0.5) as f32;
|
|
let weight = t * 700.0 + 200.0;
|
|
let width = t * 150.0 + 50.0;
|
|
params.text.add_var_run(
|
|
sb,
|
|
None,
|
|
72.0,
|
|
&[("wght", weight), ("wdth", width)],
|
|
Color::WHITE,
|
|
Affine::translate((110.0, 800.0)),
|
|
// Add a skew to simulate an oblique font.
|
|
None,
|
|
Fill::NonZero,
|
|
"And some vello\ntext with a newline",
|
|
);
|
|
let th = params.time as f64;
|
|
let center = Point::new(500.0, 500.0);
|
|
let mut p1 = center;
|
|
p1.x += 400.0 * th.cos();
|
|
p1.y += 400.0 * th.sin();
|
|
sb.stroke(
|
|
&Stroke::new(5.0),
|
|
Affine::IDENTITY,
|
|
&Brush::Solid(Color::rgb8(128, 0, 0)),
|
|
None,
|
|
&[PathEl::MoveTo(center), PathEl::LineTo(p1)],
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((150.0, 150.0)) * Affine::scale(0.2),
|
|
Color::RED,
|
|
None,
|
|
&rect,
|
|
);
|
|
let alpha = (params.time as f64).sin() as f32 * 0.5 + 0.5;
|
|
sb.push_layer(Mix::Normal, alpha, Affine::IDENTITY, &rect);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((100.0, 100.0)) * Affine::scale(0.2),
|
|
Color::BLUE,
|
|
None,
|
|
&rect,
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((200.0, 200.0)) * Affine::scale(0.2),
|
|
Color::GREEN,
|
|
None,
|
|
&rect,
|
|
);
|
|
sb.pop_layer();
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((400.0, 100.0)),
|
|
Color::PURPLE,
|
|
None,
|
|
&star,
|
|
);
|
|
sb.fill(
|
|
Fill::EvenOdd,
|
|
Affine::translate((500.0, 100.0)),
|
|
Color::PURPLE,
|
|
None,
|
|
&star,
|
|
);
|
|
sb.draw_image(
|
|
&piet_logo,
|
|
Affine::translate((800.0, 50.0)) * Affine::rotate(20f64.to_radians()),
|
|
);
|
|
}
|
|
|
|
fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|
let th = params.time;
|
|
let linear = Gradient::new_linear((0.0, 0.0), (0.0, 200.0)).with_stops([
|
|
Color::RED,
|
|
Color::GREEN,
|
|
Color::BLUE,
|
|
]);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::rotate(25f64.to_radians()) * Affine::scale_non_uniform(2.0, 1.0),
|
|
&Gradient::new_radial((200.0, 200.0), 80.0).with_stops([
|
|
Color::RED,
|
|
Color::GREEN,
|
|
Color::BLUE,
|
|
]),
|
|
None,
|
|
&Rect::from_origin_size((100.0, 100.0), (200.0, 200.0)),
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((200.0, 600.0)),
|
|
&linear,
|
|
Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))),
|
|
&Rect::from_origin_size(Point::default(), (400.0, 200.0)),
|
|
);
|
|
sb.stroke(
|
|
&Stroke::new(40.0),
|
|
Affine::translate((800.0, 600.0)),
|
|
&linear,
|
|
Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))),
|
|
&Rect::from_origin_size(Point::default(), (400.0, 200.0)),
|
|
);
|
|
}
|
|
|
|
fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|
const BLEND_MODES: &[Mix] = &[
|
|
Mix::Normal,
|
|
Mix::Multiply,
|
|
Mix::Darken,
|
|
Mix::Screen,
|
|
Mix::Lighten,
|
|
Mix::Overlay,
|
|
Mix::ColorDodge,
|
|
Mix::ColorBurn,
|
|
Mix::HardLight,
|
|
Mix::SoftLight,
|
|
Mix::Difference,
|
|
Mix::Exclusion,
|
|
Mix::Hue,
|
|
Mix::Saturation,
|
|
Mix::Color,
|
|
Mix::Luminosity,
|
|
];
|
|
for (ix, &blend) in BLEND_MODES.iter().enumerate() {
|
|
let i = ix % 4;
|
|
let j = ix / 4;
|
|
let transform = Affine::translate((i as f64 * 225., j as f64 * 225.));
|
|
let square = blend_square(blend.into());
|
|
sb.append(&square, Some(transform));
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
|
|
fn included_tiger() -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
|
|
let contents = include_str!(concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/../assets/Ghostscript_Tiger.svg"
|
|
));
|
|
crate::svg::svg_function_of("Ghostscript Tiger".to_string(), move || contents)
|
|
}
|
|
|
|
// Support functions
|
|
|
|
fn render_cardioid(sb: &mut SceneBuilder) {
|
|
let n = 601;
|
|
let dth = std::f64::consts::PI * 2.0 / (n as f64);
|
|
let center = Point::new(1024.0, 768.0);
|
|
let r = 750.0;
|
|
let mut path = BezPath::new();
|
|
for i in 1..n {
|
|
let mut p0 = center;
|
|
let a0 = i as f64 * dth;
|
|
p0.x += a0.cos() * r;
|
|
p0.y += a0.sin() * r;
|
|
let mut p1 = center;
|
|
let a1 = ((i * 2) % n) as f64 * dth;
|
|
p1.x += a1.cos() * r;
|
|
p1.y += a1.sin() * r;
|
|
path.push(PathEl::MoveTo(p0));
|
|
path.push(PathEl::LineTo(p1));
|
|
}
|
|
sb.stroke(
|
|
&Stroke::new(2.0),
|
|
Affine::IDENTITY,
|
|
Color::rgb8(0, 0, 255),
|
|
None,
|
|
&path,
|
|
);
|
|
}
|
|
|
|
fn render_clip_test(sb: &mut SceneBuilder) {
|
|
const N: usize = 16;
|
|
const X0: f64 = 50.0;
|
|
const Y0: f64 = 450.0;
|
|
// Note: if it gets much larger, it will exceed the 1MB scratch buffer.
|
|
// But this is a pretty demanding test.
|
|
const X1: f64 = 550.0;
|
|
const Y1: f64 = 950.0;
|
|
let step = 1.0 / ((N + 1) as f64);
|
|
for i in 0..N {
|
|
let t = ((i + 1) as f64) * step;
|
|
let path = [
|
|
PathEl::MoveTo((X0, Y0).into()),
|
|
PathEl::LineTo((X1, Y0).into()),
|
|
PathEl::LineTo((X1, Y0 + t * (Y1 - Y0)).into()),
|
|
PathEl::LineTo((X1 + t * (X0 - X1), Y1).into()),
|
|
PathEl::LineTo((X0, Y1).into()),
|
|
PathEl::ClosePath,
|
|
];
|
|
sb.push_layer(Mix::Clip, 1.0, Affine::IDENTITY, &path);
|
|
}
|
|
let rect = Rect::new(X0, Y0, X1, Y1);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
&Brush::Solid(Color::rgb8(0, 255, 0)),
|
|
None,
|
|
&rect,
|
|
);
|
|
for _ in 0..N {
|
|
sb.pop_layer();
|
|
}
|
|
}
|
|
|
|
fn render_alpha_test(sb: &mut SceneBuilder) {
|
|
// Alpha compositing tests.
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgb8(255, 0, 0),
|
|
None,
|
|
&make_diamond(1024.0, 100.0),
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgba8(0, 255, 0, 0x80),
|
|
None,
|
|
&make_diamond(1024.0, 125.0),
|
|
);
|
|
sb.push_layer(
|
|
Mix::Clip,
|
|
1.0,
|
|
Affine::IDENTITY,
|
|
&make_diamond(1024.0, 150.0),
|
|
);
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgba8(0, 0, 255, 0x80),
|
|
None,
|
|
&make_diamond(1024.0, 175.0),
|
|
);
|
|
sb.pop_layer();
|
|
}
|
|
|
|
fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) {
|
|
// Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
|
|
let rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.));
|
|
let linear =
|
|
Gradient::new_linear((0.0, 0.0), (200.0, 0.0)).with_stops([Color::BLACK, Color::WHITE]);
|
|
sb.fill(Fill::NonZero, transform, &linear, None, &rect);
|
|
const GRADIENTS: &[(f64, f64, Color)] = &[
|
|
(150., 0., Color::rgb8(255, 240, 64)),
|
|
(175., 100., Color::rgb8(255, 96, 240)),
|
|
(125., 200., Color::rgb8(64, 192, 255)),
|
|
];
|
|
for (x, y, c) in GRADIENTS {
|
|
let mut color2 = c.clone();
|
|
color2.a = 0;
|
|
let radial = Gradient::new_radial((*x, *y), 100.0).with_stops([*c, color2]);
|
|
sb.fill(Fill::NonZero, transform, &radial, None, &rect);
|
|
}
|
|
const COLORS: &[Color] = &[
|
|
Color::rgb8(255, 0, 0),
|
|
Color::rgb8(0, 255, 0),
|
|
Color::rgb8(0, 0, 255),
|
|
];
|
|
sb.push_layer(Mix::Normal, 1.0, transform, &rect);
|
|
for (i, c) in COLORS.iter().enumerate() {
|
|
let linear = Gradient::new_linear((0.0, 0.0), (0.0, 200.0)).with_stops([Color::WHITE, *c]);
|
|
sb.push_layer(blend, 1.0, transform, &rect);
|
|
// squash the ellipse
|
|
let a = transform
|
|
* Affine::translate((100., 100.))
|
|
* Affine::rotate(std::f64::consts::FRAC_PI_3 * (i * 2 + 1) as f64)
|
|
* Affine::scale_non_uniform(1.0, 0.357)
|
|
* Affine::translate((-100., -100.));
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
a,
|
|
&linear,
|
|
None,
|
|
&Ellipse::new((100., 100.), (90., 90.), 0.),
|
|
);
|
|
sb.pop_layer();
|
|
}
|
|
sb.pop_layer();
|
|
}
|
|
|
|
fn blend_square(blend: BlendMode) -> SceneFragment {
|
|
let mut fragment = SceneFragment::default();
|
|
let mut sb = SceneBuilder::for_fragment(&mut fragment);
|
|
render_blend_square(&mut sb, blend, Affine::IDENTITY);
|
|
fragment
|
|
}
|
|
|
|
fn conflation_artifacts(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|
use PathEl::*;
|
|
const N: f64 = 50.0;
|
|
const S: f64 = 4.0;
|
|
|
|
let scale = Affine::scale(S);
|
|
let x = N + 0.5; // Fractional pixel offset reveals the problem on axis-aligned edges.
|
|
let mut y = N;
|
|
|
|
let bg_color = Color::rgb8(255, 194, 19);
|
|
let fg_color = Color::rgb8(12, 165, 255);
|
|
|
|
// Two adjacent triangles touching at diagonal edge with opposing winding numbers
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((x, y)) * scale,
|
|
fg_color,
|
|
None,
|
|
&[
|
|
// triangle 1
|
|
MoveTo((0.0, 0.0).into()),
|
|
LineTo((N, N).into()),
|
|
LineTo((0.0, N).into()),
|
|
LineTo((0.0, 0.0).into()),
|
|
// triangle 2
|
|
MoveTo((0.0, 0.0).into()),
|
|
LineTo((N, N).into()),
|
|
LineTo((N, 0.0).into()),
|
|
LineTo((0.0, 0.0).into()),
|
|
],
|
|
);
|
|
|
|
// Adjacent rects, opposite winding
|
|
y += S * N + 10.0;
|
|
sb.fill(
|
|
Fill::EvenOdd,
|
|
Affine::translate((x, y)) * scale,
|
|
bg_color,
|
|
None,
|
|
&Rect::new(0.0, 0.0, N, N),
|
|
);
|
|
sb.fill(
|
|
Fill::EvenOdd,
|
|
Affine::translate((x, y)) * scale,
|
|
fg_color,
|
|
None,
|
|
&[
|
|
// left rect
|
|
MoveTo((0.0, 0.0).into()),
|
|
LineTo((0.0, N).into()),
|
|
LineTo((N * 0.5, N).into()),
|
|
LineTo((N * 0.5, 0.0).into()),
|
|
// right rect
|
|
MoveTo((N * 0.5, 0.0).into()),
|
|
LineTo((N, 0.0).into()),
|
|
LineTo((N, N).into()),
|
|
LineTo((N * 0.5, N).into()),
|
|
],
|
|
);
|
|
|
|
// Adjacent rects, same winding
|
|
y += S * N + 10.0;
|
|
sb.fill(
|
|
Fill::EvenOdd,
|
|
Affine::translate((x, y)) * scale,
|
|
bg_color,
|
|
None,
|
|
&Rect::new(0.0, 0.0, N, N),
|
|
);
|
|
sb.fill(
|
|
Fill::EvenOdd,
|
|
Affine::translate((x, y)) * scale,
|
|
fg_color,
|
|
None,
|
|
&[
|
|
// left rect
|
|
MoveTo((0.0, 0.0).into()),
|
|
LineTo((0.0, N).into()),
|
|
LineTo((N * 0.5, N).into()),
|
|
LineTo((N * 0.5, 0.0).into()),
|
|
// right rect
|
|
MoveTo((N * 0.5, 0.0).into()),
|
|
LineTo((N * 0.5, N).into()),
|
|
LineTo((N, N).into()),
|
|
LineTo((N, 0.0).into()),
|
|
],
|
|
);
|
|
}
|
|
|
|
fn labyrinth(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|
use PathEl::*;
|
|
|
|
let rows: &[[u8; 12]] = &[
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1],
|
|
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1],
|
|
[1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
|
[0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
|
[1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0],
|
|
[0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
|
[1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1],
|
|
[0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
|
|
[0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0],
|
|
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
];
|
|
let cols: &[[u8; 10]] = &[
|
|
[1, 1, 1, 1, 0, 1, 1, 1, 1, 1],
|
|
[0, 0, 1, 0, 0, 0, 1, 1, 1, 0],
|
|
[0, 1, 1, 0, 1, 1, 1, 0, 0, 1],
|
|
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0],
|
|
[0, 0, 1, 0, 1, 0, 0, 0, 0, 1],
|
|
[0, 0, 1, 1, 1, 0, 0, 0, 1, 0],
|
|
[0, 1, 0, 1, 1, 1, 0, 0, 0, 0],
|
|
[1, 1, 1, 0, 1, 1, 1, 0, 1, 0],
|
|
[1, 1, 0, 1, 1, 0, 0, 0, 1, 0],
|
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
|
|
[0, 0, 1, 1, 0, 0, 0, 0, 1, 0],
|
|
[0, 0, 0, 0, 0, 0, 1, 0, 0, 1],
|
|
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1],
|
|
];
|
|
let mut path = BezPath::new();
|
|
for (y, row) in rows.iter().enumerate() {
|
|
for (x, flag) in row.iter().enumerate() {
|
|
let x = x as f64;
|
|
let y = y as f64;
|
|
if *flag == 1 {
|
|
path.push(MoveTo((x - 0.1, y + 0.1).into()));
|
|
path.push(LineTo((x + 1.1, y + 0.1).into()));
|
|
path.push(LineTo((x + 1.1, y - 0.1).into()));
|
|
path.push(LineTo((x - 0.1, y - 0.1).into()));
|
|
|
|
// The above is equivalent to the following stroke with width 0.2 and square
|
|
// caps.
|
|
//path.push(MoveTo((x, y).into()));
|
|
//path.push(LineTo((x + 1.0, y).into()));
|
|
}
|
|
}
|
|
}
|
|
for (x, col) in cols.iter().enumerate() {
|
|
for (y, flag) in col.iter().enumerate() {
|
|
let x = x as f64;
|
|
let y = y as f64;
|
|
if *flag == 1 {
|
|
path.push(MoveTo((x - 0.1, y - 0.1).into()));
|
|
path.push(LineTo((x - 0.1, y + 1.1).into()));
|
|
path.push(LineTo((x + 0.1, y + 1.1).into()));
|
|
path.push(LineTo((x + 0.1, y - 0.1).into()));
|
|
// The above is equivalent to the following stroke with width 0.2 and square
|
|
// caps.
|
|
//path.push(MoveTo((x, y).into()));
|
|
//path.push(LineTo((x, y + 1.0).into()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note the artifacts are clearly visible at a fractional pixel offset/translation. They
|
|
// disappear if the translation amount below is a whole number.
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::translate((20.5, 20.5)) * Affine::scale(80.0),
|
|
Color::rgba8(0x70, 0x80, 0x80, 0xff),
|
|
None,
|
|
&path,
|
|
)
|
|
}
|
|
|
|
fn base_color_test(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|
// Cycle through the hue value every 5 seconds (t % 5) * 360/5
|
|
let color = Color::hlc((params.time % 5.0) * 72.0, 80.0, 80.0);
|
|
params.base_color = Some(color);
|
|
|
|
// Blend a white square over it.
|
|
sb.fill(
|
|
Fill::NonZero,
|
|
Affine::IDENTITY,
|
|
Color::rgba8(255, 255, 255, 128),
|
|
None,
|
|
&Rect::new(50.0, 50.0, 500.0, 500.0),
|
|
);
|
|
}
|
|
|
|
fn around_center(xform: Affine, center: Point) -> Affine {
|
|
Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2())
|
|
}
|
|
|
|
fn make_diamond(cx: f64, cy: f64) -> [PathEl; 5] {
|
|
const SIZE: f64 = 50.0;
|
|
[
|
|
PathEl::MoveTo(Point::new(cx, cy - SIZE)),
|
|
PathEl::LineTo(Point::new(cx + SIZE, cy)),
|
|
PathEl::LineTo(Point::new(cx, cy + SIZE)),
|
|
PathEl::LineTo(Point::new(cx - SIZE, cy)),
|
|
PathEl::ClosePath,
|
|
]
|
|
}
|