Merge pull request #208 from dfrg/peniko

Update piet-scene to depend on peniko
This commit is contained in:
Chad Brokaw 2022-11-23 17:06:26 -05:00 committed by GitHub
commit 5dbeb992e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 443 additions and 1438 deletions

21
Cargo.lock generated
View file

@ -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",

View file

@ -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<PgpuTransform> for piet_scene::Affine {
impl From<PgpuTransform> 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<Self::Item> {
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::<Vec<_>>();
(*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,
}
}

View file

@ -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<Affine>) -> Rect {
if let Some(transform) = &transform {
Rect::from_points(
self.fragment
.points()
let points = self.fragment.points();
if points.is_empty() {
return Rect::default();
}
let mut points = points
.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 {
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]
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"

View file

@ -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,
);
}

View file

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

View file

@ -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<GradientStops, (u32, u64)>,
map: HashMap<ColorStops, (u32, u64)>,
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) {
entry.1 = self.epoch;
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_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u;

View file

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

View file

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

View file

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

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;
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<SceneFragment> {
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<Item = moscato::Element> + Clone,
) -> impl Iterator<Item = PathElement> + 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))
fn convert_path(path: impl Iterator<Item = moscato::Element> + Clone) -> peniko::kurbo::BezPath {
let mut result = peniko::kurbo::BezPath::new();
for el in path {
result.push(convert_path_el(&el));
}
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,
})
result
}
fn convert_transformed_path(
path: impl Iterator<Item = moscato::Element> + Clone,
xform: &Affine,
) -> impl Iterator<Item = PathElement> + 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))
) -> peniko::kurbo::BezPath {
let mut result = peniko::kurbo::BezPath::new();
for el in path {
result.push(*xform * convert_path_el(&el));
}
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,
})
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 {
.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,
}
}

View file

@ -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<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.
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<PathElement>,
{
pub fn push_layer(
&mut self,
blend: impl Into<BlendMode>,
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<BrushRef<'b>>,
brush_transform: Option<Affine>,
elements: E,
) where
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
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<D>,
style: &Stroke,
transform: Affine,
brush: &Brush,
brush: impl Into<BrushRef<'b>>,
brush_transform: Option<Affine>,
elements: E,
) where
D: Borrow<[f32]>,
E: IntoIterator,
E::IntoIter: Clone,
E::Item: Borrow<PathElement>,
{
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<E>(&mut self, elements: E, is_fill: bool) -> bool
where
E: Iterator,
E::Item: Borrow<PathElement>,
{
/// 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<BrushRef<'b>>) {
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<BlendMode>) {
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<BlendMode>) {
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];

View file

@ -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<Affine>,
pub transform_stream: Vec<[f32; 6]>,
pub tag_stream: Vec<u8>,
pub pathseg_stream: Vec<u8>,
pub linewidth_stream: Vec<f32>,
@ -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 {

View file

@ -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<ResourcePatch>,
/// Cache of gradient stops, referenced by range from the patches.
pub stops: Vec<GradientStop>,
/// Cache of color stops, referenced by range from the patches.
pub stops: Vec<ColorStop>,
}
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"
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"

View file

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

View file

@ -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<GradientStops, (u32, u64)>,
map: HashMap<ColorStops, (u32, u64)>,
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) {
entry.1 = self.epoch;
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_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u;

View file

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