| // Copyright 2022 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet}; |
| use vello::kurbo::{ |
| Affine, BezPath, Cap, Circle, Ellipse, Join, PathEl, Point, Rect, Shape, Stroke, Vec2, |
| }; |
| 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) => { |
| scene!($name, stringify!($name), $animated) |
| }; |
| ($func:expr, $name: expr, $animated: literal) => { |
| ExampleScene { |
| config: SceneConfig { |
| animated: $animated, |
| name: $name.to_owned(), |
| }, |
| function: Box::new($func), |
| } |
| }; |
| } |
| |
| pub fn test_scenes() -> SceneSet { |
| let scenes = vec![ |
| scene!(splash_with_tiger(), "splash_with_tiger", false), |
| scene!(funky_paths), |
| scene!(stroke_styles(Affine::IDENTITY), "stroke_styles", false), |
| scene!( |
| stroke_styles(Affine::scale_non_uniform(1.2, 0.7)), |
| "stroke_styles (non-uniform scale)", |
| false |
| ), |
| scene!( |
| stroke_styles(Affine::skew(1., 0.)), |
| "stroke_styles (skew)", |
| false |
| ), |
| scene!(tricky_strokes), |
| scene!(fill_types), |
| scene!(cardioid_and_friends), |
| scene!(animated_text: animated), |
| scene!(gradient_extend), |
| scene!(two_point_radial), |
| scene!(brush_transform: animated), |
| scene!(blend_grid), |
| scene!(conflation_artifacts), |
| scene!(labyrinth), |
| scene!(robust_paths), |
| scene!(base_color_test: animated), |
| scene!(clip_test: animated), |
| scene!(longpathdash(Cap::Butt), "longpathdash (butt caps)", false), |
| scene!(longpathdash(Cap::Round), "longpathdash (round caps)", false), |
| scene!(crate::mmark::MMark::new(80_000), "mmark", false), |
| scene!(many_draw_objects), |
| ]; |
| |
| SceneSet { scenes } |
| } |
| |
| // Scenes |
| |
| fn funky_paths(scene: &mut Scene, _: &mut SceneParams) { |
| use PathEl::*; |
| let missing_movetos = [ |
| MoveTo((0., 0.).into()), |
| 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] = []; |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((100.0, 100.0)), |
| Color::rgb8(0, 0, 255), |
| None, |
| &missing_movetos, |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgb8(0, 0, 255), |
| None, |
| &empty, |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgb8(0, 0, 255), |
| None, |
| &only_movetos, |
| ); |
| scene.stroke( |
| &Stroke::new(8.0), |
| Affine::translate((100.0, 100.0)), |
| Color::rgb8(0, 255, 255), |
| None, |
| &missing_movetos, |
| ); |
| } |
| |
| fn stroke_styles(transform: Affine) -> impl FnMut(&mut Scene, &mut SceneParams) { |
| use PathEl::*; |
| move |scene, params| { |
| let colors = [ |
| Color::rgb8(140, 181, 236), |
| Color::rgb8(246, 236, 202), |
| Color::rgb8(201, 147, 206), |
| Color::rgb8(150, 195, 160), |
| ]; |
| let simple_stroke = [MoveTo((0., 0.).into()), LineTo((100., 0.).into())]; |
| let join_stroke = [ |
| MoveTo((0., 0.).into()), |
| CurveTo((20., 0.).into(), (42.5, 5.).into(), (50., 25.).into()), |
| CurveTo((57.5, 5.).into(), (80., 0.).into(), (100., 0.).into()), |
| ]; |
| let miter_stroke = [ |
| MoveTo((0., 0.).into()), |
| LineTo((90., 16.).into()), |
| LineTo((0., 31.).into()), |
| LineTo((90., 46.).into()), |
| ]; |
| let closed_strokes = [ |
| MoveTo((0., 0.).into()), |
| LineTo((90., 21.).into()), |
| LineTo((0., 42.).into()), |
| ClosePath, |
| MoveTo((200., 0.).into()), |
| CurveTo((100., 72.).into(), (300., 72.).into(), (200., 0.).into()), |
| ClosePath, |
| MoveTo((290., 0.).into()), |
| CurveTo((200., 72.).into(), (400., 72.).into(), (310., 0.).into()), |
| ClosePath, |
| ]; |
| let cap_styles = [Cap::Butt, Cap::Square, Cap::Round]; |
| let join_styles = [Join::Bevel, Join::Miter, Join::Round]; |
| let miter_limits = [4., 6., 0.1, 10.]; |
| |
| // Simple strokes with cap combinations |
| let t = Affine::translate((60., 40.)) * Affine::scale(2.); |
| let mut y = 0.; |
| let mut color_idx = 0; |
| for start in cap_styles { |
| for end in cap_styles { |
| params.text.add( |
| scene, |
| None, |
| 12., |
| None, |
| Affine::translate((0., y)) * t, |
| &format!("Start cap: {:?}, End cap: {:?}", start, end), |
| ); |
| scene.stroke( |
| &Stroke::new(20.).with_start_cap(start).with_end_cap(end), |
| Affine::translate((0., y + 30.)) * t * transform, |
| colors[color_idx], |
| None, |
| &simple_stroke, |
| ); |
| y += 180.; |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| } |
| |
| // Dashed strokes with cap combinations |
| let t = Affine::translate((450., 0.)) * t; |
| y = 0.; |
| for start in cap_styles { |
| for end in cap_styles { |
| params.text.add( |
| scene, |
| None, |
| 12., |
| None, |
| Affine::translate((0., y)) * t, |
| &format!("Dashing - Start cap: {:?}, End cap: {:?}", start, end), |
| ); |
| scene.stroke( |
| &Stroke::new(20.) |
| .with_start_cap(start) |
| .with_end_cap(end) |
| .with_dashes(0., [10.0, 21.0]), |
| Affine::translate((0., y + 30.)) * t * transform, |
| colors[color_idx], |
| None, |
| &simple_stroke, |
| ); |
| y += 180.; |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| } |
| |
| // Cap and join combinations |
| let t = Affine::translate((550., 0.)) * t; |
| y = 0.; |
| for cap in cap_styles { |
| for join in join_styles { |
| params.text.add( |
| scene, |
| None, |
| 12., |
| None, |
| Affine::translate((0., y)) * t, |
| &format!("Caps: {:?}, Joins: {:?}", cap, join), |
| ); |
| scene.stroke( |
| &Stroke::new(20.).with_caps(cap).with_join(join), |
| Affine::translate((0., y + 30.)) * t * transform, |
| colors[color_idx], |
| None, |
| &join_stroke, |
| ); |
| y += 185.; |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| } |
| |
| // Miter limit |
| let t = Affine::translate((500., 0.)) * t; |
| y = 0.; |
| for ml in miter_limits { |
| params.text.add( |
| scene, |
| None, |
| 12., |
| None, |
| Affine::translate((0., y)) * t, |
| &format!("Miter limit: {}", ml), |
| ); |
| scene.stroke( |
| &Stroke::new(10.) |
| .with_caps(Cap::Butt) |
| .with_join(Join::Miter) |
| .with_miter_limit(ml), |
| Affine::translate((0., y + 30.)) * t * transform, |
| colors[color_idx], |
| None, |
| &miter_stroke, |
| ); |
| y += 180.; |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| |
| // Closed paths |
| for (i, join) in join_styles.iter().enumerate() { |
| params.text.add( |
| scene, |
| None, |
| 12., |
| None, |
| Affine::translate((0., y)) * t, |
| &format!("Closed path with join: {:?}", join), |
| ); |
| // The cap style is not important since a closed path shouldn't have any caps. |
| scene.stroke( |
| &Stroke::new(10.) |
| .with_caps(cap_styles[i]) |
| .with_join(*join) |
| .with_miter_limit(5.), |
| Affine::translate((0., y + 30.)) * t * transform, |
| colors[color_idx], |
| None, |
| &closed_strokes, |
| ); |
| y += 180.; |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| } |
| } |
| |
| // This test has been adapted from Skia's "trickycubicstrokes" GM slide which can be found at |
| // `github.com/google/skia/blob/0d4d11451c4f4e184305cbdbd67f6b3edfa4b0e3/gm/trickycubicstrokes.cpp` |
| fn tricky_strokes(scene: &mut Scene, params: &mut SceneParams) { |
| use PathEl::*; |
| let colors = [ |
| Color::rgb8(140, 181, 236), |
| Color::rgb8(246, 236, 202), |
| Color::rgb8(201, 147, 206), |
| Color::rgb8(150, 195, 160), |
| ]; |
| |
| const CELL_SIZE: f64 = 200.; |
| const STROKE_WIDTH: f64 = 30.; |
| const NUM_COLS: usize = 5; |
| |
| fn stroke_bounds(pts: &[(f64, f64); 4]) -> Rect { |
| use kurbo::CubicBez; |
| CubicBez::new(pts[0], pts[1], pts[2], pts[3]) |
| .bounding_box() |
| .inflate(STROKE_WIDTH, STROKE_WIDTH) |
| } |
| |
| fn map_rect_to_rect(src: &Rect, dst: &Rect) -> (Affine, f64) { |
| let (scale, x_larger) = { |
| let sx = dst.width() / src.width(); |
| let sy = dst.height() / src.height(); |
| (sx.min(sy), sx > sy) |
| }; |
| let tx = dst.x0 - src.x0 * scale; |
| let ty = dst.y0 - src.y0 * scale; |
| let (tx, ty) = if x_larger { |
| (tx + 0.5 * (dst.width() - src.width() * scale), ty) |
| } else { |
| (tx, ty + 0.5 * (dst.height() - src.height() * scale)) |
| }; |
| (Affine::new([scale, 0.0, 0.0, scale, tx, ty]), scale) |
| } |
| |
| let tricky_cubics = [ |
| [(122., 737.), (348., 553.), (403., 761.), (400., 760.)], |
| [(244., 520.), (244., 518.), (1141., 634.), (394., 688.)], |
| [(550., 194.), (138., 130.), (1035., 246.), (288., 300.)], |
| [(226., 733.), (556., 779.), (-43., 471.), (348., 683.)], |
| [(268., 204.), (492., 304.), (352., 23.), (433., 412.)], |
| [(172., 480.), (396., 580.), (256., 299.), (338., 677.)], |
| [(731., 340.), (318., 252.), (1026., -64.), (367., 265.)], |
| [(475., 708.), (62., 620.), (770., 304.), (220., 659.)], |
| [(0., 0.), (128., 128.), (128., 0.), (0., 128.)], // Perfect cusp |
| [(0., 0.01), (128., 127.999), (128., 0.01), (0., 127.99)], // Near-cusp |
| [(0., -0.01), (128., 128.001), (128., -0.01), (0., 128.001)], // Near-cusp |
| [(0., 0.), (0., -10.), (0., -10.), (0., 10.)], // Flat line with 180 |
| [(10., 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s |
| [(39., -39.), (40., -40.), (40., -40.), (0., 0.)], // Flat diagonal with 180 |
| [(40., 40.), (0., 0.), (200., 200.), (0., 0.)], // Diag w/ an internal 180 |
| [(0., 0.), (1e-2, 0.), (-1e-2, 0.), (0., 0.)], // Circle |
| // Flat line with no turns: |
| [ |
| (400.75, 100.05), |
| (400.75, 100.05), |
| (100.05, 300.95), |
| (100.05, 300.95), |
| ], |
| [(0.5, 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s |
| [(10., 0.), (0., 0.), (10., 0.), (10., 0.)], // Flat line with a 180 |
| ]; |
| |
| // Flat conic with a cusp: (1,1) (2,1) (1,1), weight: 1 |
| let flat_quad = [ |
| // moveTo(1., 1.), |
| [(2., 1.), (1., 1.)], |
| ]; |
| // Flat conic with a cusp: (1,1) (100,1) (25,1), weight: 0.3 |
| let flat_conic_as_quads = [ |
| // moveTo(1., 1.), |
| [(2.232486, 1.000000), (3.471740, 1.000000)], |
| [(4.710995, 1.000000), (5.949262, 1.000000)], |
| [(7.187530, 1.000000), (8.417061, 1.000000)], |
| [(9.646591, 1.000000), (10.859690, 1.000000)], |
| [(12.072789, 1.000000), (13.261865, 1.000000)], |
| [(14.450940, 1.000000), (15.608549, 1.000000)], |
| [(16.766161, 1.000000), (17.885059, 1.000000)], |
| [(19.003958, 1.000000), (20.077141, 1.000000)], |
| [(21.150328, 1.000000), (22.171083, 1.000000)], |
| [(23.191839, 1.000000), (24.153776, 1.000000)], |
| [(25.115715, 1.000000), (26.012812, 1.000000)], |
| [(26.909912, 1.000000), (27.736557, 1.000000)], |
| [(28.563202, 1.000000), (29.314220, 1.000000)], |
| [(30.065239, 1.000000), (30.735928, 1.000000)], |
| [(31.406620, 1.000000), (31.992788, 1.000000)], |
| [(32.578957, 1.000000), (33.076927, 1.000000)], |
| [(33.574905, 1.000000), (33.981567, 1.000000)], |
| [(34.388233, 1.000000), (34.701038, 1.000000)], |
| [(35.013851, 1.000000), (35.230850, 1.000000)], |
| [(35.447845, 1.000000), (35.567669, 1.000000)], |
| [(35.687500, 1.000000), (35.709404, 1.000000)], |
| [(35.731312, 1.000000), (35.655155, 1.000000)], |
| [(35.579006, 1.000000), (35.405273, 1.000000)], |
| [(35.231541, 1.000000), (34.961311, 1.000000)], |
| [(34.691086, 1.000000), (34.326057, 1.000000)], |
| [(33.961029, 1.000000), (33.503479, 1.000000)], |
| [(33.045937, 1.000000), (32.498734, 1.000000)], |
| [(31.951530, 1.000000), (31.318098, 1.000000)], |
| [(30.684669, 1.000000), (29.968971, 1.000000)], |
| [(29.253277, 1.000000), (28.459791, 1.000000)], |
| [(27.666309, 1.000000), (26.800005, 1.000000)], |
| [(25.933704, 1.000000), (25.000000, 1.000000)], |
| ]; |
| // Flat conic with a cusp: (1,1) (100,1) (25,1), weight: 1.5 |
| let bigger_flat_conic_as_quads = [ |
| // moveTo(1., 1.), |
| [(8.979845, 1.000000), (15.795975, 1.000000)], |
| [(22.612104, 1.000000), (28.363287, 1.000000)], |
| [(34.114471, 1.000000), (38.884045, 1.000000)], |
| [(43.653618, 1.000000), (47.510696, 1.000000)], |
| [(51.367767, 1.000000), (54.368233, 1.000000)], |
| [(57.368698, 1.000000), (59.556030, 1.000000)], |
| [(61.743366, 1.000000), (63.149269, 1.000000)], |
| [(64.555168, 1.000000), (65.200005, 1.000000)], |
| [(65.844841, 1.000000), (65.737961, 1.000000)], |
| [(65.631073, 1.000000), (64.770912, 1.000000)], |
| [(63.910763, 1.000000), (62.284878, 1.000000)], |
| [(60.658997, 1.000000), (58.243816, 1.000000)], |
| [(55.828640, 1.000000), (52.589172, 1.000000)], |
| [(49.349705, 1.000000), (45.239006, 1.000000)], |
| [(41.128315, 1.000000), (36.086826, 1.000000)], |
| [(31.045338, 1.000000), (25.000000, 1.000000)], |
| ]; |
| |
| let mut idx = 0; |
| let mut color_idx = 0; |
| for (i, cubic) in tricky_cubics.into_iter().enumerate() { |
| idx += 1; |
| let x = (i % NUM_COLS) as f64 * CELL_SIZE; |
| let y = (i / NUM_COLS) as f64 * CELL_SIZE; |
| let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE); |
| let bounds = stroke_bounds(&cubic); |
| let (t, s) = map_rect_to_rect(&bounds, &cell); |
| scene.stroke( |
| &Stroke::new(STROKE_WIDTH / s) |
| .with_caps(Cap::Butt) |
| .with_join(Join::Miter), |
| t, |
| colors[color_idx], |
| None, |
| &[ |
| MoveTo(cubic[0].into()), |
| CurveTo(cubic[1].into(), cubic[2].into(), cubic[3].into()), |
| ], |
| ); |
| color_idx = (color_idx + 1) % colors.len(); |
| } |
| |
| let flat_curves = [ |
| flat_quad.as_slice(), |
| flat_conic_as_quads.as_slice(), |
| bigger_flat_conic_as_quads.as_slice(), |
| ]; |
| for quads in flat_curves.iter() { |
| let mut path = BezPath::new(); |
| path.push(MoveTo((1., 1.).into())); |
| for quad in quads.iter() { |
| path.push(QuadTo(quad[0].into(), quad[1].into())); |
| } |
| let x = (idx % NUM_COLS) as f64 * CELL_SIZE; |
| let y = (idx / NUM_COLS) as f64 * CELL_SIZE; |
| let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE); |
| let bounds = path.bounding_box().inflate(STROKE_WIDTH, STROKE_WIDTH); |
| let (t, s) = map_rect_to_rect(&bounds, &cell); |
| scene.stroke( |
| &Stroke::new(STROKE_WIDTH / s) |
| .with_caps(Cap::Butt) |
| .with_join(Join::Miter), |
| t, |
| colors[color_idx], |
| None, |
| &path, |
| ); |
| color_idx = (color_idx + 1) % colors.len(); |
| idx += 1; |
| } |
| |
| let curve_count = tricky_cubics.len() + flat_curves.len(); |
| params.resolution = Some(Vec2::new( |
| CELL_SIZE * NUM_COLS as f64, |
| CELL_SIZE * (1 + curve_count / NUM_COLS) as f64, |
| )); |
| } |
| |
| fn fill_types(scene: &mut Scene, params: &mut SceneParams) { |
| use PathEl::*; |
| let rect = Rect::from_origin_size(Point::new(0., 0.), (500., 500.)); |
| let star = [ |
| MoveTo((250., 0.).into()), |
| LineTo((105., 450.).into()), |
| LineTo((490., 175.).into()), |
| LineTo((10., 175.).into()), |
| LineTo((395., 450.).into()), |
| ClosePath, |
| ]; |
| let arcs = [ |
| MoveTo((0., 480.).into()), |
| CurveTo((500., 480.).into(), (500., -10.).into(), (0., -10.).into()), |
| ClosePath, |
| MoveTo((500., -10.).into()), |
| CurveTo((0., -10.).into(), (0., 480.).into(), (500., 480.).into()), |
| ClosePath, |
| ]; |
| let scale = Affine::scale(0.6); |
| let t = Affine::translate((10., 25.)); |
| let rules = [ |
| (Fill::NonZero, "Non-Zero", star.as_slice()), |
| (Fill::EvenOdd, "Even-Odd", &star), |
| (Fill::NonZero, "Non-Zero", &arcs), |
| (Fill::EvenOdd, "Even-Odd", &arcs), |
| ]; |
| for (i, rule) in rules.iter().enumerate() { |
| let t = Affine::translate(((i % 2) as f64 * 306., (i / 2) as f64 * 340.)) * t; |
| params.text.add(scene, None, 24., None, t, rule.1); |
| let t = Affine::translate((0., 5.)) * t * scale; |
| scene.fill( |
| Fill::NonZero, |
| t, |
| &Brush::Solid(Color::rgb8(128, 128, 128)), |
| None, |
| &rect, |
| ); |
| scene.fill( |
| rule.0, |
| Affine::translate((0., 10.)) * t, |
| Color::YELLOW, |
| None, |
| &rule.2, |
| ); |
| } |
| |
| // Draw blends |
| let t = Affine::translate((700., 0.)) * t; |
| for (i, rule) in rules.iter().enumerate() { |
| let t = Affine::translate(((i % 2) as f64 * 306., (i / 2) as f64 * 340.)) * t; |
| params.text.add(scene, None, 24., None, t, rule.1); |
| let t = Affine::translate((0., 5.)) * t * scale; |
| scene.fill( |
| Fill::NonZero, |
| t, |
| &Brush::Solid(Color::rgb8(128, 128, 128)), |
| None, |
| &rect, |
| ); |
| scene.fill( |
| rule.0, |
| Affine::translate((0., 10.)) * t, |
| Color::YELLOW, |
| None, |
| &rule.2, |
| ); |
| scene.fill( |
| rule.0, |
| Affine::translate((0., 10.)) * t * Affine::rotate(0.06), |
| Color::rgba(0., 1., 0.7, 0.6), |
| None, |
| &rule.2, |
| ); |
| scene.fill( |
| rule.0, |
| Affine::translate((0., 10.)) * t * Affine::rotate(-0.06), |
| Color::rgba(0.9, 0.7, 0.5, 0.6), |
| None, |
| &rule.2, |
| ); |
| } |
| } |
| |
| fn cardioid_and_friends(scene: &mut Scene, _: &mut SceneParams) { |
| render_cardioid(scene); |
| render_clip_test(scene); |
| render_alpha_test(scene); |
| //render_tiger(scene, false); |
| } |
| |
| fn longpathdash(cap: Cap) -> impl FnMut(&mut Scene, &mut SceneParams) { |
| use std::f64::consts::PI; |
| use PathEl::*; |
| move |scene, _| { |
| let mut path = BezPath::new(); |
| let mut x = 32; |
| while x < 256 { |
| let mut a: f64 = 0.0; |
| while a < PI * 2.0 { |
| let pts = [ |
| (256.0 + a.sin() * x as f64, 256.0 + a.cos() * x as f64), |
| ( |
| 256.0 + (a + PI / 3.0).sin() * (x + 64) as f64, |
| 256.0 + (a + PI / 3.0).cos() * (x + 64) as f64, |
| ), |
| ]; |
| path.push(MoveTo(pts[0].into())); |
| let mut i: f64 = 0.0; |
| while i < 1.0 { |
| path.push(LineTo( |
| ( |
| pts[0].0 * (1.0 - i) + pts[1].0 * i, |
| pts[0].1 * (1.0 - i) + pts[1].1 * i, |
| ) |
| .into(), |
| )); |
| i += 0.05; |
| } |
| a += PI * 0.01; |
| } |
| x += 16; |
| } |
| scene.stroke( |
| &Stroke::new(1.0) |
| .with_caps(cap) |
| .with_join(Join::Bevel) |
| .with_dashes(0.0, [1.0, 1.0]), |
| Affine::translate((50.0, 50.0)), |
| Color::YELLOW, |
| None, |
| &path, |
| ); |
| } |
| } |
| |
| fn animated_text(scene: &mut Scene, 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, |
| ]; |
| scene.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( |
| scene, |
| None, |
| text_size, |
| None, |
| Affine::translate((110.0, 600.0)), |
| s, |
| ); |
| params.text.add_run( |
| scene, |
| 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( |
| scene, |
| 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; |
| let center = Point::new(500.0, 500.0); |
| let mut p1 = center; |
| p1.x += 400.0 * th.cos(); |
| p1.y += 400.0 * th.sin(); |
| scene.stroke( |
| &Stroke::new(5.0), |
| Affine::IDENTITY, |
| &Brush::Solid(Color::rgb8(128, 0, 0)), |
| None, |
| &[PathEl::MoveTo(center), PathEl::LineTo(p1)], |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((150.0, 150.0)) * Affine::scale(0.2), |
| Color::RED, |
| None, |
| &rect, |
| ); |
| let alpha = params.time.sin() as f32 * 0.5 + 0.5; |
| scene.push_layer(Mix::Normal, alpha, Affine::IDENTITY, &rect); |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((100.0, 100.0)) * Affine::scale(0.2), |
| Color::BLUE, |
| None, |
| &rect, |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((200.0, 200.0)) * Affine::scale(0.2), |
| Color::GREEN, |
| None, |
| &rect, |
| ); |
| scene.pop_layer(); |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((400.0, 100.0)), |
| Color::PURPLE, |
| None, |
| &star, |
| ); |
| scene.fill( |
| Fill::EvenOdd, |
| Affine::translate((500.0, 100.0)), |
| Color::PURPLE, |
| None, |
| &star, |
| ); |
| scene.draw_image( |
| &piet_logo, |
| Affine::translate((800.0, 50.0)) * Affine::rotate(20f64.to_radians()), |
| ); |
| } |
| |
| fn brush_transform(scene: &mut Scene, 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, |
| ]); |
| scene.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)), |
| ); |
| scene.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)), |
| ); |
| scene.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 gradient_extend(scene: &mut Scene, params: &mut SceneParams) { |
| enum Kind { |
| Linear, |
| Radial, |
| Sweep, |
| } |
| fn square(scene: &mut Scene, kind: Kind, transform: Affine, extend: Extend) { |
| let colors = [Color::RED, Color::rgb8(0, 255, 0), Color::BLUE]; |
| let width = 300f64; |
| let height = 300f64; |
| let gradient: Brush = match kind { |
| Kind::Linear => { |
| Gradient::new_linear((width * 0.35, height * 0.5), (width * 0.65, height * 0.5)) |
| .with_stops(colors) |
| .with_extend(extend) |
| .into() |
| } |
| Kind::Radial => { |
| let center = (width * 0.5, height * 0.5); |
| let radius = (width * 0.25) as f32; |
| Gradient::new_two_point_radial(center, radius * 0.25, center, radius) |
| .with_stops(colors) |
| .with_extend(extend) |
| .into() |
| } |
| Kind::Sweep => Gradient::new_sweep( |
| (width * 0.5, height * 0.5), |
| 30f32.to_radians(), |
| 150f32.to_radians(), |
| ) |
| .with_stops(colors) |
| .with_extend(extend) |
| .into(), |
| }; |
| scene.fill( |
| Fill::NonZero, |
| transform, |
| &gradient, |
| None, |
| &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 = Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0)); |
| square(scene, kind, transform, *extend); |
| } |
| } |
| for (i, label) in ["Pad", "Repeat", "Reflect"].iter().enumerate() { |
| let x = i as f64 * 350.0 + 50.0; |
| params.text.add( |
| scene, |
| None, |
| 32.0, |
| Some(&Color::WHITE.into()), |
| Affine::translate((x, 70.0)), |
| label, |
| ); |
| } |
| params.resolution = Some((1200.0, 1200.0).into()); |
| } |
| |
| #[allow(clippy::too_many_arguments)] |
| fn two_point_radial(scene: &mut Scene, _params: &mut SceneParams) { |
| fn make( |
| scene: &mut Scene, |
| x0: f64, |
| y0: f64, |
| r0: f32, |
| x1: f64, |
| y1: f64, |
| r1: f32, |
| transform: Affine, |
| extend: Extend, |
| ) { |
| let colors = [Color::RED, Color::YELLOW, Color::rgb8(6, 85, 186)]; |
| let width = 400f64; |
| let height = 200f64; |
| let rect = Rect::new(0.0, 0.0, width, height); |
| scene.fill(Fill::NonZero, transform, Color::WHITE, None, &rect); |
| scene.fill( |
| Fill::NonZero, |
| transform, |
| &Gradient::new_two_point_radial((x0, y0), r0, (x1, y1), r1) |
| .with_stops(colors) |
| .with_extend(extend), |
| None, |
| &Rect::new(0.0, 0.0, width, height), |
| ); |
| let r0 = r0 as f64 - 1.0; |
| let r1 = r1 as f64 - 1.0; |
| let stroke_width = 1.0; |
| scene.stroke( |
| &Stroke::new(stroke_width), |
| transform, |
| Color::BLACK, |
| None, |
| &Ellipse::new((x0, y0), (r0, r0), 0.0), |
| ); |
| scene.stroke( |
| &Stroke::new(stroke_width), |
| transform, |
| Color::BLACK, |
| None, |
| &Ellipse::new((x1, y1), (r1, r1), 0.0), |
| ); |
| } |
| |
| // These demonstrate radial gradient patterns similar to the examples shown |
| // at <https://learn.microsoft.com/en-us/typography/opentype/spec/colr#radial-gradients> |
| |
| 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( |
| scene, |
| x0, |
| y, |
| r0, |
| x1, |
| y, |
| r1, |
| Affine::translate((i as f64 * 420.0 + 20.0, 20.0)), |
| *mode, |
| ); |
| } |
| |
| 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( |
| scene, |
| x1, |
| y, |
| r1, |
| x0, |
| y, |
| r0, |
| Affine::translate((i as f64 * 420.0 + 20.0, 240.0)), |
| *mode, |
| ); |
| } |
| |
| 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( |
| scene, |
| x0, |
| y, |
| r0, |
| x1, |
| y, |
| r1, |
| Affine::translate((i as f64 * 420.0 + 20.0, 460.0)), |
| *mode, |
| ); |
| } |
| |
| 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( |
| scene, |
| x0, |
| y0, |
| r0, |
| x1, |
| y1, |
| r1, |
| Affine::translate((i as f64 * 420.0 + 20.0, 680.0)), |
| *mode, |
| ); |
| } |
| |
| 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 = 96.0; |
| // Shift p0 so the outer edges of both circles touch |
| let p0 = Point::new(x1, y1) |
| + ((Point::new(x0, y0) - Point::new(x1, y1)).normalize() * (r1 - r0)); |
| make( |
| scene, |
| p0.x, |
| p0.y, |
| r0 as f32, |
| x1, |
| y1, |
| r1 as f32, |
| Affine::translate((i as f64 * 420.0 + 20.0, 900.0)), |
| *mode, |
| ); |
| } |
| } |
| |
| fn blend_grid(scene: &mut Scene, _: &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()); |
| scene.append(&square, Some(transform)); |
| } |
| } |
| |
| // Support functions |
| |
| fn render_cardioid(scene: &mut Scene) { |
| 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)); |
| } |
| scene.stroke( |
| &Stroke::new(2.0), |
| Affine::IDENTITY, |
| Color::rgb8(0, 0, 255), |
| None, |
| &path, |
| ); |
| } |
| |
| fn render_clip_test(scene: &mut Scene) { |
| 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, |
| ]; |
| scene.push_layer(Mix::Clip, 1.0, Affine::IDENTITY, &path); |
| } |
| let rect = Rect::new(X0, Y0, X1, Y1); |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| &Brush::Solid(Color::rgb8(0, 255, 0)), |
| None, |
| &rect, |
| ); |
| for _ in 0..N { |
| scene.pop_layer(); |
| } |
| } |
| |
| fn render_alpha_test(scene: &mut Scene) { |
| // Alpha compositing tests. |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgb8(255, 0, 0), |
| None, |
| &make_diamond(1024.0, 100.0), |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgba8(0, 255, 0, 0x80), |
| None, |
| &make_diamond(1024.0, 125.0), |
| ); |
| scene.push_layer( |
| Mix::Clip, |
| 1.0, |
| Affine::IDENTITY, |
| &make_diamond(1024.0, 150.0), |
| ); |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgba8(0, 0, 255, 0x80), |
| None, |
| &make_diamond(1024.0, 175.0), |
| ); |
| scene.pop_layer(); |
| } |
| |
| fn render_blend_square(scene: &mut Scene, 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]); |
| scene.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; |
| color2.a = 0; |
| let radial = Gradient::new_radial((*x, *y), 100.0).with_stops([*c, color2]); |
| scene.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), |
| ]; |
| scene.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]); |
| scene.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.)); |
| scene.fill( |
| Fill::NonZero, |
| a, |
| &linear, |
| None, |
| &Ellipse::new((100., 100.), (90., 90.), 0.), |
| ); |
| scene.pop_layer(); |
| } |
| scene.pop_layer(); |
| } |
| |
| fn blend_square(blend: BlendMode) -> Scene { |
| let mut fragment = Scene::default(); |
| render_blend_square(&mut fragment, blend, Affine::IDENTITY); |
| fragment |
| } |
| |
| fn conflation_artifacts(scene: &mut Scene, _: &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 |
| scene.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; |
| scene.fill( |
| Fill::EvenOdd, |
| Affine::translate((x, y)) * scale, |
| bg_color, |
| None, |
| &Rect::new(0.0, 0.0, N, N), |
| ); |
| scene.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; |
| scene.fill( |
| Fill::EvenOdd, |
| Affine::translate((x, y)) * scale, |
| bg_color, |
| None, |
| &Rect::new(0.0, 0.0, N, N), |
| ); |
| scene.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(scene: &mut Scene, _: &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. |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((20.5, 20.5)) * Affine::scale(80.0), |
| Color::rgba8(0x70, 0x80, 0x80, 0xff), |
| None, |
| &path, |
| ); |
| } |
| |
| fn robust_paths(scene: &mut Scene, _: &mut SceneParams) { |
| let mut path = BezPath::new(); |
| path.move_to((16.0, 16.0)); |
| path.line_to((32.0, 16.0)); |
| path.line_to((32.0, 32.0)); |
| path.line_to((16.0, 32.0)); |
| path.close_path(); |
| path.move_to((48.0, 18.0)); |
| path.line_to((64.0, 23.0)); |
| path.line_to((64.0, 33.0)); |
| path.line_to((48.0, 38.0)); |
| path.close_path(); |
| path.move_to((80.0, 18.0)); |
| path.line_to((82.0, 16.0)); |
| path.line_to((94.0, 16.0)); |
| path.line_to((96.0, 18.0)); |
| path.line_to((96.0, 30.0)); |
| path.line_to((94.0, 32.0)); |
| path.line_to((82.0, 32.0)); |
| path.line_to((80.0, 30.0)); |
| path.close_path(); |
| path.move_to((112.0, 16.0)); |
| path.line_to((128.0, 16.0)); |
| path.line_to((128.0, 32.0)); |
| path.close_path(); |
| path.move_to((144.0, 16.0)); |
| path.line_to((160.0, 32.0)); |
| path.line_to((144.0, 32.0)); |
| path.close_path(); |
| path.move_to((168.0, 8.0)); |
| path.line_to((184.0, 8.0)); |
| path.line_to((184.0, 24.0)); |
| path.close_path(); |
| path.move_to((200.0, 8.0)); |
| path.line_to((216.0, 24.0)); |
| path.line_to((200.0, 24.0)); |
| path.close_path(); |
| path.move_to((241.0, 17.5)); |
| path.line_to((255.0, 17.5)); |
| path.line_to((255.0, 19.5)); |
| path.line_to((241.0, 19.5)); |
| path.close_path(); |
| path.move_to((241.0, 22.5)); |
| path.line_to((256.0, 22.5)); |
| path.line_to((256.0, 24.5)); |
| path.line_to((241.0, 24.5)); |
| path.close_path(); |
| scene.fill(Fill::NonZero, Affine::IDENTITY, Color::YELLOW, None, &path); |
| scene.fill( |
| Fill::EvenOdd, |
| Affine::translate((300.0, 0.0)), |
| Color::LIME, |
| None, |
| &path, |
| ); |
| |
| path.move_to((8.0, 4.0)); |
| path.line_to((8.0, 40.0)); |
| path.line_to((260.0, 40.0)); |
| path.line_to((260.0, 4.0)); |
| path.close_path(); |
| scene.fill( |
| Fill::NonZero, |
| Affine::translate((0.0, 100.0)), |
| Color::YELLOW, |
| None, |
| &path, |
| ); |
| scene.fill( |
| Fill::EvenOdd, |
| Affine::translate((300.0, 100.0)), |
| Color::LIME, |
| None, |
| &path, |
| ); |
| } |
| |
| fn base_color_test(scene: &mut Scene, 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. |
| scene.fill( |
| Fill::NonZero, |
| Affine::IDENTITY, |
| Color::rgba8(255, 255, 255, 128), |
| None, |
| &Rect::new(50.0, 50.0, 500.0, 500.0), |
| ); |
| } |
| |
| fn clip_test(scene: &mut Scene, params: &mut SceneParams) { |
| let clip = { |
| const X0: f64 = 50.0; |
| const Y0: f64 = 0.0; |
| const X1: f64 = 200.0; |
| const Y1: f64 = 500.0; |
| [ |
| PathEl::MoveTo((X0, Y0).into()), |
| PathEl::LineTo((X1, Y0).into()), |
| PathEl::LineTo((X1, Y0 + (Y1 - Y0)).into()), |
| PathEl::LineTo((X1 + (X0 - X1), Y1).into()), |
| PathEl::LineTo((X0, Y1).into()), |
| PathEl::ClosePath, |
| ] |
| }; |
| scene.push_layer(Mix::Clip, 1.0, Affine::IDENTITY, &clip); |
| { |
| let text_size = 60.0 + 40.0 * (params.time as f32).sin(); |
| let s = "Some clipped text!"; |
| params.text.add( |
| scene, |
| None, |
| text_size, |
| None, |
| Affine::translate((110.0, 100.0)), |
| s, |
| ); |
| } |
| scene.pop_layer(); |
| |
| let large_background_rect = kurbo::Rect::new(-1000.0, -1000.0, 2000.0, 2000.0); |
| let inside_clip_rect = kurbo::Rect::new(11.0, 13.399999999999999, 59.0, 56.6); |
| let outside_clip_rect = kurbo::Rect::new( |
| 12.599999999999998, |
| 12.599999999999998, |
| 57.400000000000006, |
| 57.400000000000006, |
| ); |
| let clip_rect = kurbo::Rect::new(0.0, 0.0, 74.4, 339.20000000000005); |
| let scale = 2.0; |
| |
| scene.push_layer( |
| BlendMode { |
| mix: peniko::Mix::Normal, |
| compose: peniko::Compose::SrcOver, |
| }, |
| 1.0, |
| Affine::new([scale, 0.0, 0.0, scale, 27.07470703125, 176.40660533027858]), |
| &clip_rect, |
| ); |
| |
| scene.fill( |
| peniko::Fill::NonZero, |
| kurbo::Affine::new([scale, 0.0, 0.0, scale, 27.07470703125, 176.40660533027858]), |
| peniko::Color::rgb8(0, 0, 255), |
| None, |
| &large_background_rect, |
| ); |
| scene.fill( |
| peniko::Fill::NonZero, |
| kurbo::Affine::new([ |
| scale, |
| 0.0, |
| 0.0, |
| scale, |
| 29.027636718750003, |
| 182.9755506427786, |
| ]), |
| peniko::Color::rgb8(0, 255, 0), |
| None, |
| &inside_clip_rect, |
| ); |
| scene.fill( |
| peniko::Fill::NonZero, |
| kurbo::Affine::new([ |
| scale, |
| 0.0, |
| 0.0, |
| scale, |
| 29.027636718750003, |
| scale * 559.3583631427786, |
| ]), |
| peniko::Color::rgb8(255, 0, 0), |
| None, |
| &outside_clip_rect, |
| ); |
| |
| scene.pop_layer(); |
| } |
| |
| 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, |
| ] |
| } |
| |
| fn many_draw_objects(scene: &mut Scene, params: &mut SceneParams) { |
| const N_WIDE: usize = 300; |
| const N_HIGH: usize = 300; |
| const SCENE_WIDTH: f64 = 2000.0; |
| const SCENE_HEIGHT: f64 = 1500.0; |
| params.resolution = Some((SCENE_WIDTH, SCENE_HEIGHT).into()); |
| for j in 0..N_HIGH { |
| let y = (j as f64 + 0.5) * (SCENE_HEIGHT / N_HIGH as f64); |
| for i in 0..N_WIDE { |
| let x = (i as f64 + 0.5) * (SCENE_WIDTH / N_WIDE as f64); |
| let c = Circle::new((x, y), 3.0); |
| scene.fill(Fill::NonZero, Affine::IDENTITY, Color::YELLOW, None, &c); |
| } |
| } |
| } |
| |
| fn splash_screen(scene: &mut Scene, params: &mut SceneParams) { |
| let strings = [ |
| "Vello test", |
| " Arrow keys: switch scenes", |
| " Space: reset transform", |
| " S: toggle stats", |
| " V: toggle vsync", |
| " M: cycle AA method", |
| " Q, E: rotate", |
| ]; |
| // Tweak to make it fit with tiger |
| let a = Affine::scale(0.11) * Affine::translate((-90.0, -50.0)); |
| for (i, s) in strings.iter().enumerate() { |
| let text_size = if i == 0 { 60.0 } else { 40.0 }; |
| params.text.add( |
| scene, |
| None, |
| text_size, |
| None, |
| a * Affine::translate((100.0, 100.0 + 60.0 * i as f64)), |
| s, |
| ); |
| } |
| } |
| |
| fn splash_with_tiger() -> impl FnMut(&mut Scene, &mut SceneParams) { |
| let contents = include_str!(concat!( |
| env!("CARGO_MANIFEST_DIR"), |
| "/../assets/Ghostscript_Tiger.svg" |
| )); |
| let mut tiger = crate::svg::svg_function_of("Ghostscript Tiger".to_string(), move || contents); |
| move |scene, params| { |
| tiger(scene, params); |
| splash_screen(scene, params); |
| } |
| } |