Skip to content

Commit

Permalink
Bump cairo (#453)
Browse files Browse the repository at this point in the history
* make image formats optional via features

* make sure image is also imported when importing an image format

* make the features propperly formatted

* fix merge

* Move cairo over to 0.14 version

* Fix clippy issues

* Fix weird addition

* Remove snapshots

* Fix tests

* Fix CI

* Make piet-cairo almost unwrap free

* Make gradient stops hashable (#454)

Implement Hash and Eq traits on gradient stops, which implies doing the
same for colors. This facilitates creating a cache of baked gradient
ramps.

Closes #450

* [cairo] Fix for big-endian systems

Cairo defines pixels as "a 32-bit quantity [...] stored in
native-endian". However, the existing code was treating a pixel as four
8-bit quantities. Put differently, it was hardcoding little endian.

This commit changes the code to first calculate the pixel value as an
u32 and then use u32::to_ne_bytes() to get back to "the world of bytes".
For readability, this is done in a new helper function.

I did not actually check if this fixes anything on big endian, but at
least it does not change anything for little endian according to the
test-pictures examples.

Fixes: #224
Signed-off-by: Uli Schlachter <psychon@znc.in>

* Remove unnecessary dst_off parameter

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Fix clippy warning about single-character names

error: 5 bindings with single-character names in scope
Error:    --> piet-cairo/src/lib.rs:523:47
    |
523 | fn write_rgba(data: &mut [u8], offset: usize, x: usize, r: u8, g: u8, b: u8, a: u8) {
    |                                               ^         ^      ^      ^      ^
    |
    = note: `-D clippy::many-single-char-names` implied by `-D warnings`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Add a simple criterion benchmark

This adds a simple benchmark for piet-cairo's make_image function.

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Fill benchmark image with random data

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Make rustfmt happy

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Try to clarify docs for ImageFormat

The image format is based on individual bytes and thus independent of
the system's endianness. This commit tries to make that more explicit.

This was originally suggested in issue #224.

Signed-off-by: Uli Schlachter <psychon@znc.in>

* Fix issue with benchmarks

* Remove redefinition

* Add safety comment

Co-authored-by: Raph Levien <raph.levien@gmail.com>
Co-authored-by: Uli Schlachter <psychon@znc.in>
  • Loading branch information
3 people authored Jul 1, 2021
1 parent f14021b commit 88c7f19
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 113 deletions.
12 changes: 6 additions & 6 deletions piet-cairo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ categories = ["rendering::graphics-api"]
[dependencies]
piet = { version = "0.4.0", path = "../piet" }

cairo-rs = { version = "0.9.1", default-features = false } # We don't need glib
pango = { version = "0.9.1", features = ["v1_44"] }
pango-sys = { version = "0.10.0", features = ["v1_44"] }
pangocairo = "0.10.0"
glib = "0.10.3"
cairo-rs = { version = "0.14.0", default-features = false } # We don't need glib
pango = { version = "0.14.0", features = ["v1_44"] }
pango-sys = { version = "0.14.0", features = ["v1_44"] }
pangocairo = "0.14.0"
glib = "0.14.0"
unicode-segmentation = "1.3.0"
xi-unicode = "0.3.0"

[dev-dependencies]
piet = { version = "0.4.0", path = "../piet", features = ["samples"] }
cairo-rs = { version = "0.9.1", default-features = false, features = ["png"] }
cairo-rs = { version = "0.14.0", default-features = false, features = ["png"] }
criterion = "0.3"

[[bench]]
Expand Down
2 changes: 1 addition & 1 deletion piet-cairo/benches/make_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn bench_make_image(c: &mut Criterion) {
c.bench_function(&format!("make_image_{}_{:?}", name, format), |b| {
let unused_surface =
ImageSurface::create(Format::ARgb32, 1, 1).expect("Can't create surface");
let cr = Context::new(&unused_surface);
let cr = Context::new(&unused_surface).unwrap();
let mut piet_context = CairoRenderContext::new(&cr);

let width = black_box(width.try_into().unwrap());
Expand Down
2 changes: 1 addition & 1 deletion piet-cairo/examples/test-picture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn run_sample(idx: usize, base_dir: &Path) -> Result<(), Box<dyn std::error::Err

let surface = ImageSurface::create(Format::ARgb32, size.width as i32, size.height as i32)
.expect("Can't create surface");
let cr = Context::new(&surface);
let cr = Context::new(&surface).unwrap();
cr.scale(HIDPI, HIDPI);
let mut piet_context = CairoRenderContext::new(&cr);
sample.draw(&mut piet_context)?;
Expand Down
94 changes: 57 additions & 37 deletions piet-cairo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct CairoRenderContext<'a> {
// by cairo. Instead we maintain our own stack, which will contain
// only those transforms applied by us.
transform_stack: Vec<Affine>,
error: Result<(), cairo::Error>,
}

impl<'a> CairoRenderContext<'a> {}
Expand Down Expand Up @@ -65,7 +66,10 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
type Image = CairoImage;

fn status(&mut self) -> Result<(), Error> {
Ok(())
match self.error {
Ok(_) => Ok(()),
Err(err) => Err(Error::BackendError(err.into())),
}
}

fn clear(&mut self, region: impl Into<Option<Rect>>, color: Color) {
Expand All @@ -87,8 +91,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
byte_to_frac(rgba),
);
rc.ctx.set_operator(cairo::Operator::Source);
rc.ctx.paint();
Ok(())
rc.ctx.paint().map_err(convert_error)
});
}

Expand Down Expand Up @@ -121,15 +124,15 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
self.set_path(shape);
self.set_brush(&*brush);
self.ctx.set_fill_rule(cairo::FillRule::Winding);
self.ctx.fill();
self.error = self.ctx.fill();
}

fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box());
self.set_path(shape);
self.set_brush(&*brush);
self.ctx.set_fill_rule(cairo::FillRule::EvenOdd);
self.ctx.fill();
self.error = self.ctx.fill();
}

fn clip(&mut self, shape: impl Shape) {
Expand All @@ -143,7 +146,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
self.set_path(shape);
self.set_stroke(width, None);
self.set_brush(&*brush);
self.ctx.stroke();
self.error = self.ctx.stroke();
}

fn stroke_styled(
Expand All @@ -157,7 +160,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
self.set_path(shape);
self.set_stroke(width, Some(style));
self.set_brush(&*brush);
self.ctx.stroke();
self.error = self.ctx.stroke();
}

fn text(&mut self) -> &mut Self::Text {
Expand All @@ -168,30 +171,29 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
let pos = pos.into();
let offset = layout.pango_offset();
self.ctx.move_to(pos.x - offset.x, pos.y - offset.y);
pangocairo::show_layout(&self.ctx, layout.pango_layout());
pangocairo::show_layout(self.ctx, layout.pango_layout());
}

fn save(&mut self) -> Result<(), Error> {
self.ctx.save();
self.ctx.save().map_err(convert_error)?;
let state = self.transform_stack.last().copied().unwrap_or_default();
self.transform_stack.push(state);
self.status()
Ok(())
}

fn restore(&mut self) -> Result<(), Error> {
if self.transform_stack.pop().is_some() {
// we're defensive about calling restore on the inner context,
// because an unbalanced call will trigger a panic in cairo-rs
self.ctx.restore();
self.status()
self.ctx.restore().map_err(convert_error)
} else {
Err(Error::StackUnbalance)
}
}

fn finish(&mut self) -> Result<(), Error> {
self.ctx.get_target().flush();
self.status()
self.ctx.target().flush();
Ok(())
}

fn transform(&mut self, transform: Affine) {
Expand Down Expand Up @@ -234,11 +236,9 @@ impl<'a> RenderContext for CairoRenderContext<'a> {
// Confident no borrow errors because we just created it.
let bytes_per_pixel = format.bytes_per_pixel();
let bytes_per_row = width * bytes_per_pixel;
let stride = image.get_stride() as usize;
let stride = image.stride() as usize;
{
let mut data = image
.get_data()
.map_err(|e| Error::BackendError(Box::new(e)))?;
let mut data = image.data().map_err(|e| Error::BackendError(Box::new(e)))?;
for y in 0..height {
let src_off = y * bytes_per_row;
let data = &mut data[y * stride..];
Expand Down Expand Up @@ -331,9 +331,16 @@ impl<'a> RenderContext for CairoRenderContext<'a> {

fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || rect);
let (image, origin) = compute_blurred_rect(rect, blur_radius);
self.set_brush(&*brush);
self.ctx.mask_surface(&image, origin.x, origin.y);
match compute_blurred_rect(rect, blur_radius) {
Ok((image, origin)) => {
self.set_brush(&*brush);
self.error = self
.ctx
.mask_surface(&image, origin.x, origin.y)
.map_err(cairo::Error::into);
}
Err(err) => self.error = Err(err),
}
}
}

Expand All @@ -349,7 +356,7 @@ impl<'a> IntoBrush<CairoRenderContext<'a>> for Brush {

impl Image for CairoImage {
fn size(&self) -> Size {
Size::new(self.0.get_width().into(), self.0.get_height().into())
Size::new(self.0.width().into(), self.0.height().into())
}
}

Expand All @@ -364,6 +371,7 @@ impl<'a> CairoRenderContext<'a> {
ctx,
text: CairoText::new(),
transform_stack: Vec::new(),
error: Ok(()),
}
}

Expand All @@ -379,8 +387,8 @@ impl<'a> CairoRenderContext<'a> {
byte_to_frac(rgba >> 8),
byte_to_frac(rgba),
),
Brush::Linear(ref linear) => self.ctx.set_source(linear),
Brush::Radial(ref radial) => self.ctx.set_source(radial),
Brush::Linear(ref linear) => self.error = self.ctx.set_source(linear),
Brush::Radial(ref radial) => self.error = self.ctx.set_source(radial),
}
}

Expand Down Expand Up @@ -439,7 +447,7 @@ impl<'a> CairoRenderContext<'a> {
) {
let src_rect = match src_rect {
Some(src_rect) => src_rect,
None => Size::new(image.get_width() as f64, image.get_height() as f64).to_rect(),
None => Size::new(image.width() as f64, image.height() as f64).to_rect(),
};
// Cairo returns an error if we try to paint an empty image, causing us to panic. We check if
// either the source or destination is empty, and early-return if so.
Expand All @@ -462,8 +470,8 @@ impl<'a> CairoRenderContext<'a> {
dst_rect.y0 - scale_y * src_rect.y0,
);
rc.ctx.scale(scale_x, scale_y);
rc.ctx.set_source(&surface_pattern);
rc.ctx.paint();
rc.error = rc.ctx.set_source(&surface_pattern);
rc.error = rc.ctx.paint();
Ok(())
});
}
Expand Down Expand Up @@ -503,17 +511,29 @@ fn affine_to_matrix(affine: Affine) -> Matrix {
}
}

fn compute_blurred_rect(rect: Rect, radius: f64) -> (ImageSurface, Point) {
fn compute_blurred_rect(rect: Rect, radius: f64) -> Result<(ImageSurface, Point), cairo::Error> {
let size = piet::util::size_for_blurred_rect(rect, radius);
// TODO: maybe not panic on error (but likely to happen only in extreme cases such as OOM)
let mut image =
ImageSurface::create(Format::A8, size.width as i32, size.height as i32).unwrap();
let stride = image.get_stride() as usize;
let mut data = image.get_data().unwrap();
let rect_exp = piet::util::compute_blurred_rect(rect, radius, stride, &mut *data);
std::mem::drop(data);
let origin = rect_exp.origin();
(image, origin)
match ImageSurface::create(Format::A8, size.width as i32, size.height as i32) {
Ok(mut image) => {
let stride = image.stride() as usize;
// An error is returned when either:
// The reference to image is dropped (it isnt since its still in scope),
// There is an error on image (there isnt since we havnt used it yet),
// The pointer to the image is null aka the surface isnt an imagesurface (it is an imagesurface),
// Or the surface is finished (it isnt, we know because we dont finish it).
// Since we know none of these cases should happen, we know that this should not panic.
let mut data = image.data().unwrap();
let rect_exp = piet::util::compute_blurred_rect(rect, radius, stride, &mut *data);
std::mem::drop(data);
let origin = rect_exp.origin();
Ok((image, origin))
}
Err(err) => Err(err),
}
}

fn convert_error(err: cairo::Error) -> Error {
Error::BackendError(err.into())
}

fn write_rgba(data: &mut [u8], column: usize, r: u8, g: u8, b: u8, a: u8) {
Expand Down
Loading

0 comments on commit 88c7f19

Please sign in to comment.