M Cargo.toml => Cargo.toml +1 -0
@@ 2,5 2,6 @@
members = [
"piet",
+ "piet-cairo",
"piet-direct2d",
]
A piet-cairo/Cargo.toml => piet-cairo/Cargo.toml +23 -0
@@ 0,0 1,23 @@
+[package]
+name = "piet-cairo"
+version = "0.1.0"
+authors = ["Raph Levien <raph.levien@gmail.com>"]
+description = "Cairo backend for piet 2D graphics abstraction."
+license = "MIT/Apache-2.0"
+edition = "2018"
+keywords = ["graphics", "2d"]
+categories = ["rendering::graphics-api"]
+
+[dependencies]
+kurbo = "0.1.0"
+piet = { path = "../piet" }
+
+[dependencies.cairo-rs]
+version = "0.5.0"
+# We don't need glib
+default-features = false
+
+[dev-dependencies.cairo-rs]
+version = "0.5.0"
+features = ["png"]
+default-features = false
A piet-cairo/examples/basic-cairo.rs => piet-cairo/examples/basic-cairo.rs +45 -0
@@ 0,0 1,45 @@
+//! Basic example of rendering on Cairo.
+
+use std::fs::File;
+
+use kurbo::BezPath;
+
+use cairo::{Context, Format, ImageSurface};
+
+use piet::RenderContext;
+use piet_cairo::CairoRenderContext;
+
+const TEXTURE_WIDTH: i32 = 400;
+const TEXTURE_HEIGHT: i32 = 200;
+
+const HIDPI: f64 = 2.0;
+
+fn draw_pretty_picture<R: RenderContext>(rc: &mut R) {
+ rc.clear(0xFF_FF_FF);
+ let brush = rc.solid_brush(0x00_00_80_FF);
+ rc.line((10.0, 10.0), (100.0, 50.0), &brush, 1.0, None);
+
+ let mut path = BezPath::new();
+ path.moveto((50.0, 10.0));
+ path.quadto((60.0, 50.0), (100.0, 90.0));
+ let brush = rc.solid_brush(0x00_80_00_FF);
+ rc.stroke_path(path.elements().iter().cloned(), &brush, 1.0, None);
+
+ let mut path = BezPath::new();
+ path.moveto((10.0, 20.0));
+ path.curveto((10.0, 80.0), (100.0, 80.0), (100.0, 60.0));
+ let brush = rc.solid_brush(0x00_00_80_C0);
+ // We'll make this `&path` by fixing kurbo.
+ rc.fill_path(path.elements().iter().cloned(), &brush);
+}
+
+fn main() {
+ let surface = ImageSurface::create(Format::ARgb32, TEXTURE_WIDTH, TEXTURE_HEIGHT)
+ .expect("Can't create surface");
+ let mut cr = Context::new(&surface);
+ cr.scale(HIDPI, HIDPI);
+ let mut piet_context = CairoRenderContext::new(&mut cr);
+ draw_pretty_picture(&mut piet_context);
+ let mut file = File::create("temp-cairo.png").expect("Couldn't create 'file.png'");
+ surface.write_to_png(&mut file).expect("Error writing image file");
+}
A piet-cairo/src/lib.rs => piet-cairo/src/lib.rs +141 -0
@@ 0,0 1,141 @@
+//! The Cairo backend for the Piet 2D graphics abstraction.
+
+use cairo::Context;
+
+use kurbo::{PathEl, QuadBez, Vec2};
+
+use piet::{RenderContext, RoundInto};
+
+pub struct CairoRenderContext<'a> {
+ // Cairo has this as Clone and with &self methods, but we do this to avoid
+ // concurrency problems.
+ ctx: &'a mut Context,
+}
+
+impl<'a> CairoRenderContext<'a> {
+ pub fn new(ctx: &mut Context) -> CairoRenderContext {
+ CairoRenderContext { ctx }
+ }
+}
+
+pub enum Brush {
+ Solid(u32),
+}
+
+pub enum StrokeStyle {
+ // TODO: actual stroke style options
+ Default,
+}
+
+impl<'a> RenderContext for CairoRenderContext<'a> {
+ /// Cairo mostly uses raw f64, so this is as convenient as anything.
+ type Point = Vec2;
+ type Coord = f64;
+ type Brush = Brush;
+ type StrokeStyle = StrokeStyle;
+
+ fn clear(&mut self, rgb: u32) {
+ self.ctx.set_source_rgb(byte_to_frac(rgb >> 16), byte_to_frac(rgb >> 8), byte_to_frac(rgb));
+ self.ctx.paint();
+ }
+
+ fn solid_brush(&mut self, rgba: u32) -> Brush {
+ Brush::Solid(rgba)
+ }
+
+ fn line<V: RoundInto<Vec2>, C: RoundInto<f64>>(
+ &mut self,
+ p0: V,
+ p1: V,
+ brush: &Self::Brush,
+ width: C,
+ style: Option<&Self::StrokeStyle>,
+ ) {
+ self.ctx.new_path();
+ let p0 = p0.round_into();
+ let p1 = p1.round_into();
+ self.ctx.move_to(p0.x, p0.y);
+ self.ctx.line_to(p1.x, p1.y);
+ self.set_stroke(width.round_into(), style);
+ self.set_brush(brush);
+ self.ctx.stroke();
+ }
+
+ fn fill_path<I: IntoIterator<Item = PathEl>>(&mut self, iter: I, brush: &Self::Brush) {
+ self.set_path(iter);
+ self.set_brush(brush);
+ self.ctx.fill();
+ }
+
+ fn stroke_path<I: IntoIterator<Item = PathEl>, C: RoundInto<f64>>(
+ &mut self,
+ iter: I,
+ brush: &Self::Brush,
+ width: C,
+ style: Option<&Self::StrokeStyle>,
+ ) {
+ self.set_path(iter);
+ self.set_stroke(width.round_into(), style);
+ self.set_brush(brush);
+ self.ctx.stroke();
+ }
+}
+
+impl<'a> CairoRenderContext<'a> {
+ /// Set the source pattern to the brush.
+ ///
+ /// Cairo is super stateful, and we're trying to have more retained stuff.
+ /// This is part of the impedance matching.
+ fn set_brush(&mut self, brush: &Brush) {
+ match *brush {
+ Brush::Solid(rgba) =>
+ self.ctx.set_source_rgba(byte_to_frac(rgba >> 24), byte_to_frac(rgba >> 16),
+ byte_to_frac(rgba >> 8), byte_to_frac(rgba))
+ }
+ }
+
+ /// Set the stroke parameters.
+ fn set_stroke(&mut self, width: f64, style: Option<&StrokeStyle>) {
+ self.ctx.set_line_width(width);
+ if let Some(style) = style {
+ match style {
+ // TODO: actual stroke style parameters
+ StrokeStyle::Default => (),
+ }
+ }
+ }
+
+ fn set_path<I: IntoIterator<Item = PathEl>>(&mut self, iter: I) {
+ // This shouldn't be necessary, we always leave the context in no-path
+ // state. But just in case, and it should be harmless.
+ self.ctx.new_path();
+ let mut last = Vec2::default();
+ for el in iter.into_iter() {
+ match el {
+ PathEl::Moveto(p) => {
+ self.ctx.move_to(p.x, p.y);
+ last = p;
+ }
+ PathEl::Lineto(p) => {
+ self.ctx.line_to(p.x, p.y);
+ last = p;
+ }
+ PathEl::Quadto(p1, p2) => {
+ let q = QuadBez::new(last, p1, p2);
+ let c = q.raise();
+ self.ctx.curve_to(c.p1.x, c.p1.y, c.p2.x, c.p2.y, p2.x, p2.y);
+ last = p2;
+ }
+ PathEl::Curveto(p1, p2, p3) => {
+ self.ctx.curve_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
+ last = p3;
+ }
+ PathEl::Closepath => self.ctx.close_path(),
+ }
+ }
+ }
+}
+
+fn byte_to_frac(byte: u32) -> f64 {
+ ((byte & 255) as f64) * (1.0 / 255.0)
+}
M piet-direct2d/Cargo.toml => piet-direct2d/Cargo.toml +0 -1
@@ 9,7 9,6 @@ keywords = ["graphics", "2d"]
categories = ["rendering::graphics-api"]
[dependencies]
-# TODO: don't check anything in with this dep
kurbo = "0.1.0"
piet = { path = "../piet" }
M piet/src/traits.rs => piet/src/traits.rs +3 -1
@@ 51,9 51,11 @@ pub trait RenderContext {
/// I'm also thinking of retained paths. But do we want to have a separate object for
/// retained paths, or do we want to have a lightweight display list abstraction, so
/// at worst you record a single `fill_path` into that?
+ ///
+ /// TODO: this is missing fill rule.
fn fill_path<I: IntoIterator<Item = PathEl>>(&mut self, iter: I, brush: &Self::Brush);
- fn stroke_path<I: IntoIterator<Item = PathEl>, C: RoundInto<f32>>(
+ fn stroke_path<I: IntoIterator<Item = PathEl>, C: RoundInto<Self::Coord>>(
&mut self,
iter: I,
brush: &Self::Brush,