diff --git a/Cargo.lock b/Cargo.lock index d82ba57..0bc75b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,9 +503,9 @@ checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-intrusive" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6bdbb8c5a42b2bb5ee8dd9dc2c7d73ce3e15d26dfe100fb347ffa3f58c672b" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", @@ -710,9 +710,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.8.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +checksum = "e119590a03caff1f7a582e8ee8c2164ddcc975791701188132fd1d1b518d3871" dependencies = [ "arrayvec 0.7.2", ] @@ -1068,6 +1068,15 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "peniko" +version = "0.1.0" +source = "git+https://github.com/linebender/peniko#b83821720aa51a3942be5d20c71525a1ae61ac0a" +dependencies = [ + "kurbo 0.9.0", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1093,7 +1102,6 @@ version = "0.1.0" dependencies = [ "bytemuck", "clap 3.2.23", - "kurbo 0.8.3", "ndk 0.3.0", "ndk-glue 0.3.0", "ndk-sys 0.2.2", @@ -1162,8 +1170,8 @@ name = "piet-scene" version = "0.1.0" dependencies = [ "bytemuck", - "kurbo 0.8.3", "moscato", + "peniko", "smallvec", ] @@ -1174,7 +1182,6 @@ dependencies = [ "bytemuck", "env_logger", "futures-intrusive", - "kurbo 0.8.3", "parking_lot", "piet-scene", "png", diff --git a/pgpu-render/src/lib.rs b/pgpu-render/src/lib.rs index 43ff438..b699840 100644 --- a/pgpu-render/src/lib.rs +++ b/pgpu-render/src/lib.rs @@ -26,7 +26,8 @@ mod render; -use piet_scene::{Brush, Color, Fill, PathElement}; +use piet_scene::kurbo::{Affine, PathEl, Point}; +use piet_scene::{Brush, Color, Fill}; use render::*; use std::ffi::c_void; use std::mem::transmute; @@ -145,7 +146,7 @@ pub struct PgpuPathElement { pub points: [PgpuPoint; 3], } -#[derive(Clone)] +#[derive(Copy, Clone)] #[repr(C)] pub struct PgpuPathIter { pub context: *mut c_void, @@ -197,16 +198,16 @@ pub struct PgpuTransform { pub dy: f32, } -impl From for piet_scene::Affine { +impl From for Affine { fn from(xform: PgpuTransform) -> Self { - Self { - xx: xform.xx, - yx: xform.yx, - xy: xform.xy, - yy: xform.yy, - dx: xform.dx, - dy: xform.dy, - } + Affine::new([ + xform.xx as f64, + xform.yx as f64, + xform.xy as f64, + xform.yy as f64, + xform.dx as f64, + xform.dy as f64, + ]) } } @@ -239,27 +240,26 @@ pub unsafe extern "C" fn pgpu_scene_builder_add_glyph( } impl Iterator for PgpuPathIter { - type Item = PathElement; + type Item = PathEl; fn next(&mut self) -> Option { let mut el = PgpuPathElement { verb: PgpuPathVerb::MoveTo, points: [PgpuPoint::default(); 3], }; + fn conv_pt(pt: PgpuPoint) -> Point { + Point::new(pt.x as f64, pt.y as f64) + } if (self.next_element)(self.context, &mut el as _) { let p = &el.points; Some(match el.verb { - PgpuPathVerb::MoveTo => PathElement::MoveTo((p[0].x, p[0].y).into()), - PgpuPathVerb::LineTo => PathElement::LineTo((p[0].x, p[0].y).into()), - PgpuPathVerb::QuadTo => { - PathElement::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into()) + PgpuPathVerb::MoveTo => PathEl::MoveTo(conv_pt(p[0])), + PgpuPathVerb::LineTo => PathEl::LineTo(conv_pt(p[0])), + PgpuPathVerb::QuadTo => PathEl::QuadTo(conv_pt(p[0]), conv_pt(p[1])), + PgpuPathVerb::CurveTo => { + PathEl::CurveTo(conv_pt(p[0]), conv_pt(p[1]), conv_pt(p[2])) } - PgpuPathVerb::CurveTo => PathElement::CurveTo( - (p[0].x, p[0].y).into(), - (p[1].x, p[1].y).into(), - (p[2].x, p[2].y).into(), - ), - PgpuPathVerb::Close => PathElement::Close, + PgpuPathVerb::Close => PathEl::ClosePath, }) } else { None @@ -308,12 +308,13 @@ pub unsafe extern "C" fn pgpu_scene_builder_fill_path( } else { Some((*brush_transform).into()) }; + let path_els = (*path).collect::>(); (*builder).builder.fill( fill, (*builder).transform, &brush, brush_transform, - (*path).clone(), + &&path_els[..], ); } @@ -445,13 +446,14 @@ pub unsafe extern "C" fn pgpu_glyph_bbox( glyph: *const PgpuGlyph, transform: &[f32; 6], ) -> PgpuRect { - let transform = piet_scene::Affine::new(transform); + let transform: PgpuTransform = std::mem::transmute(*transform); + let transform = transform.into(); let rect = (*glyph).bbox(Some(transform)); PgpuRect { - x0: rect.min.x, - y0: rect.min.y, - x1: rect.max.x, - y1: rect.max.y, + x0: rect.min_x() as f32, + y0: rect.min_y() as f32, + x1: rect.max_x() as f32, + y1: rect.max_y() as f32, } } diff --git a/pgpu-render/src/render.rs b/pgpu-render/src/render.rs index 6c59997..4bedf5c 100644 --- a/pgpu-render/src/render.rs +++ b/pgpu-render/src/render.rs @@ -18,7 +18,8 @@ use piet_gpu::{PixelFormat, RenderConfig}; use piet_gpu_hal::{QueryPool, Session}; use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; use piet_scene::glyph::{GlyphContext, GlyphProvider}; -use piet_scene::{Affine, Rect, Scene, SceneFragment}; +use piet_scene::kurbo::{Affine, Point, Rect}; +use piet_scene::{Scene, SceneFragment}; /// State and resources for rendering a scene. pub struct PgpuRenderer { @@ -141,7 +142,7 @@ pub struct PgpuSceneBuilder<'a> { } impl<'a> PgpuSceneBuilder<'a> { - pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &piet_scene::Affine) { + pub fn add_glyph(&mut self, glyph: &PgpuGlyph, transform: &Affine) { self.builder.append(&glyph.fragment, Some(*transform)); } @@ -214,15 +215,25 @@ pub struct PgpuGlyph { impl PgpuGlyph { pub fn bbox(&self, transform: Option) -> Rect { + let points = self.fragment.points(); + if points.is_empty() { + return Rect::default(); + } + let mut points = points + .iter() + .map(|pt| Point::new(pt[0] as f64, pt[1] as f64)); if let Some(transform) = &transform { - Rect::from_points( - self.fragment - .points() - .iter() - .map(|p| p.transform(transform)), - ) + let mut rect = Rect::from_center_size(points.next().unwrap(), (0.0, 0.0)); + for point in points { + rect = rect.union_pt(*transform * point); + } + rect } else { - Rect::from_points(self.fragment.points()) + let mut rect = Rect::from_center_size(points.next().unwrap(), (0.0, 0.0)); + for point in points { + rect = rect.union_pt(point); + } + rect } } } diff --git a/piet-gpu/Cargo.toml b/piet-gpu/Cargo.toml index 7b09e59..8e5b82e 100644 --- a/piet-gpu/Cargo.toml +++ b/piet-gpu/Cargo.toml @@ -28,7 +28,6 @@ path = "../piet-gpu-types" [dependencies.piet-scene] path = "../piet-scene" -features = ["kurbo"] [dependencies] png = "0.17.6" @@ -38,7 +37,6 @@ winit = {version = "0.27.3", default-features = false, features = ["x11", "wayl raw-window-handle = "0.5" clap = "3.2.22" bytemuck = { version = "1.7.2", features = ["derive"] } -kurbo = "0.8.3" [target.'cfg(target_os = "android")'.dependencies] ndk = "0.3" diff --git a/piet-gpu/bin/winit.rs b/piet-gpu/bin/winit.rs index 1a98aa8..4b3e990 100644 --- a/piet-gpu/bin/winit.rs +++ b/piet-gpu/bin/winit.rs @@ -190,7 +190,7 @@ fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str) None, 40.0, None, - piet_scene::Affine::translate(110.0, 50.0), + piet_scene::kurbo::Affine::translate((110.0, 50.0)), info, ); } diff --git a/piet-gpu/src/pico_svg.rs b/piet-gpu/src/pico_svg.rs index eebe3ec..673b195 100644 --- a/piet-gpu/src/pico_svg.rs +++ b/piet-gpu/src/pico_svg.rs @@ -4,8 +4,7 @@ use std::str::FromStr; use roxmltree::{Document, Node}; -use kurbo::{Affine, BezPath}; - +use piet_scene::kurbo::{Affine, BezPath}; use piet_scene::Color; pub struct PicoSvg { diff --git a/piet-gpu/src/ramp.rs b/piet-gpu/src/ramp.rs index 1c834f7..86af504 100644 --- a/piet-gpu/src/ramp.rs +++ b/piet-gpu/src/ramp.rs @@ -1,4 +1,4 @@ -use piet_scene::{Color, GradientStop, GradientStops}; +use piet_scene::{Color, ColorStop, ColorStops}; use std::collections::HashMap; @@ -8,7 +8,7 @@ const RETAINED_COUNT: usize = 64; #[derive(Default)] pub struct RampCache { epoch: u64, - map: HashMap, + map: HashMap, data: Vec, } @@ -22,7 +22,7 @@ impl RampCache { } } - pub fn add(&mut self, stops: &[GradientStop]) -> u32 { + pub fn add(&mut self, stops: &[ColorStop]) -> u32 { if let Some(entry) = self.map.get_mut(stops) { entry.1 = self.epoch; entry.0 @@ -64,7 +64,7 @@ impl RampCache { } } -fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator + 'a { +fn make_ramp<'a>(stops: &'a [ColorStop]) -> impl Iterator + 'a { let mut last_u = 0.0; let mut last_c = ColorF64::from_color(stops[0].color); let mut this_u = last_u; diff --git a/piet-gpu/src/samples.rs b/piet-gpu/src/samples.rs index 70215b9..fd6ea6b 100644 --- a/piet-gpu/src/samples.rs +++ b/piet-gpu/src/samples.rs @@ -1,47 +1,47 @@ use crate::PicoSvg; -use kurbo::BezPath; +use piet_scene::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; use piet_scene::*; use crate::SimpleText; pub fn render_funky_paths(sb: &mut SceneBuilder) { - use PathElement::*; + use PathEl::*; let missing_movetos = [ LineTo((100.0, 100.0).into()), LineTo((100.0, 200.0).into()), - Close, + 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: [PathElement; 0] = []; + let empty: [PathEl; 0] = []; sb.fill( Fill::NonZero, - Affine::translate(100.0, 100.0), - &Color::rgb8(0, 0, 255).into(), + Affine::translate((100.0, 100.0)), + Color::rgb8(0, 0, 255), None, - missing_movetos, + &missing_movetos, ); sb.fill( Fill::NonZero, Affine::IDENTITY, - &Color::rgb8(0, 0, 255).into(), + Color::rgb8(0, 0, 255), None, - empty, + &empty, ); sb.fill( Fill::NonZero, Affine::IDENTITY, - &Color::rgb8(0, 0, 255).into(), + Color::rgb8(0, 0, 255), None, - only_movetos, + &only_movetos, ); sb.stroke( - &simple_stroke(8.0), - Affine::translate(100.0, 100.0), - &Color::rgb8(0, 255, 255).into(), + &Stroke::new(8.0), + Affine::translate((100.0, 100.0)), + Color::rgb8(0, 255, 255), None, - missing_movetos, + &missing_movetos, ); } @@ -58,18 +58,18 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { sb.fill( Fill::NonZero, Affine::IDENTITY, - &fill.color.into(), + fill.color, None, - convert_bez_path(&fill.path), + &fill.path, ); } Item::Stroke(stroke) => { sb.stroke( - &simple_stroke(stroke.width as f32), + &Stroke::new(stroke.width as f32), Affine::IDENTITY, - &stroke.color.into(), + stroke.color, None, - convert_bez_path(&stroke.path), + &stroke.path, ); } } @@ -101,26 +101,26 @@ pub fn render_scene(sb: &mut SceneBuilder) { #[allow(unused)] fn render_cardioid(sb: &mut SceneBuilder) { let n = 601; - let dth = std::f32::consts::PI * 2.0 / (n as f32); + 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 = vec![]; + let mut path = BezPath::new(); for i in 1..n { let mut p0 = center; - let a0 = i as f32 * dth; + 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 f32 * dth; + let a1 = ((i * 2) % n) as f64 * dth; p1.x += a1.cos() * r; p1.y += a1.sin() * r; - path.push(PathElement::MoveTo(p0)); - path.push(PathElement::LineTo(p1)); + path.push(PathEl::MoveTo(p0)); + path.push(PathEl::LineTo(p1)); } sb.stroke( - &simple_stroke(2.0), + &Stroke::new(2.0), Affine::IDENTITY, - &Brush::Solid(Color::rgb8(0, 0, 0)), + Color::rgb8(0, 0, 0), None, &path, ); @@ -129,35 +129,32 @@ fn render_cardioid(sb: &mut SceneBuilder) { #[allow(unused)] fn render_clip_test(sb: &mut SceneBuilder) { const N: usize = 16; - const X0: f32 = 50.0; - const Y0: f32 = 450.0; + 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: f32 = 550.0; - const Y1: f32 = 950.0; - let step = 1.0 / ((N + 1) as f32); + 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 f32) * step; - let path = &[ - PathElement::MoveTo((X0, Y0).into()), - PathElement::LineTo((X1, Y0).into()), - PathElement::LineTo((X1, Y0 + t * (Y1 - Y0)).into()), - PathElement::LineTo((X1 + t * (X0 - X1), Y1).into()), - PathElement::LineTo((X0, Y1).into()), - PathElement::Close, + 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.into(), Affine::IDENTITY, path); + sb.push_layer(Mix::Clip, Affine::IDENTITY, &path); } - let rect = Rect { - min: Point::new(X0, Y0), - max: Point::new(X1, Y1), - }; + let rect = Rect::new(X0, Y0, X1, Y1); sb.fill( Fill::NonZero, Affine::IDENTITY, &Brush::Solid(Color::rgb8(0, 0, 0)), None, - rect.elements(), + &rect, ); for _ in 0..N { sb.pop_layer(); @@ -170,28 +167,24 @@ fn render_alpha_test(sb: &mut SceneBuilder) { sb.fill( Fill::NonZero, Affine::IDENTITY, - &Color::rgb8(255, 0, 0).into(), + Color::rgb8(255, 0, 0), None, - make_diamond(1024.0, 100.0), + &make_diamond(1024.0, 100.0), ); sb.fill( Fill::NonZero, Affine::IDENTITY, - &Color::rgba8(0, 255, 0, 0x80).into(), + Color::rgba8(0, 255, 0, 0x80), None, - make_diamond(1024.0, 125.0), - ); - sb.push_layer( - Mix::Clip.into(), - Affine::IDENTITY, - make_diamond(1024.0, 150.0), + &make_diamond(1024.0, 125.0), ); + sb.push_layer(Mix::Clip, Affine::IDENTITY, &make_diamond(1024.0, 150.0)); sb.fill( Fill::NonZero, Affine::IDENTITY, - &Color::rgba8(0, 0, 255, 0x80).into(), + Color::rgba8(0, 0, 255, 0x80), None, - make_diamond(1024.0, 175.0), + &make_diamond(1024.0, 175.0), ); sb.pop_layer(); } @@ -219,7 +212,7 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { for (ix, &blend) in BLEND_MODES.iter().enumerate() { let i = ix % 4; let j = ix / 4; - let transform = Affine::translate(i as f32 * 225., j as f32 * 225.); + let transform = Affine::translate((i as f64 * 225., j as f64 * 225.)); let square = blend_square(blend.into()); sb.append(&square, Some(transform)); } @@ -228,25 +221,10 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { #[allow(unused)] 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 stops = &[ - GradientStop { - color: Color::rgb8(0, 0, 0), - offset: 0.0, - }, - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(200.0, 0.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, transform, &linear, None, rect.elements()); - const GRADIENTS: &[(f32, f32, Color)] = &[ + let rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.)); + let linear = LinearGradient::new((0.0, 0.0), (200.0, 0.0)).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)), @@ -254,62 +232,30 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin for (x, y, c) in GRADIENTS { let mut color2 = c.clone(); color2.a = 0; - let stops = &[ - GradientStop { - color: c.clone(), - offset: 0.0, - }, - GradientStop { - color: color2, - offset: 1.0, - }, - ][..]; - let rad = Brush::RadialGradient(RadialGradient { - center0: Point::new(*x, *y), - center1: Point::new(*x, *y), - radius0: 0.0, - radius1: 100.0, - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, transform, &rad, None, rect.elements()); + let radial = RadialGradient::new((*x, *y), 100.0).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.into(), transform, rect.elements()); + sb.push_layer(Mix::Normal, transform, &rect); for (i, c) in COLORS.iter().enumerate() { - let stops = &[ - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 0.0, - }, - GradientStop { - color: c.clone(), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(0.0, 200.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.push_layer(blend, transform, rect.elements()); + let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::WHITE, *c]); + sb.push_layer(blend, transform, &rect); // squash the ellipse let a = transform - * Affine::translate(100., 100.) - * Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) - * Affine::scale(1.0, 0.357) - * Affine::translate(-100., -100.); + * 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, - make_ellipse(100., 100., 90., 90.), + &Ellipse::new((100., 100.), (90., 90.), 0.), ); sb.pop_layer(); } @@ -332,7 +278,7 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) Affine::IDENTITY, &Brush::Solid(Color::rgb8(128, 128, 128)), None, - Rect::from_origin_size(Point::new(0.0, 0.0), 1000.0, 1000.0).elements(), + &Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0)), ); let text_size = 60.0 + 40.0 * (0.01 * i as f32).sin(); let s = "\u{1f600}hello piet-gpu text!"; @@ -341,7 +287,7 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) None, text_size, None, - Affine::translate(110.0, 600.0), + Affine::translate((110.0, 600.0)), s, ); text.add( @@ -349,121 +295,58 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) None, text_size, None, - Affine::translate(110.0, 700.0), + Affine::translate((110.0, 700.0)), s, ); - let th = (std::f32::consts::PI / 180.0) * (i as f32); + let th = (std::f64::consts::PI / 180.0) * (i 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( - &simple_stroke(5.0), + &Stroke::new(5.0), Affine::IDENTITY, &Brush::Solid(Color::rgb8(128, 0, 0)), None, - &[PathElement::MoveTo(center), PathElement::LineTo(p1)], + &&[PathEl::MoveTo(center), PathEl::LineTo(p1)][..], ); } #[allow(unused)] pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) { - let th = (std::f32::consts::PI / 180.0) * (i as f32); - let stops = &[ - GradientStop { - color: Color::rgb8(255, 0, 0), - offset: 0.0, - }, - GradientStop { - color: Color::rgb8(0, 255, 0), - offset: 0.5, - }, - GradientStop { - color: Color::rgb8(0, 0, 255), - offset: 1.0, - }, - ][..]; - let linear = LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(0.0, 200.0), - stops: stops.into(), - extend: ExtendMode::Pad, - } - .into(); + let th = (std::f64::consts::PI / 180.0) * (i as f64); + let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([ + Color::RED, + Color::GREEN, + Color::BLUE, + ]); sb.fill( Fill::NonZero, - Affine::translate(200.0, 200.0), + Affine::translate((200.0, 200.0)), &linear, - Some(Affine::rotate(th).around_center(200.0, 100.0)), - Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), + Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))), + &Rect::from_origin_size(Point::default(), (400.0, 200.0)), ); sb.stroke( - &simple_stroke(40.0), - Affine::translate(800.0, 200.0), + &Stroke::new(40.0), + Affine::translate((800.0, 200.0)), &linear, - Some(Affine::rotate(th).around_center(200.0, 100.0)), - Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), + Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))), + &Rect::from_origin_size(Point::default(), (400.0, 200.0)), ); } -fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator + 'a + Clone { - path.elements() - .iter() - .map(|el| PathElement::from_kurbo(*el)) +fn around_center(xform: Affine, center: Point) -> Affine { + Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2()) } -fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator + Clone { - let a = 0.551915024494; - let arx = a * rx; - let ary = a * ry; - let elements = [ - PathElement::MoveTo(Point::new(cx + rx, cy)), - PathElement::CurveTo( - Point::new(cx + rx, cy + ary), - Point::new(cx + arx, cy + ry), - Point::new(cx, cy + ry), - ), - PathElement::CurveTo( - Point::new(cx - arx, cy + ry), - Point::new(cx - rx, cy + ary), - Point::new(cx - rx, cy), - ), - PathElement::CurveTo( - Point::new(cx - rx, cy - ary), - Point::new(cx - arx, cy - ry), - Point::new(cx, cy - ry), - ), - PathElement::CurveTo( - Point::new(cx + arx, cy - ry), - Point::new(cx + rx, cy - ary), - Point::new(cx + rx, cy), - ), - PathElement::Close, - ]; - (0..elements.len()).map(move |i| elements[i]) -} - -fn make_diamond(cx: f32, cy: f32) -> impl Iterator + Clone { - const SIZE: f32 = 50.0; - let elements = [ - PathElement::MoveTo(Point::new(cx, cy - SIZE)), - PathElement::LineTo(Point::new(cx + SIZE, cy)), - PathElement::LineTo(Point::new(cx, cy + SIZE)), - PathElement::LineTo(Point::new(cx - SIZE, cy)), - PathElement::Close, - ]; - (0..elements.len()).map(move |i| elements[i]) -} - -fn simple_stroke(width: f32) -> Stroke<[f32; 0]> { - Stroke { - width, - join: Join::Round, - miter_limit: 1.4, - start_cap: Cap::Round, - end_cap: Cap::Round, - dash_pattern: [], - dash_offset: 0.0, - scale: true, - } +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, + ] } diff --git a/piet-gpu/src/simple_text.rs b/piet-gpu/src/simple_text.rs index 8bcb97c..00eefaa 100644 --- a/piet-gpu/src/simple_text.rs +++ b/piet-gpu/src/simple_text.rs @@ -15,7 +15,8 @@ // Also licensed under MIT license, at your choice. use piet_scene::glyph::{pinot, pinot::TableProvider, GlyphContext}; -use piet_scene::{Affine, Brush, SceneBuilder}; +use piet_scene::kurbo::Affine; +use piet_scene::{Brush, SceneBuilder}; pub use pinot::FontRef; @@ -49,8 +50,8 @@ impl SimpleText { }); if let Some(cmap) = font.cmap() { if let Some(hmtx) = font.hmtx() { - let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f32; - let scale = size / upem; + let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f64; + let scale = size as f64 / upem; let vars: [(pinot::types::Tag, f32); 0] = []; let mut provider = self.gcx.new_provider(font, None, size, false, vars); let hmetrics = hmtx.hmetrics(); @@ -58,17 +59,18 @@ impl SimpleText { .get(hmetrics.len().saturating_sub(1)) .map(|h| h.advance_width) .unwrap_or(0); - let mut pen_x = 0f32; + let mut pen_x = 0f64; for ch in text.chars() { let gid = cmap.map(ch as u32).unwrap_or(0); let advance = hmetrics .get(gid as usize) .map(|h| h.advance_width) - .unwrap_or(default_advance) as f32 + .unwrap_or(default_advance) as f64 * scale; if let Some(glyph) = provider.get(gid, brush) { - let xform = - transform * Affine::translate(pen_x, 0.0) * Affine::scale(1.0, -1.0); + let xform = transform + * Affine::translate((pen_x, 0.0)) + * Affine::scale_non_uniform(1.0, -1.0); builder.append(&glyph, Some(xform)); } pen_x += advance; diff --git a/piet-scene/Cargo.toml b/piet-scene/Cargo.toml index e80cbb8..239176c 100644 --- a/piet-scene/Cargo.toml +++ b/piet-scene/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" bytemuck = { version = "1.7.2", features = ["derive"] } smallvec = "1.8.0" moscato = { git = "https://github.com/dfrg/pinot" } -kurbo = { version = "0.8.3", optional = true } +peniko = { git = "https://github.com/linebender/peniko" } diff --git a/piet-scene/src/brush/color.rs b/piet-scene/src/brush/color.rs deleted file mode 100644 index 59ffae5..0000000 --- a/piet-scene/src/brush/color.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -/// 32-bit RGBA color. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] -pub struct Color { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} - -impl Color { - pub const fn rgb8(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b, a: 255 } - } - - pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Self { - Self { r, g, b, a } - } - - pub fn to_premul_u32(self) -> u32 { - let a = self.a as f64 * (1.0 / 255.0); - let r = (self.r as f64 * a).round() as u32; - let g = (self.g as f64 * a).round() as u32; - let b = (self.b as f64 * a).round() as u32; - (r << 24) | (g << 16) | (b << 8) | self.a as u32 - } -} diff --git a/piet-scene/src/brush/gradient.rs b/piet-scene/src/brush/gradient.rs deleted file mode 100644 index a010ad5..0000000 --- a/piet-scene/src/brush/gradient.rs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -use super::color::Color; -use super::ExtendMode; -use crate::geometry::Point; -use smallvec::SmallVec; -use std::hash::{Hash, Hasher}; - -/// Offset and color of a transition point in a gradient. -#[derive(Copy, Clone, PartialOrd, Default, Debug)] -pub struct GradientStop { - pub offset: f32, - pub color: Color, -} - -impl Hash for GradientStop { - fn hash(&self, state: &mut H) { - self.offset.to_bits().hash(state); - self.color.hash(state); - } -} - -// Override PartialEq to use to_bits for the offset to match with the Hash impl -impl std::cmp::PartialEq for GradientStop { - fn eq(&self, other: &Self) -> bool { - self.offset.to_bits() == other.offset.to_bits() && self.color == other.color - } -} - -impl std::cmp::Eq for GradientStop {} - -/// Collection of gradient stops. -pub type GradientStops = SmallVec<[GradientStop; 4]>; - -/// Definition of a gradient that transitions between two or more colors along -/// a line. -#[derive(Clone, Debug)] -pub struct LinearGradient { - pub start: Point, - pub end: Point, - pub stops: GradientStops, - pub extend: ExtendMode, -} - -/// Definition of a gradient that transitions between two or more colors that -/// radiate from an origin. -#[derive(Clone, Debug)] -pub struct RadialGradient { - pub center0: Point, - pub radius0: f32, - pub center1: Point, - pub radius1: f32, - pub stops: GradientStops, - pub extend: ExtendMode, -} - -/// Definition gradient that transitions between two or more colors that rotate -/// around a center point. -#[derive(Clone, Debug)] -pub struct SweepGradient { - pub center: Point, - pub start_angle: f32, - pub end_angle: f32, - pub stops: GradientStops, - pub extend: ExtendMode, -} diff --git a/piet-scene/src/brush/image.rs b/piet-scene/src/brush/image.rs deleted file mode 100644 index 737ca94..0000000 --- a/piet-scene/src/brush/image.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -use std::result::Result; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::Arc; - -/// Image data resource. -#[derive(Clone, Debug)] -pub struct Image(Arc); - -#[derive(Clone, Debug)] -struct Inner { - id: u64, - width: u32, - height: u32, - data: Arc<[u8]>, -} - -impl Image { - pub fn new( - width: u32, - height: u32, - data: impl Into>, - ) -> Result { - let data_size = width - .checked_mul(height) - .and_then(|x| x.checked_mul(4)) - .ok_or(ImageDataSizeError)? as usize; - let data = data.into(); - if data.len() < data_size { - return Err(ImageDataSizeError); - } - static ID: AtomicU64 = AtomicU64::new(1); - Ok(Self(Arc::new(Inner { - id: ID.fetch_add(1, Ordering::Relaxed), - width, - height, - data, - }))) - } - - pub fn id(&self) -> u64 { - self.0.id - } - - pub fn width(&self) -> u32 { - self.0.width - } - - pub fn height(&self) -> u32 { - self.0.height - } - - pub fn data(&self) -> &[u8] { - &self.0.data - } -} - -/// Error returned when image data size is not sufficient for the specified -/// dimensions. -#[derive(Clone, Debug)] -pub struct ImageDataSizeError; diff --git a/piet-scene/src/brush/mod.rs b/piet-scene/src/brush/mod.rs deleted file mode 100644 index 1d7a674..0000000 --- a/piet-scene/src/brush/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -mod color; -mod gradient; -mod image; - -pub use color::Color; -pub use gradient::*; -pub use image::*; - -/// Describes the content of a filled or stroked shape. -#[derive(Clone, Debug)] -pub enum Brush { - Solid(Color), - LinearGradient(LinearGradient), - RadialGradient(RadialGradient), - SweepGradient(SweepGradient), - Image(Image), -} - -/// Defines how a brush is extended when the content does not -/// completely fill a shape. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ExtendMode { - Pad, - Repeat, - Reflect, -} - -impl From for Brush { - fn from(c: Color) -> Self { - Self::Solid(c) - } -} - -impl From for Brush { - fn from(g: LinearGradient) -> Self { - Self::LinearGradient(g) - } -} - -impl From for Brush { - fn from(g: RadialGradient) -> Self { - Self::RadialGradient(g) - } -} diff --git a/piet-scene/src/conv.rs b/piet-scene/src/conv.rs new file mode 100644 index 0000000..ad16062 --- /dev/null +++ b/piet-scene/src/conv.rs @@ -0,0 +1,28 @@ +use peniko::kurbo::{Affine, Point}; + +pub fn affine_to_f32(affine: &Affine) -> [f32; 6] { + let c = affine.as_coeffs(); + [ + c[0] as f32, + c[1] as f32, + c[2] as f32, + c[3] as f32, + c[4] as f32, + c[5] as f32, + ] +} + +pub fn affine_from_f32(coeffs: &[f32; 6]) -> Affine { + Affine::new([ + coeffs[0] as f64, + coeffs[1] as f64, + coeffs[2] as f64, + coeffs[3] as f64, + coeffs[4] as f64, + coeffs[5] as f64, + ]) +} + +pub fn point_to_f32(point: Point) -> [f32; 2] { + [point.x as f32, point.y as f32] +} diff --git a/piet-scene/src/geometry.rs b/piet-scene/src/geometry.rs deleted file mode 100644 index dc76fe8..0000000 --- a/piet-scene/src/geometry.rs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -// This module is based in part on kurbo (https://github.com/linebender/kurbo) - -use bytemuck::{Pod, Zeroable}; -use core::borrow::Borrow; -use core::hash::{Hash, Hasher}; - -/// Two dimensional point. -#[derive(Copy, Clone, PartialEq, PartialOrd, Default, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Point { - pub x: f32, - pub y: f32, -} - -impl Hash for Point { - fn hash(&self, state: &mut H) { - self.x.to_bits().hash(state); - self.y.to_bits().hash(state); - } -} - -impl Point { - pub const fn new(x: f32, y: f32) -> Self { - Self { x, y } - } - - pub fn transform(&self, affine: &Affine) -> Self { - Self { - x: self.x * affine.xx + self.y * affine.yx + affine.dx, - y: self.y * affine.yy + self.y * affine.xy + affine.dy, - } - } -} - -impl From<[f32; 2]> for Point { - fn from(value: [f32; 2]) -> Self { - Self::new(value[0], value[1]) - } -} - -impl From<(f32, f32)> for Point { - fn from(value: (f32, f32)) -> Self { - Self::new(value.0, value.1) - } -} - -/// Affine transformation matrix. -#[derive(Copy, Clone, PartialEq, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Affine { - pub xx: f32, - pub yx: f32, - pub xy: f32, - pub yy: f32, - pub dx: f32, - pub dy: f32, -} - -impl Affine { - pub const IDENTITY: Self = Self { - xx: 1.0, - yx: 0.0, - xy: 0.0, - yy: 1.0, - dx: 0.0, - dy: 0.0, - }; - - pub const fn new(elements: &[f32; 6]) -> Self { - Self { - xx: elements[0], - yx: elements[1], - xy: elements[2], - yy: elements[3], - dx: elements[4], - dy: elements[5], - } - } - - /// Creates a new affine transform representing the specified scale along the - /// x and y axes. - pub fn scale(x: f32, y: f32) -> Self { - Self::new(&[x, 0., 0., y, 0., 0.]) - } - - /// Creates a new affine transform representing the specified translation. - pub fn translate(x: f32, y: f32) -> Self { - Self::new(&[1., 0., 0., 1., x, y]) - } - - /// Creates a new affine transform representing a counter-clockwise - /// rotation for the specified angle in radians. - pub fn rotate(th: f32) -> Self { - let (s, c) = th.sin_cos(); - Self::new(&[c, s, -s, c, 0., 0.]) - } - - /// Creates a new skew transform - pub fn skew(x: f32, y: f32) -> Self { - Self::new(&[1., x.tan(), y.tan(), 1., 0., 0.]) - } - - pub fn around_center(&self, x: f32, y: f32) -> Self { - Self::translate(x, y) * *self * Self::translate(-x, -y) - } - - /// Transforms the specified point. - pub fn transform_point(&self, point: Point) -> Point { - Point { - x: point.x * self.xx + point.y * self.yx + self.dx, - y: point.y * self.yy + point.y * self.xy + self.dy, - } - } - - /// Compute the determinant of this transform. - pub fn determinant(self) -> f32 { - self.xx * self.yy - self.yx * self.xy - } - - /// Compute the inverse transform. - /// - /// Produces NaN values when the determinant is zero. - pub fn inverse(self) -> Self { - let inv_det = self.determinant().recip(); - Self::new(&[ - inv_det * self.yy, - -inv_det * self.yx, - -inv_det * self.xy, - inv_det * self.xx, - inv_det * (self.xy * self.dy - self.yy * self.dx), - inv_det * (self.yx * self.dx - self.xx * self.dy), - ]) - } -} - -impl Default for Affine { - fn default() -> Self { - Self::IDENTITY - } -} - -impl std::ops::Mul for Affine { - type Output = Self; - fn mul(self, other: Self) -> Self { - Self::new(&[ - self.xx * other.xx + self.xy * other.yx, - self.yx * other.xx + self.yy * other.yx, - self.xx * other.xy + self.xy * other.yy, - self.yx * other.xy + self.yy * other.yy, - self.xx * other.dx + self.xy * other.dy + self.dx, - self.yx * other.dx + self.yy * other.dy + self.dy, - ]) - } -} - -/// Axis-aligned rectangle represented as minimum and maximum points. -#[derive(Copy, Clone, Default, Debug, Pod, Zeroable)] -#[repr(C)] -pub struct Rect { - pub min: Point, - pub max: Point, -} - -impl Rect { - /// Creates a new rectangle that encloses the specified collection of - /// points. - pub fn from_points(points: I) -> Self - where - I: IntoIterator, - I::Item: Borrow, - { - let mut rect = Self { - min: Point::new(f32::MAX, f32::MAX), - max: Point::new(f32::MIN, f32::MIN), - }; - let mut count = 0; - for point in points { - rect.add(*point.borrow()); - count += 1; - } - if count != 0 { - rect - } else { - Self::default() - } - } - - /// Creates a new rectangle from an origin point and dimensions. - pub fn from_origin_size(origin: Point, width: f32, height: f32) -> Self { - Self { - min: origin, - max: Point::new(origin.x + width, origin.y + height), - } - } - - /// Returns the width of the rectangle. - pub fn width(&self) -> f32 { - self.max.x - self.min.x - } - - /// Returns the height of the rectangle. - pub fn height(&self) -> f32 { - self.max.y - self.min.y - } - - /// Extends the rectangle to include the specified point. - pub fn add(&mut self, point: Point) { - self.min.x = self.min.x.min(point.x); - self.min.y = self.min.y.min(point.y); - self.max.x = self.max.x.max(point.x); - self.max.y = self.max.y.max(point.y); - } - - /// Returns a new rectangle that encloses the minimum and maximum points - /// of this rectangle after applying the specified transform to each. - pub fn transform(&self, affine: &Affine) -> Self { - Self::from_points([self.min.transform(affine), self.max.transform(affine)]) - } -} diff --git a/piet-scene/src/glyph.rs b/piet-scene/src/glyph.rs index 6f752b7..d18a89e 100644 --- a/piet-scene/src/glyph.rs +++ b/piet-scene/src/glyph.rs @@ -18,10 +18,9 @@ pub use moscato::pinot; -use crate::brush::{Brush, Color}; -use crate::geometry::Affine; -use crate::path::PathElement; -use crate::scene::{Fill, SceneBuilder, SceneFragment}; +use crate::scene::{SceneBuilder, SceneFragment}; +use peniko::kurbo::{Affine, Rect}; +use peniko::{Brush, Color, Fill, Mix}; use moscato::{Context, Scaler}; use pinot::{types::Tag, FontRef}; @@ -93,7 +92,7 @@ impl<'a> GlyphProvider<'a> { Affine::IDENTITY, brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))), None, - convert_path(path.elements()), + &convert_path(path.elements()), ); builder.finish(); Some(fragment) @@ -102,7 +101,6 @@ impl<'a> GlyphProvider<'a> { /// Returns a scene fragment containing the commands and resources to /// render the specified color glyph. pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option { - use crate::geometry::*; use moscato::Command; let glyph = self.scaler.color_glyph(palette_index, gid)?; let mut fragment = SceneFragment::default(); @@ -125,39 +123,39 @@ impl<'a> GlyphProvider<'a> { let path = glyph.path(*path_index)?; if let Some(xform) = xform_stack.last() { builder.push_layer( - Default::default(), + Mix::Clip, Affine::IDENTITY, - convert_transformed_path(path.elements(), xform), + &convert_transformed_path(path.elements(), xform), ); } else { builder.push_layer( - Default::default(), + Mix::Clip, Affine::IDENTITY, - convert_path(path.elements()), + &convert_path(path.elements()), ); } } Command::PopClip => builder.pop_layer(), Command::PushLayer(bounds) => { - let mut rect = Rect { - min: Point::new(bounds.min.x, bounds.min.y), - max: Point::new(bounds.max.x, bounds.max.y), - }; + let mut min = convert_point(bounds.min); + let mut max = convert_point(bounds.max); if let Some(xform) = xform_stack.last() { - rect = rect.transform(xform); + min = *xform * min; + max = *xform * max; } - builder.push_layer(Default::default(), Affine::IDENTITY, rect.elements()); + let rect = Rect::from_points(min, max); + builder.push_layer(Mix::Normal, Affine::IDENTITY, &rect); } Command::PopLayer => builder.pop_layer(), Command::BeginBlend(bounds, mode) => { - let mut rect = Rect { - min: Point::new(bounds.min.x, bounds.min.y), - max: Point::new(bounds.max.x, bounds.max.y), - }; + let mut min = convert_point(bounds.min); + let mut max = convert_point(bounds.max); if let Some(xform) = xform_stack.last() { - rect = rect.transform(xform); + min = *xform * min; + max = *xform * max; } - builder.push_layer(convert_blend(*mode), Affine::IDENTITY, rect.elements()) + let rect = Rect::from_points(min, max); + builder.push_layer(convert_blend(*mode), Affine::IDENTITY, &rect); } Command::EndBlend => builder.pop_layer(), Command::SimpleFill(path_index, brush, brush_xform) => { @@ -170,7 +168,7 @@ impl<'a> GlyphProvider<'a> { Affine::IDENTITY, &brush, brush_xform.map(|x| x * *xform), - convert_transformed_path(path.elements(), xform), + &convert_transformed_path(path.elements(), xform), ); } else { builder.fill( @@ -178,7 +176,7 @@ impl<'a> GlyphProvider<'a> { Affine::IDENTITY, &brush, brush_xform, - convert_path(path.elements()), + &convert_path(path.elements()), ); } } @@ -193,54 +191,28 @@ impl<'a> GlyphProvider<'a> { } } -fn convert_path( - path: impl Iterator + Clone, -) -> impl Iterator + Clone { - use crate::geometry::Point; - path.map(|el| match el { - moscato::Element::MoveTo(p0) => PathElement::MoveTo(Point::new(p0.x, p0.y)), - moscato::Element::LineTo(p0) => PathElement::LineTo(Point::new(p0.x, p0.y)), - moscato::Element::QuadTo(p0, p1) => { - PathElement::QuadTo(Point::new(p0.x, p0.y), Point::new(p1.x, p1.y)) - } - moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo( - Point::new(p0.x, p0.y), - Point::new(p1.x, p1.y), - Point::new(p2.x, p2.y), - ), - moscato::Element::Close => PathElement::Close, - }) +fn convert_path(path: impl Iterator + Clone) -> peniko::kurbo::BezPath { + let mut result = peniko::kurbo::BezPath::new(); + for el in path { + result.push(convert_path_el(&el)); + } + result } fn convert_transformed_path( path: impl Iterator + Clone, xform: &Affine, -) -> impl Iterator + Clone { - use crate::geometry::Point; - let xform = *xform; - path.map(move |el| match el { - moscato::Element::MoveTo(p0) => { - PathElement::MoveTo(Point::new(p0.x, p0.y).transform(&xform)) - } - moscato::Element::LineTo(p0) => { - PathElement::LineTo(Point::new(p0.x, p0.y).transform(&xform)) - } - moscato::Element::QuadTo(p0, p1) => PathElement::QuadTo( - Point::new(p0.x, p0.y).transform(&xform), - Point::new(p1.x, p1.y).transform(&xform), - ), - moscato::Element::CurveTo(p0, p1, p2) => PathElement::CurveTo( - Point::new(p0.x, p0.y).transform(&xform), - Point::new(p1.x, p1.y).transform(&xform), - Point::new(p2.x, p2.y).transform(&xform), - ), - moscato::Element::Close => PathElement::Close, - }) +) -> peniko::kurbo::BezPath { + let mut result = peniko::kurbo::BezPath::new(); + for el in path { + result.push(*xform * convert_path_el(&el)); + } + result } -fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode { - use crate::scene::{BlendMode, Compose, Mix}; +fn convert_blend(mode: moscato::CompositeMode) -> peniko::BlendMode { use moscato::CompositeMode; + use peniko::{BlendMode, Compose}; let mut mix = Mix::Normal; let mut compose = Compose::SrcOver; match mode { @@ -276,20 +248,23 @@ fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode { BlendMode { mix, compose } } -fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { - crate::geometry::Affine { - xx: xform.xx, - yx: xform.yx, - xy: xform.xy, - yy: xform.yy, - dx: xform.dx, - dy: xform.dy, - } +fn convert_transform(xform: &moscato::Transform) -> peniko::kurbo::Affine { + peniko::kurbo::Affine::new([ + xform.xx as f64, + xform.yx as f64, + xform.xy as f64, + xform.yy as f64, + xform.dx as f64, + xform.dy as f64, + ]) } -fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { - use crate::brush::*; - use crate::geometry::*; +fn convert_point(point: moscato::Point) -> peniko::kurbo::Point { + peniko::kurbo::Point::new(point.x as f64, point.y as f64) +} + +fn convert_brush(brush: &moscato::Brush) -> peniko::Brush { + use peniko::{LinearGradient, RadialGradient}; match brush { moscato::Brush::Solid(color) => Brush::Solid(Color { r: color.r, @@ -298,43 +273,58 @@ fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { a: color.a, }), moscato::Brush::LinearGradient(grad) => Brush::LinearGradient(LinearGradient { - start: Point::new(grad.start.x, grad.start.y), - end: Point::new(grad.end.x, grad.end.y), + start: convert_point(grad.start), + end: convert_point(grad.end), stops: convert_stops(&grad.stops), extend: convert_extend(grad.extend), }), moscato::Brush::RadialGradient(grad) => Brush::RadialGradient(RadialGradient { - center0: Point::new(grad.center0.x, grad.center0.y), - center1: Point::new(grad.center1.x, grad.center1.y), - radius0: grad.radius0, - radius1: grad.radius1, + start_center: convert_point(grad.center0), + end_center: convert_point(grad.center1), + start_radius: grad.radius0, + end_radius: grad.radius1, stops: convert_stops(&grad.stops), extend: convert_extend(grad.extend), }), } } -fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::GradientStops { - use crate::brush::GradientStop; +fn convert_stops(stops: &[moscato::ColorStop]) -> peniko::ColorStops { stops .iter() - .map(|stop| GradientStop { - offset: stop.offset, - color: Color { - r: stop.color.r, - g: stop.color.g, - b: stop.color.b, - a: stop.color.a, - }, + .map(|stop| { + ( + stop.offset, + Color { + r: stop.color.r, + g: stop.color.g, + b: stop.color.b, + a: stop.color.a, + }, + ) + .into() }) .collect() } -fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::ExtendMode { - use crate::brush::ExtendMode::*; +fn convert_extend(extend: moscato::ExtendMode) -> peniko::Extend { + use peniko::Extend::*; match extend { moscato::ExtendMode::Pad => Pad, moscato::ExtendMode::Repeat => Repeat, moscato::ExtendMode::Reflect => Reflect, } } + +fn convert_path_el(el: &moscato::Element) -> peniko::kurbo::PathEl { + use peniko::kurbo::PathEl::*; + match el { + moscato::Element::MoveTo(p0) => MoveTo(convert_point(*p0)), + moscato::Element::LineTo(p0) => LineTo(convert_point(*p0)), + moscato::Element::QuadTo(p0, p1) => QuadTo(convert_point(*p0), convert_point(*p1)), + moscato::Element::CurveTo(p0, p1, p2) => { + CurveTo(convert_point(*p0), convert_point(*p1), convert_point(*p2)) + } + moscato::Element::Close => ClosePath, + } +} diff --git a/piet-scene/src/lib.rs b/piet-scene/src/lib.rs index 4e0729b..04ac3c7 100644 --- a/piet-scene/src/lib.rs +++ b/piet-scene/src/lib.rs @@ -14,117 +14,12 @@ // // Also licensed under MIT license, at your choice. -mod brush; -mod geometry; -mod path; -mod resource; +pub use peniko; + +mod conv; mod scene; pub mod glyph; -pub use brush::*; -pub use geometry::*; -pub use path::*; -pub use resource::*; +pub use peniko::*; pub use scene::*; - -/// Implement conversions to and from Kurbo types when the `kurbo` feature is -/// enabled. -#[cfg(feature = "kurbo")] -mod kurbo_conv { - use super::geometry::{Affine, Point, Rect}; - use super::path::PathElement; - - impl Point { - /// Creates a new point from the equivalent kurbo type. - pub fn from_kurbo(point: kurbo::Point) -> Self { - Self::new(point.x as f32, point.y as f32) - } - } - - impl From for kurbo::Point { - fn from(p: Point) -> kurbo::Point { - Self::new(p.x as f64, p.y as f64) - } - } - - impl Affine { - /// Creates a new affine transformation from the equivalent kurbo type. - pub fn from_kurbo(affine: kurbo::Affine) -> Self { - let c = affine.as_coeffs(); - Self { - xx: c[0] as f32, - yx: c[1] as f32, - xy: c[2] as f32, - yy: c[3] as f32, - dx: c[4] as f32, - dy: c[5] as f32, - } - } - } - - impl From for kurbo::Affine { - fn from(a: Affine) -> Self { - Self::new([ - a.xx as f64, - a.yx as f64, - a.yx as f64, - a.yy as f64, - a.dx as f64, - a.dy as f64, - ]) - } - } - - impl Rect { - /// Creates a new rectangle from the equivalent kurbo type. - pub fn from_kurbo(rect: kurbo::Rect) -> Self { - Self { - min: Point::new(rect.x0 as f32, rect.y0 as f32), - max: Point::new(rect.x1 as f32, rect.y1 as f32), - } - } - } - - impl From for kurbo::Rect { - fn from(r: Rect) -> Self { - Self { - x0: r.min.x as f64, - y0: r.min.y as f64, - x1: r.max.x as f64, - y1: r.max.y as f64, - } - } - } - - impl PathElement { - /// Creates a new path element from the equivalent kurbo type. - pub fn from_kurbo(el: kurbo::PathEl) -> Self { - use kurbo::PathEl::*; - match el { - MoveTo(p0) => Self::MoveTo(Point::from_kurbo(p0)), - LineTo(p0) => Self::LineTo(Point::from_kurbo(p0)), - QuadTo(p0, p1) => Self::QuadTo(Point::from_kurbo(p0), Point::from_kurbo(p1)), - CurveTo(p0, p1, p2) => Self::CurveTo( - Point::from_kurbo(p0), - Point::from_kurbo(p1), - Point::from_kurbo(p2), - ), - ClosePath => Self::Close, - } - } - } - - impl From for kurbo::PathEl { - fn from(e: PathElement) -> Self { - use PathElement::*; - match e { - MoveTo(p0) => Self::MoveTo(p0.into()), - LineTo(p0) => Self::LineTo(p0.into()), - QuadTo(p0, p1) => Self::QuadTo(p0.into(), p1.into()), - CurveTo(p0, p1, p2) => Self::CurveTo(p0.into(), p1.into(), p2.into()), - Close => Self::ClosePath, - } - } - } -} diff --git a/piet-scene/src/path.rs b/piet-scene/src/path.rs deleted file mode 100644 index bccf656..0000000 --- a/piet-scene/src/path.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -use super::geometry::{Point, Rect}; - -/// Action of a path element. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum PathVerb { - MoveTo, - LineTo, - QuadTo, - CurveTo, - Close, -} - -/// Element of a path represented by a verb and its associated points. -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum PathElement { - MoveTo(Point), - LineTo(Point), - QuadTo(Point, Point), - CurveTo(Point, Point, Point), - Close, -} - -impl PathElement { - /// Returns the verb that describes the action of the path element. - pub fn verb(&self) -> PathVerb { - match self { - Self::MoveTo(..) => PathVerb::MoveTo, - Self::LineTo(..) => PathVerb::LineTo, - Self::QuadTo(..) => PathVerb::QuadTo, - Self::CurveTo(..) => PathVerb::CurveTo, - Self::Close => PathVerb::Close, - } - } -} - -impl Rect { - pub fn elements(&self) -> impl Iterator + Clone { - let elements = [ - PathElement::MoveTo((self.min.x, self.min.y).into()), - PathElement::LineTo((self.max.x, self.min.y).into()), - PathElement::LineTo((self.max.x, self.max.y).into()), - PathElement::LineTo((self.min.x, self.max.y).into()), - PathElement::Close, - ]; - (0..5).map(move |i| elements[i]) - } -} diff --git a/piet-scene/src/scene/blend.rs b/piet-scene/src/scene/blend.rs deleted file mode 100644 index 43ecfa7..0000000 --- a/piet-scene/src/scene/blend.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -/// Defines the color mixing function for a blend operation. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[repr(C)] -pub enum Mix { - Normal = 0, - Multiply = 1, - Screen = 2, - Overlay = 3, - Darken = 4, - Lighten = 5, - ColorDodge = 6, - ColorBurn = 7, - HardLight = 8, - SoftLight = 9, - Difference = 10, - Exclusion = 11, - Hue = 12, - Saturation = 13, - Color = 14, - Luminosity = 15, - // Clip is the same as normal, but doesn't always push a blend group. - Clip = 128, -} - -/// Defines the layer composition function for a blend operation. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[repr(C)] -pub enum Compose { - Clear = 0, - Copy = 1, - Dest = 2, - SrcOver = 3, - DestOver = 4, - SrcIn = 5, - DestIn = 6, - SrcOut = 7, - DestOut = 8, - SrcAtop = 9, - DestAtop = 10, - Xor = 11, - Plus = 12, - PlusLighter = 13, -} - -/// Blend mode consisting of mixing and composition functions. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct BlendMode { - pub mix: Mix, - pub compose: Compose, -} - -impl BlendMode { - pub fn new(mix: Mix, compose: Compose) -> Self { - Self { mix, compose } - } - - pub fn pack(&self) -> u32 { - (self.mix as u32) << 8 | self.compose as u32 - } -} - -impl Default for BlendMode { - fn default() -> Self { - Self { - mix: Mix::Clip, - compose: Compose::SrcOver, - } - } -} - -impl From for BlendMode { - fn from(mix: Mix) -> Self { - Self { - mix, - compose: Compose::SrcOver, - } - } -} - -impl From for BlendMode { - fn from(compose: Compose) -> Self { - Self { - mix: Mix::Normal, - compose, - } - } -} diff --git a/piet-scene/src/scene/builder.rs b/piet-scene/src/scene/builder.rs index 7100433..17a236a 100644 --- a/piet-scene/src/scene/builder.rs +++ b/piet-scene/src/scene/builder.rs @@ -14,11 +14,11 @@ // // Also licensed under MIT license, at your choice. -use super::style::{Fill, Stroke}; -use super::{Affine, BlendMode, PathElement, Scene, SceneData, SceneFragment}; -use crate::{brush::*, ResourcePatch}; +use super::{conv, Scene, SceneData, SceneFragment}; +use crate::ResourcePatch; use bytemuck::{Pod, Zeroable}; -use core::borrow::Borrow; +use peniko::kurbo::{Affine, PathEl, Rect, Shape}; +use peniko::{BlendMode, BrushRef, ColorStop, Fill, Stroke}; use smallvec::SmallVec; /// Builder for constructing a scene or scene fragment. @@ -51,44 +51,43 @@ impl<'a> SceneBuilder<'a> { /// Pushes a new layer bound by the specifed shape and composed with /// previous layers using the specified blend mode. - pub fn push_layer<'s, E>(&mut self, blend: BlendMode, transform: Affine, elements: E) - where - E: IntoIterator, - E::IntoIter: Clone, - E::Item: Borrow, - { + pub fn push_layer( + &mut self, + blend: impl Into, + transform: Affine, + shape: &impl Shape, + ) { + let blend = blend.into(); self.maybe_encode_transform(transform); self.linewidth(-1.0); - let elements = elements.into_iter(); - self.encode_path(elements, true); - self.begin_clip(Some(blend)); + if !self.encode_path(shape, true) { + // If the layer shape is invalid, encode a valid empty path. This suppresses + // all drawing until the layer is popped. + self.encode_path(&Rect::new(0.0, 0.0, 0.0, 0.0), true); + } + self.begin_clip(blend); self.layers.push(blend); } /// Pops the current layer. pub fn pop_layer(&mut self) { - if let Some(layer) = self.layers.pop() { - self.end_clip(Some(layer)); + if let Some(blend) = self.layers.pop() { + self.end_clip(blend); } } /// Fills a shape using the specified style and brush. - pub fn fill<'s, E>( + pub fn fill<'b>( &mut self, _style: Fill, transform: Affine, - brush: &Brush, + brush: impl Into>, brush_transform: Option, - elements: E, - ) where - E: IntoIterator, - E::IntoIter: Clone, - E::Item: Borrow, - { + shape: &impl Shape, + ) { self.maybe_encode_transform(transform); self.linewidth(-1.0); - let elements = elements.into_iter(); - if self.encode_path(elements, true) { + if self.encode_path(shape, true) { if let Some(brush_transform) = brush_transform { self.encode_transform(transform * brush_transform); self.swap_last_tags(); @@ -100,23 +99,17 @@ impl<'a> SceneBuilder<'a> { } /// Strokes a shape using the specified style and brush. - pub fn stroke<'s, D, E>( + pub fn stroke<'b>( &mut self, - style: &Stroke, + style: &Stroke, transform: Affine, - brush: &Brush, + brush: impl Into>, brush_transform: Option, - elements: E, - ) where - D: Borrow<[f32]>, - E: IntoIterator, - E::IntoIter: Clone, - E::Item: Borrow, - { + shape: &impl Shape, + ) { self.maybe_encode_transform(transform); self.linewidth(style.width); - let elements = elements.into_iter(); - if self.encode_path(elements, false) { + if self.encode_path(shape, false) { if let Some(brush_transform) = brush_transform { self.encode_transform(transform * brush_transform); self.swap_last_tags(); @@ -134,30 +127,39 @@ impl<'a> SceneBuilder<'a> { /// Completes construction and finalizes the underlying scene. pub fn finish(mut self) { - while let Some(layer) = self.layers.pop() { - self.end_clip(Some(layer)); + while !self.layers.is_empty() { + self.pop_layer(); } } } impl<'a> SceneBuilder<'a> { - fn encode_path(&mut self, elements: E, is_fill: bool) -> bool - where - E: Iterator, - E::Item: Borrow, - { + /// Encodes a path for the specified shape. + /// + /// When the `is_fill` parameter is true, closes any open subpaths by inserting + /// a line to the start point of the subpath with the end segment bit set. + fn encode_path(&mut self, shape: &impl Shape, is_fill: bool) -> bool { let mut b = PathBuilder::new( &mut self.scene.tag_stream, &mut self.scene.pathseg_stream, is_fill, ); - for el in elements { - match el.borrow() { - PathElement::MoveTo(p0) => b.move_to(p0.x, p0.y), - PathElement::LineTo(p0) => b.line_to(p0.x, p0.y), - PathElement::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), - PathElement::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y), - PathElement::Close => b.close_path(), + for el in shape.path_elements(0.1) { + match el { + PathEl::MoveTo(p0) => b.move_to(p0.x as f32, p0.y as f32), + PathEl::LineTo(p0) => b.line_to(p0.x as f32, p0.y as f32), + PathEl::QuadTo(p0, p1) => { + b.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32) + } + PathEl::CurveTo(p0, p1, p2) => b.cubic_to( + p0.x as f32, + p0.y as f32, + p1.x as f32, + p1.y as f32, + p2.x as f32, + p2.y as f32, + ), + PathEl::ClosePath => b.close_path(), } } b.finish(); @@ -171,14 +173,16 @@ impl<'a> SceneBuilder<'a> { } fn maybe_encode_transform(&mut self, transform: Affine) { - if self.scene.transform_stream.last() != Some(&transform) { + if self.scene.transform_stream.last() != Some(&conv::affine_to_f32(&transform)) { self.encode_transform(transform); } } fn encode_transform(&mut self, transform: Affine) { self.scene.tag_stream.push(0x20); - self.scene.transform_stream.push(transform); + self.scene + .transform_stream + .push(conv::affine_to_f32(&transform)); } // Swap the last two tags in the tag stream; used for transformed @@ -196,45 +200,44 @@ impl<'a> SceneBuilder<'a> { } } - fn encode_brush(&mut self, brush: &Brush) { - match brush { - Brush::Solid(color) => { + fn encode_brush<'b>(&mut self, brush: impl Into>) { + match brush.into() { + BrushRef::Solid(color) => { self.scene.drawtag_stream.push(DRAWTAG_FILLCOLOR); let rgba_color = color.to_premul_u32(); self.scene .drawdata_stream .extend(bytemuck::bytes_of(&FillColor { rgba_color })); } - Brush::LinearGradient(gradient) => { + BrushRef::LinearGradient(gradient) => { let index = self.add_ramp(&gradient.stops); self.scene.drawtag_stream.push(DRAWTAG_FILLLINGRADIENT); self.scene .drawdata_stream .extend(bytemuck::bytes_of(&FillLinGradient { index, - p0: [gradient.start.x, gradient.start.y], - p1: [gradient.end.x, gradient.end.y], + p0: conv::point_to_f32(gradient.start), + p1: conv::point_to_f32(gradient.end), })); } - Brush::RadialGradient(gradient) => { + BrushRef::RadialGradient(gradient) => { let index = self.add_ramp(&gradient.stops); self.scene.drawtag_stream.push(DRAWTAG_FILLRADGRADIENT); self.scene .drawdata_stream .extend(bytemuck::bytes_of(&FillRadGradient { index, - p0: [gradient.center0.x, gradient.center0.y], - p1: [gradient.center1.x, gradient.center1.y], - r0: gradient.radius0, - r1: gradient.radius1, + p0: conv::point_to_f32(gradient.start_center), + p1: conv::point_to_f32(gradient.end_center), + r0: gradient.start_radius, + r1: gradient.end_radius, })); } - Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), - Brush::Image(_image) => todo!("images aren't done yet!"), + BrushRef::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), } } - fn add_ramp(&mut self, stops: &[GradientStop]) -> u32 { + fn add_ramp(&mut self, stops: &[ColorStop]) -> u32 { let offset = self.scene.drawdata_stream.len(); let resources = &mut self.scene.resources; let stops_start = resources.stops.len(); @@ -247,10 +250,10 @@ impl<'a> SceneBuilder<'a> { } /// Start a clip. - fn begin_clip(&mut self, blend: Option) { + fn begin_clip(&mut self, blend: BlendMode) { self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP); let element = Clip { - blend: blend.unwrap_or(BlendMode::default()).pack(), + blend: encode_blend_mode(blend), }; self.scene .drawdata_stream @@ -258,10 +261,10 @@ impl<'a> SceneBuilder<'a> { self.scene.n_clip += 1; } - fn end_clip(&mut self, blend: Option) { + fn end_clip(&mut self, blend: BlendMode) { self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP); let element = Clip { - blend: blend.unwrap_or(BlendMode::default()).pack(), + blend: encode_blend_mode(blend), }; self.scene .drawdata_stream @@ -273,6 +276,10 @@ impl<'a> SceneBuilder<'a> { } } +fn encode_blend_mode(mode: BlendMode) -> u32 { + (mode.mix as u32) << 8 | mode.compose as u32 +} + // Tags for draw objects. See shader/drawtag.h for the authoritative source. const DRAWTAG_FILLCOLOR: u32 = 0x44; const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; @@ -371,6 +378,12 @@ impl<'a> PathBuilder<'a> { pub fn line_to(&mut self, x: f32, y: f32) { if self.state == PathState::Start { + if self.n_pathseg == 0 { + // This copies the behavior of kurbo which treats an initial line, quad + // or curve as a move. + self.move_to(x, y); + return; + } self.move_to(self.first_pt[0], self.first_pt[1]); } let buf = [x, y]; @@ -383,6 +396,10 @@ impl<'a> PathBuilder<'a> { pub fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) { if self.state == PathState::Start { + if self.n_pathseg == 0 { + self.move_to(x2, y2); + return; + } self.move_to(self.first_pt[0], self.first_pt[1]); } let buf = [x1, y1, x2, y2]; @@ -395,6 +412,10 @@ impl<'a> PathBuilder<'a> { pub fn cubic_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) { if self.state == PathState::Start { + if self.n_pathseg == 0 { + self.move_to(x3, y3); + return; + } self.move_to(self.first_pt[0], self.first_pt[1]); } let buf = [x1, y1, x2, y2, x3, y3]; diff --git a/piet-scene/src/scene/mod.rs b/piet-scene/src/scene/mod.rs index 91fd14b..f2aecb4 100644 --- a/piet-scene/src/scene/mod.rs +++ b/piet-scene/src/scene/mod.rs @@ -14,22 +14,19 @@ // // Also licensed under MIT license, at your choice. -mod blend; mod builder; -mod style; +mod resource; -pub use blend::{BlendMode, Compose, Mix}; pub use builder::SceneBuilder; -pub use style::*; +pub use resource::{ResourceBundle, ResourcePatch}; -use super::geometry::{Affine, Point}; -use super::path::PathElement; -use super::resource::{ResourceBundle, ResourcePatch}; +use super::conv; +use peniko::kurbo::Affine; /// Raw data streams describing an encoded scene. #[derive(Default)] pub struct SceneData { - pub transform_stream: Vec, + pub transform_stream: Vec<[f32; 6]>, pub tag_stream: Vec, pub pathseg_stream: Vec, pub linewidth_stream: Vec, @@ -58,8 +55,7 @@ impl SceneData { self.n_clip = 0; self.resources.clear(); if !is_fragment { - self.transform_stream - .push(Affine::new(&[1.0, 0.0, 0.0, 1.0, 0.0, 0.0])); + self.transform_stream.push([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); self.linewidth_stream.push(-1.0); } } @@ -68,8 +64,12 @@ impl SceneData { let stops_base = self.resources.stops.len(); let drawdata_base = self.drawdata_stream.len(); if let Some(transform) = *transform { - self.transform_stream - .extend(other.transform_stream.iter().map(|x| transform * *x)); + self.transform_stream.extend( + other + .transform_stream + .iter() + .map(|x| conv::affine_to_f32(&(transform * conv::affine_from_f32(x)))), + ); } else { self.transform_stream .extend_from_slice(&other.transform_stream); @@ -127,9 +127,8 @@ impl SceneFragment { self.data.is_empty() } - /// Returns the underlying stream of points that defined all encoded path - /// segments. - pub fn points(&self) -> &[Point] { + /// Returns the the entire sequence of points in the scene fragment. + pub fn points(&self) -> &[[f32; 2]] { if self.is_empty() { &[] } else { diff --git a/piet-scene/src/resource.rs b/piet-scene/src/scene/resource.rs similarity index 83% rename from piet-scene/src/resource.rs rename to piet-scene/src/scene/resource.rs index 7069304..3f0fafa 100644 --- a/piet-scene/src/resource.rs +++ b/piet-scene/src/scene/resource.rs @@ -1,13 +1,13 @@ -use crate::brush::GradientStop; use core::ops::Range; +use peniko::ColorStop; #[derive(Default)] /// Collection of late bound resources for a scene or scene fragment. pub struct ResourceBundle { /// Sequence of resource patches. pub patches: Vec, - /// Cache of gradient stops, referenced by range from the patches. - pub stops: Vec, + /// Cache of color stops, referenced by range from the patches. + pub stops: Vec, } impl ResourceBundle { diff --git a/piet-scene/src/scene/style.rs b/piet-scene/src/scene/style.rs deleted file mode 100644 index 0aded61..0000000 --- a/piet-scene/src/scene/style.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2022 The piet-gpu authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. - -use core::borrow::Borrow; - -/// Describes the winding rule that determines the interior portion of a path. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Fill { - NonZero, - EvenOdd, -} - -/// Defines the connection between two segments of a stroke. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Join { - /// A straight line connecting the segments. - Bevel, - /// The segments are extended to their natural intersection point. - Miter, - /// An arc between the segments. - Round, -} - -/// Defines the shape to be drawn at the ends of a stroke. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub enum Cap { - /// Flat cap. - Butt, - /// Square cap with dimensions equal to half the stroke width. - Square, - /// Rounded cap with radius equal to half the stroke width. - Round, -} - -/// Describes the visual style of a stroke. -#[derive(Copy, Clone, Debug)] -pub struct Stroke -where - D: Borrow<[f32]>, -{ - /// Width of the stroke. - pub width: f32, - /// Style for connecting segments of the stroke. - pub join: Join, - /// Limit for miter joins. - pub miter_limit: f32, - /// Style for capping the beginning of an open subpath. - pub start_cap: Cap, - /// Style for capping the end of an open subpath. - pub end_cap: Cap, - /// Lengths of dashes in alternating on/off order. - pub dash_pattern: D, - /// Offset of the first dash. - pub dash_offset: f32, - /// True if the stroke width should be affected by the scale of a - /// transform. - pub scale: bool, -} diff --git a/piet-wgsl/Cargo.toml b/piet-wgsl/Cargo.toml index b785eac..99bdb55 100644 --- a/piet-wgsl/Cargo.toml +++ b/piet-wgsl/Cargo.toml @@ -9,13 +9,12 @@ edition = "2021" wgpu = "0.14" env_logger = "0.9.1" pollster = "0.2.5" -futures-intrusive = "0.4.1" +futures-intrusive = "0.5.0" parking_lot = "0.12" bytemuck = { version = "1.12.1", features = ["derive"] } png = "0.17.6" -piet-scene = { path = "../piet-scene", features = ["kurbo"] } +piet-scene = { path = "../piet-scene" } # for picosvg, should be split out roxmltree = "0.13" -kurbo = "0.8.3" diff --git a/piet-wgsl/shader/shared/blend.wgsl b/piet-wgsl/shader/shared/blend.wgsl index cb1c586..5f9c583 100644 --- a/piet-wgsl/shader/shared/blend.wgsl +++ b/piet-wgsl/shader/shared/blend.wgsl @@ -45,11 +45,11 @@ fn color_burn(cb: f32, cs: f32) -> f32 { } fn hard_light(cb: vec3, cs: vec3) -> vec3 { - return mix( - screen(cb, 2.0 * cs - 1.0), - cb * 2.0 * cs, + return mix( + screen(cb, 2.0 * cs - 1.0), + cb * 2.0 * cs, vec3(cs <= vec3(0.5)) - ); + ); } fn soft_light(cb: vec3, cs: vec3) -> vec3 { diff --git a/piet-wgsl/src/pico_svg.rs b/piet-wgsl/src/pico_svg.rs index eebe3ec..673b195 100644 --- a/piet-wgsl/src/pico_svg.rs +++ b/piet-wgsl/src/pico_svg.rs @@ -4,8 +4,7 @@ use std::str::FromStr; use roxmltree::{Document, Node}; -use kurbo::{Affine, BezPath}; - +use piet_scene::kurbo::{Affine, BezPath}; use piet_scene::Color; pub struct PicoSvg { diff --git a/piet-wgsl/src/ramp.rs b/piet-wgsl/src/ramp.rs index a26c3d9..a97c9c7 100644 --- a/piet-wgsl/src/ramp.rs +++ b/piet-wgsl/src/ramp.rs @@ -1,4 +1,4 @@ -use piet_scene::{Color, GradientStop, GradientStops}; +use piet_scene::{Color, ColorStop, ColorStops}; use std::collections::HashMap; @@ -8,7 +8,7 @@ const RETAINED_COUNT: usize = 64; #[derive(Default)] pub struct RampCache { epoch: u64, - map: HashMap, + map: HashMap, data: Vec, } @@ -22,7 +22,7 @@ impl RampCache { } } - pub fn add(&mut self, stops: &[GradientStop]) -> u32 { + pub fn add(&mut self, stops: &[ColorStop]) -> u32 { if let Some(entry) = self.map.get_mut(stops) { entry.1 = self.epoch; entry.0 @@ -72,7 +72,7 @@ impl RampCache { } } -fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator + 'a { +fn make_ramp<'a>(stops: &'a [ColorStop]) -> impl Iterator + 'a { let mut last_u = 0.0; let mut last_c = ColorF64::from_color(stops[0].color); let mut this_u = last_u; diff --git a/piet-wgsl/src/test_scene.rs b/piet-wgsl/src/test_scene.rs index 861ac54..84195f7 100644 --- a/piet-wgsl/src/test_scene.rs +++ b/piet-wgsl/src/test_scene.rs @@ -14,10 +14,10 @@ // // Also licensed under MIT license, at your choice. -use kurbo::BezPath; +use piet_scene::kurbo::{Affine, Ellipse, PathEl, Point, Rect}; use piet_scene::{ - Affine, BlendMode, Brush, Color, Compose, ExtendMode, Fill, GradientStop, LinearGradient, Mix, - PathElement, Point, RadialGradient, Rect, Scene, SceneBuilder, SceneFragment, Stroke, + BlendMode, Brush, Color, Fill, LinearGradient, Mix, RadialGradient, Scene, SceneBuilder, + SceneFragment, Stroke, }; use crate::pico_svg::PicoSvg; @@ -29,20 +29,19 @@ pub fn gen_test_scene() -> Scene { match scene_ix { 0 => { let path = [ - PathElement::MoveTo(Point::new(100.0, 100.0)), - PathElement::LineTo(Point::new(500.0, 120.0)), - PathElement::LineTo(Point::new(300.0, 150.0)), - PathElement::LineTo(Point::new(200.0, 260.0)), - PathElement::LineTo(Point::new(150.0, 210.0)), - PathElement::Close, + PathEl::MoveTo(Point::new(100.0, 100.0)), + PathEl::LineTo(Point::new(500.0, 120.0)), + PathEl::LineTo(Point::new(300.0, 150.0)), + PathEl::LineTo(Point::new(200.0, 260.0)), + PathEl::LineTo(Point::new(150.0, 210.0)), ]; let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff)); builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path); - let transform = Affine::translate(50.0, 50.0); + let transform = Affine::translate((50.0, 50.0)); let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80)); builder.fill(Fill::NonZero, transform, &brush, None, &path); - let transform = Affine::translate(100.0, 100.0); - let style = simple_stroke(1.0); + let transform = Affine::translate((100.0, 100.0)); + let style = Stroke::new(1.0); let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00)); builder.stroke(&style, transform, &brush, None, &path); } @@ -80,18 +79,18 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { sb.fill( Fill::NonZero, Affine::IDENTITY, - &fill.color.into(), + fill.color, None, - convert_bez_path(&fill.path), + &fill.path, ); } Item::Stroke(stroke) => { sb.stroke( - &simple_stroke(stroke.width as f32), + &Stroke::new(stroke.width as f32), Affine::IDENTITY, - &stroke.color.into(), + stroke.color, None, - convert_bez_path(&stroke.path), + &stroke.path, ); } } @@ -101,25 +100,6 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { } } -fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator + 'a + Clone { - path.elements() - .iter() - .map(|el| PathElement::from_kurbo(*el)) -} - -fn simple_stroke(width: f32) -> Stroke<[f32; 0]> { - Stroke { - width, - join: piet_scene::Join::Round, - miter_limit: 1.4, - start_cap: piet_scene::Cap::Round, - end_cap: piet_scene::Cap::Round, - dash_pattern: [], - dash_offset: 0.0, - scale: true, - } -} - #[allow(unused)] pub fn render_blend_grid(sb: &mut SceneBuilder) { const BLEND_MODES: &[Mix] = &[ @@ -143,7 +123,7 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { for (ix, &blend) in BLEND_MODES.iter().enumerate() { let i = ix % 4; let j = ix / 4; - let transform = Affine::translate(i as f32 * 225., j as f32 * 225.); + let transform = Affine::translate((i as f64 * 225., j as f64 * 225.)); let square = blend_square(blend.into()); sb.append(&square, Some(transform)); } @@ -152,25 +132,10 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { #[allow(unused)] 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 stops = &[ - GradientStop { - color: Color::rgb8(0, 0, 0), - offset: 0.0, - }, - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(200.0, 0.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, transform, &linear, None, rect.elements()); - const GRADIENTS: &[(f32, f32, Color)] = &[ + let rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.)); + let linear = LinearGradient::new((0.0, 0.0), (200.0, 0.0)).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)), @@ -178,62 +143,30 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin for (x, y, c) in GRADIENTS { let mut color2 = c.clone(); color2.a = 0; - let stops = &[ - GradientStop { - color: c.clone(), - offset: 0.0, - }, - GradientStop { - color: color2, - offset: 1.0, - }, - ][..]; - let rad = Brush::RadialGradient(RadialGradient { - center0: Point::new(*x, *y), - center1: Point::new(*x, *y), - radius0: 0.0, - radius1: 100.0, - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.fill(Fill::NonZero, transform, &rad, None, rect.elements()); + let radial = RadialGradient::new((*x, *y), 100.0).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.into(), transform, rect.elements()); + sb.push_layer(Mix::Normal, transform, &rect); for (i, c) in COLORS.iter().enumerate() { - let stops = &[ - GradientStop { - color: Color::rgb8(255, 255, 255), - offset: 0.0, - }, - GradientStop { - color: c.clone(), - offset: 1.0, - }, - ][..]; - let linear = Brush::LinearGradient(LinearGradient { - start: Point::new(0.0, 0.0), - end: Point::new(0.0, 200.0), - stops: stops.into(), - extend: ExtendMode::Pad, - }); - sb.push_layer(blend, transform, rect.elements()); + let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::WHITE, *c]); + sb.push_layer(blend, transform, &rect); // squash the ellipse let a = transform - * Affine::translate(100., 100.) - * Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) - * Affine::scale(1.0, 0.357) - * Affine::translate(-100., -100.); + * 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, - make_ellipse(100., 100., 90., 90.), + &Ellipse::new((100., 100.), (90., 90.), 0.), ); sb.pop_layer(); } @@ -248,34 +181,3 @@ fn blend_square(blend: BlendMode) -> SceneFragment { sb.finish(); fragment } - -fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator + Clone { - let a = 0.551915024494; - let arx = a * rx; - let ary = a * ry; - let elements = [ - PathElement::MoveTo(Point::new(cx + rx, cy)), - PathElement::CurveTo( - Point::new(cx + rx, cy + ary), - Point::new(cx + arx, cy + ry), - Point::new(cx, cy + ry), - ), - PathElement::CurveTo( - Point::new(cx - arx, cy + ry), - Point::new(cx - rx, cy + ary), - Point::new(cx - rx, cy), - ), - PathElement::CurveTo( - Point::new(cx - rx, cy - ary), - Point::new(cx - arx, cy - ry), - Point::new(cx, cy - ry), - ), - PathElement::CurveTo( - Point::new(cx + arx, cy - ry), - Point::new(cx + rx, cy - ary), - Point::new(cx + rx, cy), - ), - PathElement::Close, - ]; - (0..elements.len()).map(move |i| elements[i]) -}