Update piet-scene to depend on peniko

This adds a new dependency on peniko, reintroduces kurbo for geometry (!), removes the now defunct types from piet-scene and updates all the test scenes to use the new types.
This commit is contained in:
Chad Brokaw 2022-11-22 14:49:51 -05:00
parent 18c0da02e5
commit f19dbdb1b5
28 changed files with 411 additions and 1413 deletions

20
Cargo.lock generated
View file

@ -503,9 +503,9 @@ checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]] [[package]]
name = "futures-intrusive" name = "futures-intrusive"
version = "0.4.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6bdbb8c5a42b2bb5ee8dd9dc2c7d73ce3e15d26dfe100fb347ffa3f58c672b" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"lock_api", "lock_api",
@ -711,8 +711,7 @@ dependencies = [
[[package]] [[package]]
name = "kurbo" name = "kurbo"
version = "0.8.3" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/linebender/kurbo#79e5787a57f12d4965fcea01ab9442ecf6795775"
checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449"
dependencies = [ dependencies = [
"arrayvec 0.7.2", "arrayvec 0.7.2",
] ]
@ -1068,6 +1067,15 @@ dependencies = [
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
[[package]]
name = "peniko"
version = "0.1.0"
source = "git+https://github.com/linebender/peniko?rev=aca2934#aca29349e7dcbd31f598172953ea88b5ddcd86df"
dependencies = [
"kurbo 0.8.3",
"smallvec",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.2.0"
@ -1093,7 +1101,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"clap 3.2.23", "clap 3.2.23",
"kurbo 0.8.3",
"ndk 0.3.0", "ndk 0.3.0",
"ndk-glue 0.3.0", "ndk-glue 0.3.0",
"ndk-sys 0.2.2", "ndk-sys 0.2.2",
@ -1162,8 +1169,8 @@ name = "piet-scene"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"kurbo 0.8.3",
"moscato", "moscato",
"peniko",
"smallvec", "smallvec",
] ]
@ -1174,7 +1181,6 @@ dependencies = [
"bytemuck", "bytemuck",
"env_logger", "env_logger",
"futures-intrusive", "futures-intrusive",
"kurbo 0.8.3",
"parking_lot", "parking_lot",
"piet-scene", "piet-scene",
"png", "png",

View file

@ -26,7 +26,8 @@
mod render; 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 render::*;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::transmute; use std::mem::transmute;
@ -145,7 +146,7 @@ pub struct PgpuPathElement {
pub points: [PgpuPoint; 3], pub points: [PgpuPoint; 3],
} }
#[derive(Clone)] #[derive(Copy, Clone)]
#[repr(C)] #[repr(C)]
pub struct PgpuPathIter { pub struct PgpuPathIter {
pub context: *mut c_void, pub context: *mut c_void,
@ -197,16 +198,16 @@ pub struct PgpuTransform {
pub dy: f32, pub dy: f32,
} }
impl From<PgpuTransform> for piet_scene::Affine { impl From<PgpuTransform> for Affine {
fn from(xform: PgpuTransform) -> Self { fn from(xform: PgpuTransform) -> Self {
Self { Affine::new([
xx: xform.xx, xform.xx as f64,
yx: xform.yx, xform.yx as f64,
xy: xform.xy, xform.xy as f64,
yy: xform.yy, xform.yy as f64,
dx: xform.dx, xform.dx as f64,
dy: xform.dy, xform.dy as f64,
} ])
} }
} }
@ -239,27 +240,26 @@ pub unsafe extern "C" fn pgpu_scene_builder_add_glyph(
} }
impl Iterator for PgpuPathIter { impl Iterator for PgpuPathIter {
type Item = PathElement; type Item = PathEl;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let mut el = PgpuPathElement { let mut el = PgpuPathElement {
verb: PgpuPathVerb::MoveTo, verb: PgpuPathVerb::MoveTo,
points: [PgpuPoint::default(); 3], 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 _) { if (self.next_element)(self.context, &mut el as _) {
let p = &el.points; let p = &el.points;
Some(match el.verb { Some(match el.verb {
PgpuPathVerb::MoveTo => PathElement::MoveTo((p[0].x, p[0].y).into()), PgpuPathVerb::MoveTo => PathEl::MoveTo(conv_pt(p[0])),
PgpuPathVerb::LineTo => PathElement::LineTo((p[0].x, p[0].y).into()), PgpuPathVerb::LineTo => PathEl::LineTo(conv_pt(p[0])),
PgpuPathVerb::QuadTo => { PgpuPathVerb::QuadTo => PathEl::QuadTo(conv_pt(p[0]), conv_pt(p[1])),
PathElement::QuadTo((p[0].x, p[0].y).into(), (p[1].x, p[1].y).into()) PgpuPathVerb::CurveTo => {
PathEl::CurveTo(conv_pt(p[0]), conv_pt(p[1]), conv_pt(p[2]))
} }
PgpuPathVerb::CurveTo => PathElement::CurveTo( PgpuPathVerb::Close => PathEl::ClosePath,
(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,
}) })
} else { } else {
None None
@ -308,12 +308,13 @@ pub unsafe extern "C" fn pgpu_scene_builder_fill_path(
} else { } else {
Some((*brush_transform).into()) Some((*brush_transform).into())
}; };
let path_els = (*path).collect::<Vec<_>>();
(*builder).builder.fill( (*builder).builder.fill(
fill, fill,
(*builder).transform, (*builder).transform,
&brush, &brush,
brush_transform, brush_transform,
(*path).clone(), &&path_els[..],
); );
} }
@ -445,13 +446,14 @@ pub unsafe extern "C" fn pgpu_glyph_bbox(
glyph: *const PgpuGlyph, glyph: *const PgpuGlyph,
transform: &[f32; 6], transform: &[f32; 6],
) -> PgpuRect { ) -> 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)); let rect = (*glyph).bbox(Some(transform));
PgpuRect { PgpuRect {
x0: rect.min.x, x0: rect.min_x() as f32,
y0: rect.min.y, y0: rect.min_y() as f32,
x1: rect.max.x, x1: rect.max_x() as f32,
y1: rect.max.y, y1: rect.max_y() as f32,
} }
} }

View file

@ -18,7 +18,8 @@ use piet_gpu::{PixelFormat, RenderConfig};
use piet_gpu_hal::{QueryPool, Session}; use piet_gpu_hal::{QueryPool, Session};
use piet_scene::glyph::pinot::{types::Tag, FontDataRef}; use piet_scene::glyph::pinot::{types::Tag, FontDataRef};
use piet_scene::glyph::{GlyphContext, GlyphProvider}; 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. /// State and resources for rendering a scene.
pub struct PgpuRenderer { pub struct PgpuRenderer {
@ -141,7 +142,7 @@ pub struct PgpuSceneBuilder<'a> {
} }
impl<'a> 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)); self.builder.append(&glyph.fragment, Some(*transform));
} }
@ -214,15 +215,25 @@ pub struct PgpuGlyph {
impl PgpuGlyph { impl PgpuGlyph {
pub fn bbox(&self, transform: Option<Affine>) -> Rect { pub fn bbox(&self, transform: Option<Affine>) -> Rect {
if let Some(transform) = &transform { let points = self.fragment.points();
Rect::from_points( if points.is_empty() {
self.fragment return Rect::default();
.points() }
let mut points = points
.iter() .iter()
.map(|p| p.transform(transform)), .map(|pt| Point::new(pt[0] as f64, pt[1] as f64));
) if let Some(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 { } 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
} }
} }
} }

View file

@ -28,7 +28,6 @@ path = "../piet-gpu-types"
[dependencies.piet-scene] [dependencies.piet-scene]
path = "../piet-scene" path = "../piet-scene"
features = ["kurbo"]
[dependencies] [dependencies]
png = "0.17.6" png = "0.17.6"
@ -38,7 +37,6 @@ winit = {version = "0.27.3", default-features = false, features = ["x11", "wayl
raw-window-handle = "0.5" raw-window-handle = "0.5"
clap = "3.2.22" clap = "3.2.22"
bytemuck = { version = "1.7.2", features = ["derive"] } bytemuck = { version = "1.7.2", features = ["derive"] }
kurbo = "0.8.3"
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
ndk = "0.3" ndk = "0.3"

View file

@ -190,7 +190,7 @@ fn render_info(simple_text: &mut SimpleText, sb: &mut SceneBuilder, info: &str)
None, None,
40.0, 40.0,
None, None,
piet_scene::Affine::translate(110.0, 50.0), piet_scene::kurbo::Affine::translate((110.0, 50.0)),
info, info,
); );
} }

View file

@ -4,8 +4,7 @@ use std::str::FromStr;
use roxmltree::{Document, Node}; use roxmltree::{Document, Node};
use kurbo::{Affine, BezPath}; use piet_scene::kurbo::{Affine, BezPath};
use piet_scene::Color; use piet_scene::Color;
pub struct PicoSvg { pub struct PicoSvg {

View file

@ -1,4 +1,4 @@
use piet_scene::{Color, GradientStop, GradientStops}; use piet_scene::{Color, ColorStop, ColorStops};
use std::collections::HashMap; use std::collections::HashMap;
@ -8,7 +8,7 @@ const RETAINED_COUNT: usize = 64;
#[derive(Default)] #[derive(Default)]
pub struct RampCache { pub struct RampCache {
epoch: u64, epoch: u64,
map: HashMap<GradientStops, (u32, u64)>, map: HashMap<ColorStops, (u32, u64)>,
data: Vec<u32>, data: Vec<u32>,
} }
@ -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) { if let Some(entry) = self.map.get_mut(stops) {
entry.1 = self.epoch; entry.1 = self.epoch;
entry.0 entry.0
@ -64,7 +64,7 @@ impl RampCache {
} }
} }
fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator<Item = u32> + 'a { fn make_ramp<'a>(stops: &'a [ColorStop]) -> impl Iterator<Item = u32> + 'a {
let mut last_u = 0.0; let mut last_u = 0.0;
let mut last_c = ColorF64::from_color(stops[0].color); let mut last_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u; let mut this_u = last_u;

View file

@ -1,47 +1,47 @@
use crate::PicoSvg; use crate::PicoSvg;
use kurbo::BezPath; use piet_scene::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect};
use piet_scene::*; use piet_scene::*;
use crate::SimpleText; use crate::SimpleText;
pub fn render_funky_paths(sb: &mut SceneBuilder) { pub fn render_funky_paths(sb: &mut SceneBuilder) {
use PathElement::*; use PathEl::*;
let missing_movetos = [ let missing_movetos = &[
LineTo((100.0, 100.0).into()), LineTo((100.0, 100.0).into()),
LineTo((100.0, 200.0).into()), LineTo((100.0, 200.0).into()),
Close, ClosePath,
LineTo((0.0, 400.0).into()), LineTo((0.0, 400.0).into()),
LineTo((100.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 only_movetos = &[MoveTo((0.0, 0.0).into()), MoveTo((100.0, 100.0).into())][..];
let empty: [PathElement; 0] = []; let empty: &[PathEl] = &[];
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::translate(100.0, 100.0), Affine::translate((100.0, 100.0)),
&Color::rgb8(0, 0, 255).into(), &Color::rgb8(0, 0, 255).into(),
None, None,
missing_movetos, &missing_movetos,
); );
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Color::rgb8(0, 0, 255).into(), &Color::rgb8(0, 0, 255).into(),
None, None,
empty, &empty,
); );
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Color::rgb8(0, 0, 255).into(), &Color::rgb8(0, 0, 255).into(),
None, None,
only_movetos, &only_movetos,
); );
sb.stroke( sb.stroke(
&simple_stroke(8.0), &Stroke::new(8.0),
Affine::translate(100.0, 100.0), Affine::translate((100.0, 100.0)),
&Color::rgb8(0, 255, 255).into(), &Color::rgb8(0, 255, 255).into(),
None, None,
missing_movetos, &missing_movetos,
); );
} }
@ -60,16 +60,16 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
Affine::IDENTITY, Affine::IDENTITY,
&fill.color.into(), &fill.color.into(),
None, None,
convert_bez_path(&fill.path), &fill.path,
); );
} }
Item::Stroke(stroke) => { Item::Stroke(stroke) => {
sb.stroke( sb.stroke(
&simple_stroke(stroke.width as f32), &Stroke::new(stroke.width as f32),
Affine::IDENTITY, Affine::IDENTITY,
&stroke.color.into(), &stroke.color.into(),
None, None,
convert_bez_path(&stroke.path), &stroke.path,
); );
} }
} }
@ -101,63 +101,60 @@ pub fn render_scene(sb: &mut SceneBuilder) {
#[allow(unused)] #[allow(unused)]
fn render_cardioid(sb: &mut SceneBuilder) { fn render_cardioid(sb: &mut SceneBuilder) {
let n = 601; 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 center = Point::new(1024.0, 768.0);
let r = 750.0; let r = 750.0;
let mut path = vec![]; let mut path = vec![];
for i in 1..n { for i in 1..n {
let mut p0 = center; let mut p0 = center;
let a0 = i as f32 * dth; let a0 = i as f64 * dth;
p0.x += a0.cos() * r; p0.x += a0.cos() * r;
p0.y += a0.sin() * r; p0.y += a0.sin() * r;
let mut p1 = center; 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.x += a1.cos() * r;
p1.y += a1.sin() * r; p1.y += a1.sin() * r;
path.push(PathElement::MoveTo(p0)); path.push(PathEl::MoveTo(p0));
path.push(PathElement::LineTo(p1)); path.push(PathEl::LineTo(p1));
} }
sb.stroke( sb.stroke(
&simple_stroke(2.0), &Stroke::new(2.0),
Affine::IDENTITY, Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)), &Brush::Solid(Color::rgb8(0, 0, 0)),
None, None,
&path, &&path[..],
); );
} }
#[allow(unused)] #[allow(unused)]
fn render_clip_test(sb: &mut SceneBuilder) { fn render_clip_test(sb: &mut SceneBuilder) {
const N: usize = 16; const N: usize = 16;
const X0: f32 = 50.0; const X0: f64 = 50.0;
const Y0: f32 = 450.0; const Y0: f64 = 450.0;
// Note: if it gets much larger, it will exceed the 1MB scratch buffer. // Note: if it gets much larger, it will exceed the 1MB scratch buffer.
// But this is a pretty demanding test. // But this is a pretty demanding test.
const X1: f32 = 550.0; const X1: f64 = 550.0;
const Y1: f32 = 950.0; const Y1: f64 = 950.0;
let step = 1.0 / ((N + 1) as f32); let step = 1.0 / ((N + 1) as f64);
for i in 0..N { for i in 0..N {
let t = ((i + 1) as f32) * step; let t = ((i + 1) as f64) * step;
let path = &[ let path = &[
PathElement::MoveTo((X0, Y0).into()), PathEl::MoveTo((X0, Y0).into()),
PathElement::LineTo((X1, Y0).into()), PathEl::LineTo((X1, Y0).into()),
PathElement::LineTo((X1, Y0 + t * (Y1 - Y0)).into()), PathEl::LineTo((X1, Y0 + t * (Y1 - Y0)).into()),
PathElement::LineTo((X1 + t * (X0 - X1), Y1).into()), PathEl::LineTo((X1 + t * (X0 - X1), Y1).into()),
PathElement::LineTo((X0, Y1).into()), PathEl::LineTo((X0, Y1).into()),
PathElement::Close, PathEl::ClosePath,
]; ][..];
sb.push_layer(Mix::Clip.into(), Affine::IDENTITY, path); sb.push_layer(Mix::Clip.into(), Affine::IDENTITY, &path);
} }
let rect = Rect { let rect = Rect::new(X0, Y0, X1, Y1);
min: Point::new(X0, Y0),
max: Point::new(X1, Y1),
};
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)), &Brush::Solid(Color::rgb8(0, 0, 0)),
None, None,
rect.elements(), &rect,
); );
for _ in 0..N { for _ in 0..N {
sb.pop_layer(); sb.pop_layer();
@ -172,26 +169,26 @@ fn render_alpha_test(sb: &mut SceneBuilder) {
Affine::IDENTITY, Affine::IDENTITY,
&Color::rgb8(255, 0, 0).into(), &Color::rgb8(255, 0, 0).into(),
None, None,
make_diamond(1024.0, 100.0), &&make_diamond(1024.0, 100.0)[..],
); );
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Color::rgba8(0, 255, 0, 0x80).into(), &Color::rgba8(0, 255, 0, 0x80).into(),
None, None,
make_diamond(1024.0, 125.0), &&make_diamond(1024.0, 125.0)[..],
); );
sb.push_layer( sb.push_layer(
Mix::Clip.into(), Mix::Clip.into(),
Affine::IDENTITY, Affine::IDENTITY,
make_diamond(1024.0, 150.0), &&make_diamond(1024.0, 150.0)[..],
); );
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Color::rgba8(0, 0, 255, 0x80).into(), &Color::rgba8(0, 0, 255, 0x80).into(),
None, None,
make_diamond(1024.0, 175.0), &&make_diamond(1024.0, 175.0)[..],
); );
sb.pop_layer(); sb.pop_layer();
} }
@ -219,7 +216,7 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) {
for (ix, &blend) in BLEND_MODES.iter().enumerate() { for (ix, &blend) in BLEND_MODES.iter().enumerate() {
let i = ix % 4; let i = ix % 4;
let j = 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()); let square = blend_square(blend.into());
sb.append(&square, Some(transform)); sb.append(&square, Some(transform));
} }
@ -228,25 +225,10 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) {
#[allow(unused)] #[allow(unused)]
fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) { 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 // 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 rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.));
let stops = &[ let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::BLACK, Color::WHITE]);
GradientStop { sb.fill(Fill::NonZero, transform, &linear.into(), None, &rect);
color: Color::rgb8(0, 0, 0), const GRADIENTS: &[(f64, f64, Color)] = &[
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)] = &[
(150., 0., Color::rgb8(255, 240, 64)), (150., 0., Color::rgb8(255, 240, 64)),
(175., 100., Color::rgb8(255, 96, 240)), (175., 100., Color::rgb8(255, 96, 240)),
(125., 200., Color::rgb8(64, 192, 255)), (125., 200., Color::rgb8(64, 192, 255)),
@ -254,62 +236,40 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
for (x, y, c) in GRADIENTS { for (x, y, c) in GRADIENTS {
let mut color2 = c.clone(); let mut color2 = c.clone();
color2.a = 0; color2.a = 0;
let stops = &[ let radial = RadialGradient::new((*x, *y), 100.0).stops([c.clone(), color2]);
GradientStop { sb.fill(Fill::NonZero, transform, &radial.into(), None, &rect);
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());
} }
const COLORS: &[Color] = &[ const COLORS: &[Color] = &[
Color::rgb8(255, 0, 0), Color::rgb8(255, 0, 0),
Color::rgb8(0, 255, 0), Color::rgb8(0, 255, 0),
Color::rgb8(0, 0, 255), Color::rgb8(0, 0, 255),
]; ];
sb.push_layer(Mix::Normal.into(), transform, rect.elements()); sb.push_layer(Mix::Normal.into(), transform, &rect);
for (i, c) in COLORS.iter().enumerate() { for (i, c) in COLORS.iter().enumerate() {
let stops = &[ // let stops = &[
GradientStop { // GradientStop {
color: Color::rgb8(255, 255, 255), // color: Color::rgb8(255, 255, 255),
offset: 0.0, // offset: 0.0,
}, // },
GradientStop { // GradientStop {
color: c.clone(), // color: c.clone(),
offset: 1.0, // offset: 1.0,
}, // },
][..]; // ][..];
let linear = Brush::LinearGradient(LinearGradient { let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::WHITE, c.clone()]);
start: Point::new(0.0, 0.0), sb.push_layer(blend, transform, &rect);
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.push_layer(blend, transform, rect.elements());
// squash the ellipse // squash the ellipse
let a = transform let a = transform
* Affine::translate(100., 100.) * Affine::translate((100., 100.))
* Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) * Affine::rotate(std::f64::consts::FRAC_PI_3 * (i * 2 + 1) as f64)
* Affine::scale(1.0, 0.357) * Affine::scale_non_uniform(1.0, 0.357)
* Affine::translate(-100., -100.); * Affine::translate((-100., -100.));
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
a, a,
&linear, &linear.into(),
None, None,
make_ellipse(100., 100., 90., 90.), &Ellipse::new((100., 100.), (90., 90.), 0.),
); );
sb.pop_layer(); sb.pop_layer();
} }
@ -332,7 +292,7 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize)
Affine::IDENTITY, Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 128, 128)), &Brush::Solid(Color::rgb8(128, 128, 128)),
None, 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 text_size = 60.0 + 40.0 * (0.01 * i as f32).sin();
let s = "\u{1f600}hello piet-gpu text!"; let s = "\u{1f600}hello piet-gpu text!";
@ -341,7 +301,7 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize)
None, None,
text_size, text_size,
None, None,
Affine::translate(110.0, 600.0), Affine::translate((110.0, 600.0)),
s, s,
); );
text.add( text.add(
@ -349,121 +309,56 @@ pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize)
None, None,
text_size, text_size,
None, None,
Affine::translate(110.0, 700.0), Affine::translate((110.0, 700.0)),
s, 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 center = Point::new(500.0, 500.0);
let mut p1 = center; let mut p1 = center;
p1.x += 400.0 * th.cos(); p1.x += 400.0 * th.cos();
p1.y += 400.0 * th.sin(); p1.y += 400.0 * th.sin();
sb.stroke( sb.stroke(
&simple_stroke(5.0), &Stroke::new(5.0),
Affine::IDENTITY, Affine::IDENTITY,
&Brush::Solid(Color::rgb8(128, 0, 0)), &Brush::Solid(Color::rgb8(128, 0, 0)),
None, None,
&[PathElement::MoveTo(center), PathElement::LineTo(p1)], &&[PathEl::MoveTo(center), PathEl::LineTo(p1)][..],
); );
} }
#[allow(unused)] #[allow(unused)]
pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) { pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) {
let th = (std::f32::consts::PI / 180.0) * (i as f32); let th = (std::f64::consts::PI / 180.0) * (i as f64);
let stops = &[ let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0))
GradientStop { .stops([Color::RED, Color::GREEN, Color::BLUE])
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(); .into();
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::translate(200.0, 200.0), Affine::translate((200.0, 200.0)),
&linear, &linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)), Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), &Rect::from_origin_size(Point::default(), (400.0, 200.0)),
); );
sb.stroke( sb.stroke(
&simple_stroke(40.0), &Stroke::new(40.0),
Affine::translate(800.0, 200.0), Affine::translate((800.0, 200.0)),
&linear, &linear,
Some(Affine::rotate(th).around_center(200.0, 100.0)), Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))),
Rect::from_origin_size(Point::default(), 400.0, 200.0).elements(), &Rect::from_origin_size(Point::default(), (400.0, 200.0)),
); );
} }
fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement> + 'a + Clone { fn around_center(xform: Affine, center: Point) -> Affine {
path.elements() Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2())
.iter()
.map(|el| PathElement::from_kurbo(*el))
} }
fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = PathElement> + Clone { fn make_diamond(cx: f64, cy: f64) -> [PathEl; 5] {
let a = 0.551915024494; const SIZE: f64 = 50.0;
let arx = a * rx; [
let ary = a * ry; PathEl::MoveTo(Point::new(cx, cy - SIZE)),
let elements = [ PathEl::LineTo(Point::new(cx + SIZE, cy)),
PathElement::MoveTo(Point::new(cx + rx, cy)), PathEl::LineTo(Point::new(cx, cy + SIZE)),
PathElement::CurveTo( PathEl::LineTo(Point::new(cx - SIZE, cy)),
Point::new(cx + rx, cy + ary), PathEl::ClosePath,
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<Item = PathElement> + 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,
}
} }

View file

@ -15,7 +15,8 @@
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use piet_scene::glyph::{pinot, pinot::TableProvider, GlyphContext}; 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; pub use pinot::FontRef;
@ -49,8 +50,8 @@ impl SimpleText {
}); });
if let Some(cmap) = font.cmap() { if let Some(cmap) = font.cmap() {
if let Some(hmtx) = font.hmtx() { if let Some(hmtx) = font.hmtx() {
let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f32; let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f64;
let scale = size / upem; let scale = size as f64 / upem;
let vars: [(pinot::types::Tag, f32); 0] = []; let vars: [(pinot::types::Tag, f32); 0] = [];
let mut provider = self.gcx.new_provider(font, None, size, false, vars); let mut provider = self.gcx.new_provider(font, None, size, false, vars);
let hmetrics = hmtx.hmetrics(); let hmetrics = hmtx.hmetrics();
@ -58,17 +59,18 @@ impl SimpleText {
.get(hmetrics.len().saturating_sub(1)) .get(hmetrics.len().saturating_sub(1))
.map(|h| h.advance_width) .map(|h| h.advance_width)
.unwrap_or(0); .unwrap_or(0);
let mut pen_x = 0f32; let mut pen_x = 0f64;
for ch in text.chars() { for ch in text.chars() {
let gid = cmap.map(ch as u32).unwrap_or(0); let gid = cmap.map(ch as u32).unwrap_or(0);
let advance = hmetrics let advance = hmetrics
.get(gid as usize) .get(gid as usize)
.map(|h| h.advance_width) .map(|h| h.advance_width)
.unwrap_or(default_advance) as f32 .unwrap_or(default_advance) as f64
* scale; * scale;
if let Some(glyph) = provider.get(gid, brush) { if let Some(glyph) = provider.get(gid, brush) {
let xform = let xform = transform
transform * Affine::translate(pen_x, 0.0) * Affine::scale(1.0, -1.0); * Affine::translate((pen_x, 0.0))
* Affine::scale_non_uniform(1.0, -1.0);
builder.append(&glyph, Some(xform)); builder.append(&glyph, Some(xform));
} }
pen_x += advance; pen_x += advance;

View file

@ -8,4 +8,4 @@ edition = "2021"
bytemuck = { version = "1.7.2", features = ["derive"] } bytemuck = { version = "1.7.2", features = ["derive"] }
smallvec = "1.8.0" smallvec = "1.8.0"
moscato = { git = "https://github.com/dfrg/pinot" } moscato = { git = "https://github.com/dfrg/pinot" }
kurbo = { version = "0.8.3", optional = true } peniko = { git = "https://github.com/linebender/peniko" }

View file

@ -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
}
}

View file

@ -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<H: Hasher>(&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,
}

View file

@ -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<Inner>);
#[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<Arc<[u8]>>,
) -> Result<Self, ImageDataSizeError> {
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;

View file

@ -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<Color> for Brush {
fn from(c: Color) -> Self {
Self::Solid(c)
}
}
impl From<LinearGradient> for Brush {
fn from(g: LinearGradient) -> Self {
Self::LinearGradient(g)
}
}
impl From<RadialGradient> for Brush {
fn from(g: RadialGradient) -> Self {
Self::RadialGradient(g)
}
}

28
piet-scene/src/conv.rs Normal file
View file

@ -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]
}

View file

@ -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<H: Hasher>(&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<I>(points: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<Point>,
{
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)])
}
}

View file

@ -18,10 +18,9 @@
pub use moscato::pinot; pub use moscato::pinot;
use crate::brush::{Brush, Color}; use crate::scene::{SceneBuilder, SceneFragment};
use crate::geometry::Affine; use peniko::kurbo::{Affine, Rect};
use crate::path::PathElement; use peniko::{Brush, Color, Fill};
use crate::scene::{Fill, SceneBuilder, SceneFragment};
use moscato::{Context, Scaler}; use moscato::{Context, Scaler};
use pinot::{types::Tag, FontRef}; use pinot::{types::Tag, FontRef};
@ -93,7 +92,7 @@ impl<'a> GlyphProvider<'a> {
Affine::IDENTITY, Affine::IDENTITY,
brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))), brush.unwrap_or(&Brush::Solid(Color::rgb8(255, 255, 255))),
None, None,
convert_path(path.elements()), &convert_path(path.elements()),
); );
builder.finish(); builder.finish();
Some(fragment) Some(fragment)
@ -102,7 +101,6 @@ impl<'a> GlyphProvider<'a> {
/// Returns a scene fragment containing the commands and resources to /// Returns a scene fragment containing the commands and resources to
/// render the specified color glyph. /// render the specified color glyph.
pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<SceneFragment> { pub fn get_color(&mut self, palette_index: u16, gid: u16) -> Option<SceneFragment> {
use crate::geometry::*;
use moscato::Command; use moscato::Command;
let glyph = self.scaler.color_glyph(palette_index, gid)?; let glyph = self.scaler.color_glyph(palette_index, gid)?;
let mut fragment = SceneFragment::default(); let mut fragment = SceneFragment::default();
@ -127,37 +125,37 @@ impl<'a> GlyphProvider<'a> {
builder.push_layer( builder.push_layer(
Default::default(), Default::default(),
Affine::IDENTITY, Affine::IDENTITY,
convert_transformed_path(path.elements(), xform), &convert_transformed_path(path.elements(), xform),
); );
} else { } else {
builder.push_layer( builder.push_layer(
Default::default(), Default::default(),
Affine::IDENTITY, Affine::IDENTITY,
convert_path(path.elements()), &convert_path(path.elements()),
); );
} }
} }
Command::PopClip => builder.pop_layer(), Command::PopClip => builder.pop_layer(),
Command::PushLayer(bounds) => { Command::PushLayer(bounds) => {
let mut rect = Rect { let mut min = convert_point(bounds.min);
min: Point::new(bounds.min.x, bounds.min.y), let mut max = convert_point(bounds.max);
max: Point::new(bounds.max.x, bounds.max.y),
};
if let Some(xform) = xform_stack.last() { 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(Default::default(), Affine::IDENTITY, &rect);
} }
Command::PopLayer => builder.pop_layer(), Command::PopLayer => builder.pop_layer(),
Command::BeginBlend(bounds, mode) => { Command::BeginBlend(bounds, mode) => {
let mut rect = Rect { let mut min = convert_point(bounds.min);
min: Point::new(bounds.min.x, bounds.min.y), let mut max = convert_point(bounds.max);
max: Point::new(bounds.max.x, bounds.max.y),
};
if let Some(xform) = xform_stack.last() { 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::EndBlend => builder.pop_layer(),
Command::SimpleFill(path_index, brush, brush_xform) => { Command::SimpleFill(path_index, brush, brush_xform) => {
@ -170,7 +168,7 @@ impl<'a> GlyphProvider<'a> {
Affine::IDENTITY, Affine::IDENTITY,
&brush, &brush,
brush_xform.map(|x| x * *xform), brush_xform.map(|x| x * *xform),
convert_transformed_path(path.elements(), xform), &convert_transformed_path(path.elements(), xform),
); );
} else { } else {
builder.fill( builder.fill(
@ -178,7 +176,7 @@ impl<'a> GlyphProvider<'a> {
Affine::IDENTITY, Affine::IDENTITY,
&brush, &brush,
brush_xform, brush_xform,
convert_path(path.elements()), &convert_path(path.elements()),
); );
} }
} }
@ -193,54 +191,28 @@ impl<'a> GlyphProvider<'a> {
} }
} }
fn convert_path( fn convert_path(path: impl Iterator<Item = moscato::Element> + Clone) -> peniko::kurbo::BezPath {
path: impl Iterator<Item = moscato::Element> + Clone, let mut result = peniko::kurbo::BezPath::new();
) -> impl Iterator<Item = PathElement> + Clone { for el in path {
use crate::geometry::Point; result.push(convert_path_el(&el));
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( result
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_transformed_path( fn convert_transformed_path(
path: impl Iterator<Item = moscato::Element> + Clone, path: impl Iterator<Item = moscato::Element> + Clone,
xform: &Affine, xform: &Affine,
) -> impl Iterator<Item = PathElement> + Clone { ) -> peniko::kurbo::BezPath {
use crate::geometry::Point; let mut result = peniko::kurbo::BezPath::new();
let xform = *xform; for el in path {
path.map(move |el| match el { result.push(*xform * convert_path_el(&el));
moscato::Element::MoveTo(p0) => {
PathElement::MoveTo(Point::new(p0.x, p0.y).transform(&xform))
} }
moscato::Element::LineTo(p0) => { result
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,
})
} }
fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode { fn convert_blend(mode: moscato::CompositeMode) -> peniko::BlendMode {
use crate::scene::{BlendMode, Compose, Mix};
use moscato::CompositeMode; use moscato::CompositeMode;
use peniko::{BlendMode, Compose, Mix};
let mut mix = Mix::Normal; let mut mix = Mix::Normal;
let mut compose = Compose::SrcOver; let mut compose = Compose::SrcOver;
match mode { match mode {
@ -276,20 +248,23 @@ fn convert_blend(mode: moscato::CompositeMode) -> crate::scene::BlendMode {
BlendMode { mix, compose } BlendMode { mix, compose }
} }
fn convert_transform(xform: &moscato::Transform) -> crate::geometry::Affine { fn convert_transform(xform: &moscato::Transform) -> peniko::kurbo::Affine {
crate::geometry::Affine { peniko::kurbo::Affine::new([
xx: xform.xx, xform.xx as f64,
yx: xform.yx, xform.yx as f64,
xy: xform.xy, xform.xy as f64,
yy: xform.yy, xform.yy as f64,
dx: xform.dx, xform.dx as f64,
dy: xform.dy, xform.dy as f64,
} ])
} }
fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush { fn convert_point(point: moscato::Point) -> peniko::kurbo::Point {
use crate::brush::*; peniko::kurbo::Point::new(point.x as f64, point.y as f64)
use crate::geometry::*; }
fn convert_brush(brush: &moscato::Brush) -> peniko::Brush {
use peniko::{LinearGradient, RadialGradient};
match brush { match brush {
moscato::Brush::Solid(color) => Brush::Solid(Color { moscato::Brush::Solid(color) => Brush::Solid(Color {
r: color.r, r: color.r,
@ -298,43 +273,58 @@ fn convert_brush(brush: &moscato::Brush) -> crate::brush::Brush {
a: color.a, a: color.a,
}), }),
moscato::Brush::LinearGradient(grad) => Brush::LinearGradient(LinearGradient { moscato::Brush::LinearGradient(grad) => Brush::LinearGradient(LinearGradient {
start: Point::new(grad.start.x, grad.start.y), start: convert_point(grad.start),
end: Point::new(grad.end.x, grad.end.y), end: convert_point(grad.end),
stops: convert_stops(&grad.stops), stops: convert_stops(&grad.stops),
extend: convert_extend(grad.extend), extend: convert_extend(grad.extend),
}), }),
moscato::Brush::RadialGradient(grad) => Brush::RadialGradient(RadialGradient { moscato::Brush::RadialGradient(grad) => Brush::RadialGradient(RadialGradient {
center0: Point::new(grad.center0.x, grad.center0.y), start_center: convert_point(grad.center0),
center1: Point::new(grad.center1.x, grad.center1.y), end_center: convert_point(grad.center1),
radius0: grad.radius0, start_radius: grad.radius0,
radius1: grad.radius1, end_radius: grad.radius1,
stops: convert_stops(&grad.stops), stops: convert_stops(&grad.stops),
extend: convert_extend(grad.extend), extend: convert_extend(grad.extend),
}), }),
} }
} }
fn convert_stops(stops: &[moscato::ColorStop]) -> crate::brush::GradientStops { fn convert_stops(stops: &[moscato::ColorStop]) -> peniko::ColorStops {
use crate::brush::GradientStop;
stops stops
.iter() .iter()
.map(|stop| GradientStop { .map(|stop| {
offset: stop.offset, (
color: Color { stop.offset,
Color {
r: stop.color.r, r: stop.color.r,
g: stop.color.g, g: stop.color.g,
b: stop.color.b, b: stop.color.b,
a: stop.color.a, a: stop.color.a,
}, },
)
.into()
}) })
.collect() .collect()
} }
fn convert_extend(extend: moscato::ExtendMode) -> crate::brush::ExtendMode { fn convert_extend(extend: moscato::ExtendMode) -> peniko::Extend {
use crate::brush::ExtendMode::*; use peniko::Extend::*;
match extend { match extend {
moscato::ExtendMode::Pad => Pad, moscato::ExtendMode::Pad => Pad,
moscato::ExtendMode::Repeat => Repeat, moscato::ExtendMode::Repeat => Repeat,
moscato::ExtendMode::Reflect => Reflect, 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,
}
}

View file

@ -14,117 +14,12 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
mod brush; pub use peniko;
mod geometry;
mod path; mod conv;
mod resource;
mod scene; mod scene;
pub mod glyph; pub mod glyph;
pub use brush::*; pub use peniko::*;
pub use geometry::*;
pub use path::*;
pub use resource::*;
pub use scene::*; 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<Point> 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<Affine> 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<Rect> 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<PathElement> 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,
}
}
}
}

View file

@ -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<Item = PathElement> + 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])
}
}

View file

@ -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<Mix> for BlendMode {
fn from(mix: Mix) -> Self {
Self {
mix,
compose: Compose::SrcOver,
}
}
}
impl From<Compose> for BlendMode {
fn from(compose: Compose) -> Self {
Self {
mix: Mix::Normal,
compose,
}
}
}

View file

@ -14,11 +14,11 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use super::style::{Fill, Stroke}; use super::{conv, Scene, SceneData, SceneFragment};
use super::{Affine, BlendMode, PathElement, Scene, SceneData, SceneFragment}; use crate::ResourcePatch;
use crate::{brush::*, ResourcePatch};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use core::borrow::Borrow; use peniko::kurbo::{Affine, PathEl, Shape};
use peniko::{BlendMode, Brush, ColorStop, Fill, Stroke};
use smallvec::SmallVec; use smallvec::SmallVec;
/// Builder for constructing a scene or scene fragment. /// Builder for constructing a scene or scene fragment.
@ -51,44 +51,33 @@ impl<'a> SceneBuilder<'a> {
/// Pushes a new layer bound by the specifed shape and composed with /// Pushes a new layer bound by the specifed shape and composed with
/// previous layers using the specified blend mode. /// previous layers using the specified blend mode.
pub fn push_layer<'s, E>(&mut self, blend: BlendMode, transform: Affine, elements: E) pub fn push_layer(&mut self, blend: BlendMode, transform: Affine, shape: &impl Shape) {
where
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
self.maybe_encode_transform(transform); self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); self.encode_path(shape, true);
self.encode_path(elements, true); self.begin_clip(blend);
self.begin_clip(Some(blend));
self.layers.push(blend); self.layers.push(blend);
} }
/// Pops the current layer. /// Pops the current layer.
pub fn pop_layer(&mut self) { pub fn pop_layer(&mut self) {
if let Some(layer) = self.layers.pop() { if let Some(layer) = self.layers.pop() {
self.end_clip(Some(layer)); self.end_clip(layer);
} }
} }
/// Fills a shape using the specified style and brush. /// Fills a shape using the specified style and brush.
pub fn fill<'s, E>( pub fn fill(
&mut self, &mut self,
_style: Fill, _style: Fill,
transform: Affine, transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, shape: &impl Shape,
) where ) {
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
self.maybe_encode_transform(transform); self.maybe_encode_transform(transform);
self.linewidth(-1.0); self.linewidth(-1.0);
let elements = elements.into_iter(); if self.encode_path(shape, true) {
if self.encode_path(elements, true) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
self.encode_transform(transform * brush_transform); self.encode_transform(transform * brush_transform);
self.swap_last_tags(); self.swap_last_tags();
@ -100,23 +89,17 @@ impl<'a> SceneBuilder<'a> {
} }
/// Strokes a shape using the specified style and brush. /// Strokes a shape using the specified style and brush.
pub fn stroke<'s, D, E>( pub fn stroke(
&mut self, &mut self,
style: &Stroke<D>, style: &Stroke,
transform: Affine, transform: Affine,
brush: &Brush, brush: &Brush,
brush_transform: Option<Affine>, brush_transform: Option<Affine>,
elements: E, shape: &impl Shape,
) where ) {
D: Borrow<[f32]>,
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
self.maybe_encode_transform(transform); self.maybe_encode_transform(transform);
self.linewidth(style.width); self.linewidth(style.width);
let elements = elements.into_iter(); if self.encode_path(shape, false) {
if self.encode_path(elements, false) {
if let Some(brush_transform) = brush_transform { if let Some(brush_transform) = brush_transform {
self.encode_transform(transform * brush_transform); self.encode_transform(transform * brush_transform);
self.swap_last_tags(); self.swap_last_tags();
@ -135,29 +118,34 @@ impl<'a> SceneBuilder<'a> {
/// Completes construction and finalizes the underlying scene. /// Completes construction and finalizes the underlying scene.
pub fn finish(mut self) { pub fn finish(mut self) {
while let Some(layer) = self.layers.pop() { while let Some(layer) = self.layers.pop() {
self.end_clip(Some(layer)); self.end_clip(layer);
} }
} }
} }
impl<'a> SceneBuilder<'a> { impl<'a> SceneBuilder<'a> {
fn encode_path<E>(&mut self, elements: E, is_fill: bool) -> bool fn encode_path(&mut self, shape: &impl Shape, is_fill: bool) -> bool {
where
E: Iterator,
E::Item: Borrow<PathElement>,
{
let mut b = PathBuilder::new( let mut b = PathBuilder::new(
&mut self.scene.tag_stream, &mut self.scene.tag_stream,
&mut self.scene.pathseg_stream, &mut self.scene.pathseg_stream,
is_fill, is_fill,
); );
for el in elements { for el in shape.path_elements(0.1) {
match el.borrow() { match el {
PathElement::MoveTo(p0) => b.move_to(p0.x, p0.y), PathEl::MoveTo(p0) => b.move_to(p0.x as f32, p0.y as f32),
PathElement::LineTo(p0) => b.line_to(p0.x, p0.y), PathEl::LineTo(p0) => b.line_to(p0.x as f32, p0.y as f32),
PathElement::QuadTo(p0, p1) => b.quad_to(p0.x, p0.y, p1.x, p1.y), PathEl::QuadTo(p0, p1) => {
PathElement::CurveTo(p0, p1, p2) => b.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y), b.quad_to(p0.x as f32, p0.y as f32, p1.x as f32, p1.y as f32)
PathElement::Close => b.close_path(), }
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(); b.finish();
@ -171,14 +159,16 @@ impl<'a> SceneBuilder<'a> {
} }
fn maybe_encode_transform(&mut self, transform: Affine) { 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); self.encode_transform(transform);
} }
} }
fn encode_transform(&mut self, transform: Affine) { fn encode_transform(&mut self, transform: Affine) {
self.scene.tag_stream.push(0x20); 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 // Swap the last two tags in the tag stream; used for transformed
@ -212,8 +202,8 @@ impl<'a> SceneBuilder<'a> {
.drawdata_stream .drawdata_stream
.extend(bytemuck::bytes_of(&FillLinGradient { .extend(bytemuck::bytes_of(&FillLinGradient {
index, index,
p0: [gradient.start.x, gradient.start.y], p0: conv::point_to_f32(gradient.start),
p1: [gradient.end.x, gradient.end.y], p1: conv::point_to_f32(gradient.end),
})); }));
} }
Brush::RadialGradient(gradient) => { Brush::RadialGradient(gradient) => {
@ -223,18 +213,17 @@ impl<'a> SceneBuilder<'a> {
.drawdata_stream .drawdata_stream
.extend(bytemuck::bytes_of(&FillRadGradient { .extend(bytemuck::bytes_of(&FillRadGradient {
index, index,
p0: [gradient.center0.x, gradient.center0.y], p0: conv::point_to_f32(gradient.start_center),
p1: [gradient.center1.x, gradient.center1.y], p1: conv::point_to_f32(gradient.end_center),
r0: gradient.radius0, r0: gradient.start_radius,
r1: gradient.radius1, r1: gradient.end_radius,
})); }));
} }
Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"), Brush::SweepGradient(_gradient) => todo!("sweep gradients aren't done yet!"),
Brush::Image(_image) => todo!("images 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 offset = self.scene.drawdata_stream.len();
let resources = &mut self.scene.resources; let resources = &mut self.scene.resources;
let stops_start = resources.stops.len(); let stops_start = resources.stops.len();
@ -247,10 +236,10 @@ impl<'a> SceneBuilder<'a> {
} }
/// Start a clip. /// Start a clip.
fn begin_clip(&mut self, blend: Option<BlendMode>) { fn begin_clip(&mut self, blend: BlendMode) {
self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP); self.scene.drawtag_stream.push(DRAWTAG_BEGINCLIP);
let element = Clip { let element = Clip {
blend: blend.unwrap_or(BlendMode::default()).pack(), blend: encode_blend_mode(blend),
}; };
self.scene self.scene
.drawdata_stream .drawdata_stream
@ -258,10 +247,10 @@ impl<'a> SceneBuilder<'a> {
self.scene.n_clip += 1; self.scene.n_clip += 1;
} }
fn end_clip(&mut self, blend: Option<BlendMode>) { fn end_clip(&mut self, blend: BlendMode) {
self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP); self.scene.drawtag_stream.push(DRAWTAG_ENDCLIP);
let element = Clip { let element = Clip {
blend: blend.unwrap_or(BlendMode::default()).pack(), blend: encode_blend_mode(blend),
}; };
self.scene self.scene
.drawdata_stream .drawdata_stream
@ -273,6 +262,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. // Tags for draw objects. See shader/drawtag.h for the authoritative source.
const DRAWTAG_FILLCOLOR: u32 = 0x44; const DRAWTAG_FILLCOLOR: u32 = 0x44;
const DRAWTAG_FILLLINGRADIENT: u32 = 0x114; const DRAWTAG_FILLLINGRADIENT: u32 = 0x114;

View file

@ -14,22 +14,19 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
mod blend;
mod builder; mod builder;
mod style; mod resource;
pub use blend::{BlendMode, Compose, Mix};
pub use builder::SceneBuilder; pub use builder::SceneBuilder;
pub use style::*; pub use resource::{ResourceBundle, ResourcePatch};
use super::geometry::{Affine, Point}; use super::conv;
use super::path::PathElement; use peniko::kurbo::Affine;
use super::resource::{ResourceBundle, ResourcePatch};
/// Raw data streams describing an encoded scene. /// Raw data streams describing an encoded scene.
#[derive(Default)] #[derive(Default)]
pub struct SceneData { pub struct SceneData {
pub transform_stream: Vec<Affine>, pub transform_stream: Vec<[f32; 6]>,
pub tag_stream: Vec<u8>, pub tag_stream: Vec<u8>,
pub pathseg_stream: Vec<u8>, pub pathseg_stream: Vec<u8>,
pub linewidth_stream: Vec<f32>, pub linewidth_stream: Vec<f32>,
@ -58,8 +55,7 @@ impl SceneData {
self.n_clip = 0; self.n_clip = 0;
self.resources.clear(); self.resources.clear();
if !is_fragment { if !is_fragment {
self.transform_stream self.transform_stream.push([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
.push(Affine::new(&[1.0, 0.0, 0.0, 1.0, 0.0, 0.0]));
self.linewidth_stream.push(-1.0); self.linewidth_stream.push(-1.0);
} }
} }
@ -68,8 +64,12 @@ impl SceneData {
let stops_base = self.resources.stops.len(); let stops_base = self.resources.stops.len();
let drawdata_base = self.drawdata_stream.len(); let drawdata_base = self.drawdata_stream.len();
if let Some(transform) = *transform { if let Some(transform) = *transform {
self.transform_stream self.transform_stream.extend(
.extend(other.transform_stream.iter().map(|x| transform * *x)); other
.transform_stream
.iter()
.map(|x| conv::affine_to_f32(&(transform * conv::affine_from_f32(x)))),
);
} else { } else {
self.transform_stream self.transform_stream
.extend_from_slice(&other.transform_stream); .extend_from_slice(&other.transform_stream);
@ -127,9 +127,8 @@ impl SceneFragment {
self.data.is_empty() self.data.is_empty()
} }
/// Returns the underlying stream of points that defined all encoded path /// Returns the the entire sequence of points in the scene fragment.
/// segments. pub fn points(&self) -> &[[f32; 2]] {
pub fn points(&self) -> &[Point] {
if self.is_empty() { if self.is_empty() {
&[] &[]
} else { } else {

View file

@ -1,13 +1,13 @@
use crate::brush::GradientStop;
use core::ops::Range; use core::ops::Range;
use peniko::ColorStop;
#[derive(Default)] #[derive(Default)]
/// Collection of late bound resources for a scene or scene fragment. /// Collection of late bound resources for a scene or scene fragment.
pub struct ResourceBundle { pub struct ResourceBundle {
/// Sequence of resource patches. /// Sequence of resource patches.
pub patches: Vec<ResourcePatch>, pub patches: Vec<ResourcePatch>,
/// Cache of gradient stops, referenced by range from the patches. /// Cache of color stops, referenced by range from the patches.
pub stops: Vec<GradientStop>, pub stops: Vec<ColorStop>,
} }
impl ResourceBundle { impl ResourceBundle {

View file

@ -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<D>
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,
}

View file

@ -9,13 +9,12 @@ edition = "2021"
wgpu = "0.14" wgpu = "0.14"
env_logger = "0.9.1" env_logger = "0.9.1"
pollster = "0.2.5" pollster = "0.2.5"
futures-intrusive = "0.4.1" futures-intrusive = "0.5.0"
parking_lot = "0.12" parking_lot = "0.12"
bytemuck = { version = "1.12.1", features = ["derive"] } bytemuck = { version = "1.12.1", features = ["derive"] }
png = "0.17.6" png = "0.17.6"
piet-scene = { path = "../piet-scene", features = ["kurbo"] } piet-scene = { path = "../piet-scene" }
# for picosvg, should be split out # for picosvg, should be split out
roxmltree = "0.13" roxmltree = "0.13"
kurbo = "0.8.3"

View file

@ -4,8 +4,7 @@ use std::str::FromStr;
use roxmltree::{Document, Node}; use roxmltree::{Document, Node};
use kurbo::{Affine, BezPath}; use piet_scene::kurbo::{Affine, BezPath};
use piet_scene::Color; use piet_scene::Color;
pub struct PicoSvg { pub struct PicoSvg {

View file

@ -1,4 +1,4 @@
use piet_scene::{Color, GradientStop, GradientStops}; use piet_scene::{Color, ColorStop, ColorStops};
use std::collections::HashMap; use std::collections::HashMap;
@ -8,7 +8,7 @@ const RETAINED_COUNT: usize = 64;
#[derive(Default)] #[derive(Default)]
pub struct RampCache { pub struct RampCache {
epoch: u64, epoch: u64,
map: HashMap<GradientStops, (u32, u64)>, map: HashMap<ColorStops, (u32, u64)>,
data: Vec<u32>, data: Vec<u32>,
} }
@ -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) { if let Some(entry) = self.map.get_mut(stops) {
entry.1 = self.epoch; entry.1 = self.epoch;
entry.0 entry.0
@ -72,7 +72,7 @@ impl RampCache {
} }
} }
fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator<Item = u32> + 'a { fn make_ramp<'a>(stops: &'a [ColorStop]) -> impl Iterator<Item = u32> + 'a {
let mut last_u = 0.0; let mut last_u = 0.0;
let mut last_c = ColorF64::from_color(stops[0].color); let mut last_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u; let mut this_u = last_u;

View file

@ -14,10 +14,10 @@
// //
// Also licensed under MIT license, at your choice. // Also licensed under MIT license, at your choice.
use kurbo::BezPath; use piet_scene::kurbo::{Affine, Ellipse, PathEl, Point, Rect};
use piet_scene::{ use piet_scene::{
Affine, BlendMode, Brush, Color, Compose, ExtendMode, Fill, GradientStop, LinearGradient, Mix, BlendMode, Brush, Color, Fill, LinearGradient, Mix, RadialGradient, Scene, SceneBuilder,
PathElement, Point, RadialGradient, Rect, Scene, SceneBuilder, SceneFragment, Stroke, SceneFragment, Stroke,
}; };
use crate::pico_svg::PicoSvg; use crate::pico_svg::PicoSvg;
@ -28,21 +28,20 @@ pub fn gen_test_scene() -> Scene {
let scene_ix = 1; let scene_ix = 1;
match scene_ix { match scene_ix {
0 => { 0 => {
let path = [ let path = &[
PathElement::MoveTo(Point::new(100.0, 100.0)), PathEl::MoveTo(Point::new(100.0, 100.0)),
PathElement::LineTo(Point::new(500.0, 120.0)), PathEl::LineTo(Point::new(500.0, 120.0)),
PathElement::LineTo(Point::new(300.0, 150.0)), PathEl::LineTo(Point::new(300.0, 150.0)),
PathElement::LineTo(Point::new(200.0, 260.0)), PathEl::LineTo(Point::new(200.0, 260.0)),
PathElement::LineTo(Point::new(150.0, 210.0)), PathEl::LineTo(Point::new(150.0, 210.0)),
PathElement::Close, ][..];
];
let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff)); let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff));
builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path); 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)); let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
builder.fill(Fill::NonZero, transform, &brush, None, &path); builder.fill(Fill::NonZero, transform, &brush, None, &path);
let transform = Affine::translate(100.0, 100.0); let transform = Affine::translate((100.0, 100.0));
let style = simple_stroke(1.0); let style = Stroke::new(1.0);
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00)); let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
builder.stroke(&style, transform, &brush, None, &path); builder.stroke(&style, transform, &brush, None, &path);
} }
@ -82,16 +81,16 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
Affine::IDENTITY, Affine::IDENTITY,
&fill.color.into(), &fill.color.into(),
None, None,
convert_bez_path(&fill.path), &fill.path,
); );
} }
Item::Stroke(stroke) => { Item::Stroke(stroke) => {
sb.stroke( sb.stroke(
&simple_stroke(stroke.width as f32), &Stroke::new(stroke.width as f32),
Affine::IDENTITY, Affine::IDENTITY,
&stroke.color.into(), &stroke.color.into(),
None, 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<Item = PathElement> + '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)] #[allow(unused)]
pub fn render_blend_grid(sb: &mut SceneBuilder) { pub fn render_blend_grid(sb: &mut SceneBuilder) {
const BLEND_MODES: &[Mix] = &[ const BLEND_MODES: &[Mix] = &[
@ -143,7 +123,7 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) {
for (ix, &blend) in BLEND_MODES.iter().enumerate() { for (ix, &blend) in BLEND_MODES.iter().enumerate() {
let i = ix % 4; let i = ix % 4;
let j = 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()); let square = blend_square(blend.into());
sb.append(&square, Some(transform)); sb.append(&square, Some(transform));
} }
@ -152,25 +132,10 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) {
#[allow(unused)] #[allow(unused)]
fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) { 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 // 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 rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.));
let stops = &[ let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::BLACK, Color::WHITE]);
GradientStop { sb.fill(Fill::NonZero, transform, &linear.into(), None, &rect);
color: Color::rgb8(0, 0, 0), const GRADIENTS: &[(f64, f64, Color)] = &[
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)] = &[
(150., 0., Color::rgb8(255, 240, 64)), (150., 0., Color::rgb8(255, 240, 64)),
(175., 100., Color::rgb8(255, 96, 240)), (175., 100., Color::rgb8(255, 96, 240)),
(125., 200., Color::rgb8(64, 192, 255)), (125., 200., Color::rgb8(64, 192, 255)),
@ -178,62 +143,40 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin
for (x, y, c) in GRADIENTS { for (x, y, c) in GRADIENTS {
let mut color2 = c.clone(); let mut color2 = c.clone();
color2.a = 0; color2.a = 0;
let stops = &[ let radial = RadialGradient::new((*x, *y), 100.0).stops([c.clone(), color2]);
GradientStop { sb.fill(Fill::NonZero, transform, &radial.into(), None, &rect);
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());
} }
const COLORS: &[Color] = &[ const COLORS: &[Color] = &[
Color::rgb8(255, 0, 0), Color::rgb8(255, 0, 0),
Color::rgb8(0, 255, 0), Color::rgb8(0, 255, 0),
Color::rgb8(0, 0, 255), Color::rgb8(0, 0, 255),
]; ];
sb.push_layer(Mix::Normal.into(), transform, rect.elements()); sb.push_layer(Mix::Normal.into(), transform, &rect);
for (i, c) in COLORS.iter().enumerate() { for (i, c) in COLORS.iter().enumerate() {
let stops = &[ // let stops = &[
GradientStop { // GradientStop {
color: Color::rgb8(255, 255, 255), // color: Color::rgb8(255, 255, 255),
offset: 0.0, // offset: 0.0,
}, // },
GradientStop { // GradientStop {
color: c.clone(), // color: c.clone(),
offset: 1.0, // offset: 1.0,
}, // },
][..]; // ][..];
let linear = Brush::LinearGradient(LinearGradient { let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([Color::WHITE, c.clone()]);
start: Point::new(0.0, 0.0), sb.push_layer(blend, transform, &rect);
end: Point::new(0.0, 200.0),
stops: stops.into(),
extend: ExtendMode::Pad,
});
sb.push_layer(blend, transform, rect.elements());
// squash the ellipse // squash the ellipse
let a = transform let a = transform
* Affine::translate(100., 100.) * Affine::translate((100., 100.))
* Affine::rotate(std::f32::consts::FRAC_PI_3 * (i * 2 + 1) as f32) * Affine::rotate(std::f64::consts::FRAC_PI_3 * (i * 2 + 1) as f64)
* Affine::scale(1.0, 0.357) * Affine::scale_non_uniform(1.0, 0.357)
* Affine::translate(-100., -100.); * Affine::translate((-100., -100.));
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
a, a,
&linear, &linear.into(),
None, None,
make_ellipse(100., 100., 90., 90.), &Ellipse::new((100., 100.), (90., 90.), 0.),
); );
sb.pop_layer(); sb.pop_layer();
} }
@ -248,34 +191,3 @@ fn blend_square(blend: BlendMode) -> SceneFragment {
sb.finish(); sb.finish();
fragment fragment
} }
fn make_ellipse(cx: f32, cy: f32, rx: f32, ry: f32) -> impl Iterator<Item = PathElement> + 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])
}