diff --git a/piet-coregraphics/src/lib.rs b/piet-coregraphics/src/lib.rs index cede33e1..aac3f703 100644 --- a/piet-coregraphics/src/lib.rs +++ b/piet-coregraphics/src/lib.rs @@ -49,6 +49,7 @@ pub struct CoreGraphicsContext<'a> { // only those transforms applied by us. transform_stack: Vec, y_down: bool, + height: f64, } impl<'a> CoreGraphicsContext<'a> { @@ -99,6 +100,7 @@ impl<'a> CoreGraphicsContext<'a> { text, transform_stack: Vec::new(), y_down, + height: height.unwrap_or_default(), } } } @@ -121,14 +123,28 @@ pub enum CoreGraphicsImage { /// Empty images are not supported for core-graphics, so we need a variant here to handle that /// case. Empty, - NonEmpty(CGImage), + YUp(CGImage), + YDown(CGImage), } impl CoreGraphicsImage { + fn from_cgimage_and_ydir(image: CGImage, y_down: bool) -> Self { + match y_down { + true => CoreGraphicsImage::YDown(image), + false => CoreGraphicsImage::YUp(image), + } + } fn as_ref(&self) -> Option<&CGImage> { match self { CoreGraphicsImage::Empty => None, - CoreGraphicsImage::NonEmpty(img) => Some(img), + CoreGraphicsImage::YUp(image) | CoreGraphicsImage::YDown(image) => Some(image), + } + } + pub fn copy_image(&self) -> Option { + if let CoreGraphicsImage::YUp(image) | CoreGraphicsImage::YDown(image) = self { + Some(image.clone()) + } else { + None } } } @@ -343,19 +359,29 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { should_interpolate, rendering_intent, ); - Ok(CoreGraphicsImage::NonEmpty(image)) + + Ok(CoreGraphicsImage::from_cgimage_and_ydir(image, self.y_down)) } fn draw_image( &mut self, - image: &Self::Image, + src_image: &Self::Image, rect: impl Into, interp: InterpolationMode, ) { - let image = match image.as_ref() { - Some(img) => img, - None => return, + let image_y_down: bool; + let image = match src_image { + CoreGraphicsImage::YDown(img) => { + image_y_down = true; + img + } + CoreGraphicsImage::YUp(img) => { + image_y_down = false; + img + } + CoreGraphicsImage::Empty => return, }; + self.ctx.save(); //https://developer.apple.com/documentation/coregraphics/cginterpolationquality?language=objc let quality = match interp { @@ -366,13 +392,19 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { }; self.ctx.set_interpolation_quality(quality); let rect = rect.into(); - // CGImage is drawn flipped by default; it's easier for us to handle - // this transformation if we're drawing into a rect at the origin, so - // we convert our origin into a translation of the drawing context. - self.ctx.translate(rect.min_x(), rect.max_y()); - self.ctx.scale(1.0, -1.0); - self.ctx - .draw_image(to_cgrect(rect.with_origin(Point::ZERO)), image); + + if self.y_down && !image_y_down { + // The CGImage does not need to be inverted, draw it directly to the context. + self.ctx.draw_image(to_cgrect(rect), image); + } else { + // The CGImage needs to be flipped, which we do by translating the drawing rect to be + // centered around the origin before inverting the context. + self.ctx.translate(rect.min_x(), rect.max_y()); + self.ctx.scale(1.0, -1.0); + self.ctx + .draw_image(to_cgrect(rect.with_origin(Point::ZERO)), image); + } + self.ctx.restore(); } @@ -383,9 +415,13 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { dst_rect: impl Into, interp: InterpolationMode, ) { - if let CoreGraphicsImage::NonEmpty(image) = image { + if let CoreGraphicsImage::YDown(image) = image { + if let Some(cropped) = image.cropped(to_cgrect(src_rect)) { + self.draw_image(&CoreGraphicsImage::YDown(cropped), dst_rect, interp); + } + } else if let CoreGraphicsImage::YUp(image) = image { if let Some(cropped) = image.cropped(to_cgrect(src_rect)) { - self.draw_image(&CoreGraphicsImage::NonEmpty(cropped), dst_rect, interp); + self.draw_image(&CoreGraphicsImage::YUp(cropped), dst_rect, interp); } } } @@ -398,19 +434,24 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { // (see [`CoreGraphicsContext::new_impl`] for details). Since the `src_rect` we receive // as parameter is in piet's coordinate system, we need to first convert it to the CG one, // as otherwise our captured image area would be wrong. - - let transformation_matrix = if self.y_down { - self.ctx.get_ctm() + let src_cgrect = if self.y_down { + // If the active context is y-down (Piet's default) then we can use the context's + // transformation matrix directly. + let matrix = self.ctx.get_ctm(); + to_cgrect(src_rect).apply_transform(&matrix) } else { - self.ctx.save(); - self.ctx.translate(src_rect.x0, -src_rect.y0); + // Otherwise the active context is y-up (macOS default in coregraphics), and we need to + // temporarily translate and flip the context to capture the correct area. + let y_dir_adjusted_src_rect = Rect::new( + src_rect.x0, + self.height - src_rect.y0, + src_rect.x1, + self.height - src_rect.y1, + ); let matrix = self.ctx.get_ctm(); - self.ctx.restore(); - matrix + to_cgrect(y_dir_adjusted_src_rect).apply_transform(&matrix) }; - let src_cgrect = to_cgrect(src_rect).apply_transform(&transformation_matrix); - if src_cgrect.size.width < 1.0 || src_cgrect.size.height < 1.0 { return Err(Error::InvalidInput); } @@ -426,13 +467,16 @@ impl<'a> RenderContext for CoreGraphicsContext<'a> { if src_cgrect.size.width.round() as usize == self.ctx.width() && src_cgrect.size.height.round() as usize == self.ctx.height() { - return Ok(CoreGraphicsImage::NonEmpty(full_image)); + return Ok(CoreGraphicsImage::from_cgimage_and_ydir( + full_image, + self.y_down, + )); } - full_image - .cropped(src_cgrect) - .map(CoreGraphicsImage::NonEmpty) - .ok_or(Error::InvalidInput) + match full_image.cropped(src_cgrect) { + Some(image) => Ok(CoreGraphicsImage::from_cgimage_and_ydir(image, self.y_down)), + None => Err(Error::InvalidInput), + } } fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush) { @@ -470,7 +514,7 @@ impl Image for CoreGraphicsImage { // the issue would only be accuracy of the size. match self { CoreGraphicsImage::Empty => Size::new(0., 0.), - CoreGraphicsImage::NonEmpty(image) => { + CoreGraphicsImage::YDown(image) | CoreGraphicsImage::YUp(image) => { Size::new(image.width() as f64, image.height() as f64) } }