diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index d1c9ea1c..98f53470 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -94,7 +94,6 @@ extern "C" auto color_space = COLOR_SPACE_CAST; auto info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, alphaType, color_space); auto surface = SkSurfaces::Raster(info); - if (surface) { // The surface ref count will equal one after the pointer is returned. @@ -631,6 +630,12 @@ extern "C" CANVAS_CAST->drawImageRect(image, src_rect, dst_rect, sampling, nullptr, SkCanvas::kFast_SrcRectConstraint); } + void skiac_canvas_draw_picture(skiac_canvas *c_canvas, skiac_picture *c_picture, skiac_matrix *c_matrix, skiac_paint *c_paint) { + auto picture = reinterpret_cast(c_picture); + reinterpret_cast(c_canvas)->drawPicture(picture, MATRIX_CAST, PAINT_CAST); + } + + // Paint skiac_paint *skiac_paint_create() @@ -784,6 +789,26 @@ extern "C" return reinterpret_cast(new_path); } + // SkPictureRecorder + skiac_picture_recorder *skiac_picture_recorder_create() { + return reinterpret_cast(new SkPictureRecorder()); + } + + void skiac_picture_recorder_begin_recording(skiac_picture_recorder *c_picture_recorder, float x, float y, float width, float height) { + auto rect = SkRect::MakeXYWH(x, y, width, height); + reinterpret_cast(c_picture_recorder)->beginRecording(rect); + } + + skiac_canvas*skiac_picture_recorder_get_recording_canvas(skiac_picture_recorder *c_picture_recorder) { + auto canvas = reinterpret_cast(c_picture_recorder)->getRecordingCanvas(); + return reinterpret_cast(canvas); + } + + skiac_picture* skiac_picture_recorder_finish_recording_as_picture(skiac_picture_recorder *c_picture_recorder) { + auto picture = reinterpret_cast(c_picture_recorder)->finishRecordingAsPicture(); + return reinterpret_cast(picture.release()); + } + void skiac_path_swap(skiac_path *c_path, skiac_path *other_path) { auto other = reinterpret_cast(other_path); diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 68998188..42179c0d 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -70,6 +70,8 @@ typedef struct skiac_typeface skiac_typeface; typedef struct skiac_font_mgr skiac_font_mgr; typedef struct skiac_typeface_font_provider skiac_typeface_font_provider; typedef struct skiac_w_memory_stream skiac_w_memory_stream; +typedef struct skiac_picture_recorder skiac_picture_recorder; +typedef struct skiac_picture skiac_picture; #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) #define SK_FONT_FILE_PREFIX "C:/Windows/Fonts" @@ -317,6 +319,7 @@ extern "C" void skiac_canvas_reset(skiac_canvas *c_canvas); void skiac_canvas_write_pixels(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, int x, int y); void skiac_canvas_write_pixels_dirty(skiac_canvas *c_canvas, int width, int height, uint8_t *pixels, size_t row_bytes, size_t length, float x, float y, float dirty_x, float dirty_y, float dirty_width, float dirty_height, uint8_t cs); + void skiac_canvas_draw_picture(skiac_canvas *c_canvas, skiac_picture *c_picture, skiac_matrix *c_matrix, skiac_paint *c_paint); // Paint skiac_paint *skiac_paint_create(); @@ -495,6 +498,12 @@ extern "C" // SkSVG void skiac_svg_text_to_path(const uint8_t *data, size_t length, skiac_font_collection *c_collection, skiac_sk_data *output_data); + + // SkPictureRecorder + skiac_picture_recorder *skiac_picture_recorder_create(); + void skiac_picture_recorder_begin_recording(skiac_picture_recorder *c_picture_recorder, float x, float y, float width, float height); + skiac_canvas *skiac_picture_recorder_get_recording_canvas(skiac_picture_recorder *c_picture_recorder); + skiac_picture *skiac_picture_recorder_finish_recording_as_picture(skiac_picture_recorder *c_picture_recorder); } #endif // SKIA_CAPI_H diff --git a/src/ctx.rs b/src/ctx.rs index a012e5cd..4e171b72 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -9,6 +9,8 @@ use libavif::AvifData; use napi::{bindgen_prelude::*, JsBuffer, JsString, NapiRaw, NapiValue}; use crate::global_fonts::get_font; +use crate::picture_recorder::PictureRecorder; +use crate::sk::Canvas; use crate::{ avif::Config, error::SkError, @@ -157,13 +159,17 @@ impl Context { self.surface.canvas.set_clip_path(clip); } - pub fn clear_rect(&mut self, x: f32, y: f32, width: f32, height: f32) { + pub fn clear_rect(&mut self, x: f32, y: f32, width: f32, height: f32) -> result::Result<(), SkError> { let mut paint = Paint::new(); paint.set_style(PaintStyle::Fill); paint.set_color(0, 0, 0, 0); paint.set_stroke_miter(10.0); paint.set_blend_mode(BlendMode::Clear); - self.surface.draw_rect(x, y, width, height, &paint); + self.render_canvas(&mut paint, |canvas, paint| { + canvas.draw_rect(x, y, width, height, paint); + Ok(()) + })?; + Ok(()) } pub fn close_path(&mut self) { @@ -193,22 +199,24 @@ impl Context { } pub fn stroke_rect(&mut self, x: f32, y: f32, w: f32, h: f32) -> result::Result<(), SkError> { - let stroke_paint = self.stroke_paint()?; - if let Some(shadow_paint) = self.shadow_blur_paint(&stroke_paint) { - let surface = &mut self.surface; - let last_state = &self.state; - surface.save(); - Self::apply_shadow_offset_matrix( - surface, - last_state.shadow_offset_x, - last_state.shadow_offset_y, - )?; - surface.draw_rect(x, y, w, h, &shadow_paint); - surface.restore(); - }; - - self.surface.draw_rect(x, y, w, h, &stroke_paint); - + let mut stroke_paint = self.stroke_paint()?; + let shadow_offset_x = self.state.shadow_offset_x; + let shadow_offset_y = self.state.shadow_offset_y; + let shadow_blur_paint_option = &self.shadow_blur_paint(&stroke_paint); + self.render_canvas(&mut stroke_paint, |canvas, paint| { + if let Some(shadow_paint) = shadow_blur_paint_option { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + shadow_offset_x, + shadow_offset_y, + )?; + canvas.draw_rect(x, y, w, h, shadow_paint); + canvas.restore(); + }; + canvas.draw_rect(x, y, w, h, paint); + Ok(()) + })?; Ok(()) } @@ -267,34 +275,37 @@ impl Context { y: f32, max_width: f32, ) -> result::Result<(), SkError> { - let stroke_paint = self.stroke_paint()?; + let mut stroke_paint = self.stroke_paint()?; self.draw_text( text.replace('\n', " ").as_str(), x, y, max_width, - &stroke_paint, + &mut stroke_paint, )?; Ok(()) } pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32) -> result::Result<(), SkError> { - let fill_paint = self.fill_paint()?; - if let Some(shadow_paint) = self.shadow_blur_paint(&fill_paint) { - let surface = &mut self.surface; - let last_state = &self.state; - surface.save(); - Self::apply_shadow_offset_matrix( - surface, - last_state.shadow_offset_x, - last_state.shadow_offset_y, - )?; - surface.draw_rect(x, y, w, h, &shadow_paint); - surface.restore(); - }; - - self.surface.draw_rect(x, y, w, h, &fill_paint); + let mut fill_paint = self.fill_paint()?; + let shadow_offset_x = self.state.shadow_offset_x; + let shadow_offset_y = self.state.shadow_offset_y; + let shadow_paint_option = &self.shadow_blur_paint(&fill_paint); + self.render_canvas(&mut fill_paint, |canvas, paint| { + if let Some(shadow_paint) = shadow_paint_option { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + shadow_offset_x, + shadow_offset_y, + )?; + canvas.draw_rect(x, y, w, h, shadow_paint); + canvas.restore(); + }; + canvas.draw_rect(x, y, w, h, paint); + Ok(()) + })?; Ok(()) } @@ -305,67 +316,108 @@ impl Context { y: f32, max_width: f32, ) -> result::Result<(), SkError> { - let fill_paint = self.fill_paint()?; + let mut fill_paint = self.fill_paint()?; self.draw_text( text.replace('\n', " ").as_str(), x, y, max_width, - &fill_paint, + &mut fill_paint, )?; Ok(()) } pub fn stroke(&mut self, path: Option<&mut SkPath>) -> Result<()> { let last_state = &self.state; + let path_cloned = self.path.clone(); let p = match path { Some(path) => path, - None => &self.path, + None => &path_cloned, }; - let stroke_paint = self.stroke_paint()?; - if let Some(shadow_paint) = self.shadow_blur_paint(&stroke_paint) { - let surface = &mut self.surface; - surface.save(); - Self::apply_shadow_offset_matrix( - surface, - last_state.shadow_offset_x, - last_state.shadow_offset_y, - )?; - self.surface.canvas.draw_path(p, &shadow_paint); - self.surface.restore(); - mem::drop(shadow_paint); - } - self.surface.canvas.draw_path(p, &stroke_paint); + let mut stroke_paint = self.stroke_paint()?; + let shadow_blur_paint_option = &self.shadow_blur_paint(&stroke_paint); + let shadow_offset_x = last_state.shadow_offset_x; + let shadow_offset_y = last_state.shadow_offset_y; + self.render_canvas(&mut stroke_paint, |canvas, paint| { + if let Some(shadow_paint) = shadow_blur_paint_option { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + shadow_offset_x, + shadow_offset_y, + )?; + canvas.draw_path(p, shadow_paint); + canvas.restore(); + } + canvas.draw_path(p, paint); + Ok(()) + })?; Ok(()) } + pub fn render_canvas(&mut self, paint: &mut Paint, f: F) -> result::Result<(), SkError> + where F: Fn(&mut Canvas, &Paint) -> result::Result<(), SkError> + { + match self.state.global_composite_operation { + BlendMode::SourceIn + | BlendMode::SourceOut + | BlendMode::DestinationIn + | BlendMode::DestinationOut + | BlendMode::DestinationATop + | BlendMode::Source => { + let mut recorder_paint = paint.clone(); + recorder_paint.set_blend_mode(BlendMode::SourceOver); + let mut recorder = PictureRecorder::new(); + recorder.begin_recording(0.0, 0.0, self.width as f32, self.height as f32); + if let Some(mut canvas) = recorder.get_recording_canvas() { + f(&mut canvas, &recorder_paint)?; + } + if let Some(pict) = recorder.finish_recording_as_picture() { + self.surface.save(); + self.surface.draw_picture(pict, &Matrix::identity(), paint); + self.surface.restore(); + + } + Ok(()) + } + _ => { + f(&mut self.surface.canvas, paint)?; + Ok(()) + } + } + } + pub fn fill( &mut self, path: Option<&mut SkPath>, fill_rule: FillType, ) -> result::Result<(), SkError> { - let last_state = &self.state; + let mut fill_paint = self.fill_paint()?; + let mut path_cloned = self.path.clone(); let p = if let Some(p) = path { p.set_fill_type(fill_rule); p } else { - self.path.set_fill_type(fill_rule); - &self.path + path_cloned.set_fill_type(fill_rule); + &path_cloned }; - let fill_paint = self.fill_paint()?; - if let Some(shadow_paint) = self.shadow_blur_paint(&fill_paint) { - let surface = &mut self.surface; - surface.save(); - Self::apply_shadow_offset_matrix( - surface, - last_state.shadow_offset_x, - last_state.shadow_offset_y, - )?; - surface.canvas.draw_path(p, &shadow_paint); - surface.restore(); - mem::drop(shadow_paint); - } - self.surface.draw_path(p, &fill_paint); + let shadow_offset_x = self.state.shadow_offset_x; + let shadow_offset_y = self.state.shadow_offset_y; + let shadow_paint_option = &self.shadow_blur_paint(&fill_paint); + self.render_canvas(&mut fill_paint, |canvas, paint| { + if let Some(shadow_paint) = shadow_paint_option { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + shadow_offset_x, + shadow_offset_y + )?; + canvas.draw_path(p, shadow_paint); + canvas.restore(); + } + canvas.draw_path(p, paint); + Ok(()) + })?; Ok(()) } @@ -618,11 +670,29 @@ impl Context { d_height: f32, ) -> Result<()> { let bitmap = bitmap.0.bitmap; - let mut paint = self.fill_paint()?; + let mut paint: Paint = self.fill_paint()?; paint.set_alpha((self.state.global_alpha * 255.0).round() as u8); - if let Some(drop_shadow_paint) = self.drop_shadow_paint(&paint) { - let surface = &mut self.surface; - surface.canvas.draw_image( + let drop_shadow_paint_option = &self.drop_shadow_paint(&paint); + let image_smoothing_quality = self.state.image_smoothing_quality; + let image_smoothing_enabled = self.state.image_smoothing_enabled; + self.render_canvas(&mut paint, |canvas: &mut Canvas, paint| { + if let Some(drop_shadow_paint) = drop_shadow_paint_option { + canvas.draw_image( + bitmap, + sx, + sy, + s_width, + s_height, + dx, + dy, + d_width, + d_height, + image_smoothing_enabled, + image_smoothing_quality, + drop_shadow_paint, + ); + } + canvas.draw_image( bitmap, sx, sy, @@ -632,25 +702,12 @@ impl Context { dy, d_width, d_height, - self.state.image_smoothing_enabled, - self.state.image_smoothing_quality, - &drop_shadow_paint, + image_smoothing_enabled, + image_smoothing_quality, + &paint, ); - } - self.surface.canvas.draw_image( - bitmap, - sx, - sy, - s_width, - s_height, - dx, - dy, - d_width, - d_height, - self.state.image_smoothing_enabled, - self.state.image_smoothing_quality, - &paint, - ); + Ok(()) + })?; Ok(()) } @@ -661,55 +718,64 @@ impl Context { x: f32, y: f32, max_width: f32, - paint: &Paint, + paint: &mut Paint, ) -> result::Result<(), SkError> { let state = &self.state; let weight = state.font_style.weight; - let stretch = state.font_style.stretch; - let slant = state.font_style.style; - if let Some(shadow_paint) = self.shadow_blur_paint(paint) { - let surface = &mut self.surface; - surface.save(); - Self::apply_shadow_offset_matrix(surface, state.shadow_offset_x, state.shadow_offset_y)?; - let font = get_font()?; - surface.canvas.draw_text( + let stretch = state.font_style.stretch.clone(); + let slant = state.font_style.style.clone(); + let shadow_offset_x = state.shadow_offset_x; + let shadow_offset_y = state.shadow_offset_y; + let font_size = state.font_style.size; + let font_family = &state.font_style.family.clone(); + let text_baseline = state.text_baseline.clone(); + let text_align = state.text_align.clone(); + let text_direction = state.text_direction.clone(); + let width = self.width; + let font = get_font()?; + let shadow_blur_paint_option = &self.shadow_blur_paint(paint); + self.render_canvas(paint, |canvas, paint| { + if let Some(shadow_paint) = shadow_blur_paint_option { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas(canvas, shadow_offset_x, shadow_offset_y)?; + canvas.draw_text( + text, + x, + y, + max_width, + width as f32, + weight, + stretch as i32, + slant, + &font, + font_size, + font_family, + text_baseline, + text_align, + text_direction, + shadow_paint, + )?; + canvas.restore(); + } + canvas.draw_text( text, x, y, max_width, - self.width as f32, + width as f32, weight, stretch as i32, slant, &font, - state.font_style.size, - &state.font_style.family, - state.text_baseline, - state.text_align, - state.text_direction, - &shadow_paint, + font_size, + &font_family, + text_baseline, + text_align, + text_direction, + paint, )?; - mem::drop(font); - surface.restore(); - } - let font = get_font()?; - self.surface.canvas.draw_text( - text, - x, - y, - max_width, - self.width as f32, - weight, - stretch as i32, - slant, - &font, - state.font_style.size, - &state.font_style.family, - state.text_baseline, - state.text_align, - state.text_direction, - paint, - )?; + Ok(()) + })?; Ok(()) } @@ -741,15 +807,27 @@ impl Context { shadow_offset_x: f32, shadow_offset_y: f32, ) -> result::Result<(), SkError> { - let current_transform = surface.canvas.get_transform_matrix(); + Self::apply_shadow_offset_matrix_to_canvas( + &mut surface.canvas, + shadow_offset_x, + shadow_offset_y, + ) + } + + fn apply_shadow_offset_matrix_to_canvas( + canvas: &mut Canvas, + shadow_offset_x: f32, + shadow_offset_y: f32, + ) -> result::Result<(), SkError> { + let current_transform = canvas.get_transform_matrix(); let invert = current_transform .invert() .ok_or_else(|| SkError::Generic("Invert matrix failed".to_owned()))?; - surface.canvas.concat(&invert); + canvas.concat(&invert); let mut shadow_offset = current_transform.clone(); shadow_offset.pre_translate(shadow_offset_x, shadow_offset_y); - surface.canvas.concat(&shadow_offset); - surface.canvas.concat(¤t_transform); + canvas.concat(&shadow_offset); + canvas.concat(¤t_transform); Ok(()) } @@ -860,6 +938,7 @@ impl CanvasRenderingContext2D { pub fn set_global_composite_operation(&mut self, mode: String) { if let Ok(blend_mode) = mode.parse() { self.context.state.paint.set_blend_mode(blend_mode); + self.context.state.global_composite_operation = blend_mode; }; } diff --git a/src/lib.rs b/src/lib.rs index 9f324ab6..e21f6d69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ mod pattern; mod sk; mod state; pub mod svg; +pub mod picture_recorder; const MIME_WEBP: &str = "image/webp"; const MIME_PNG: &str = "image/png"; diff --git a/src/picture_recorder.rs b/src/picture_recorder.rs new file mode 100644 index 00000000..e03b24ab --- /dev/null +++ b/src/picture_recorder.rs @@ -0,0 +1,26 @@ +use crate::sk::{SkPictureRecorder, Canvas, SkPicture}; + + +pub struct PictureRecorder { + pub (crate) inner: SkPictureRecorder, +} + +impl PictureRecorder { + pub fn new() -> Self { + PictureRecorder { + inner: SkPictureRecorder::new(), + } + } + + pub fn begin_recording(&mut self, x: f32, y: f32, w: f32, h: f32) { + self.inner.begin_recording(x, y, w, h); + } + + pub fn get_recording_canvas(&mut self) -> Option { + self.inner.get_recording_canvas() + } + + pub fn finish_recording_as_picture(&mut self) -> Option { + self.inner.finish_recording_as_picture() + } +} diff --git a/src/sk.rs b/src/sk.rs index c58cd59e..5e4a42f0 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -208,6 +208,18 @@ pub mod ffi { pub y2: f32, } + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub struct skiac_picture_recorder { + _unused: [u8; 0] + } + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub struct skiac_picture { + _unused: [u8;0] + } + pub type SkiacFontCollectionGetFamily = Option; @@ -454,6 +466,13 @@ pub mod ffi { color_space: u8, ); + pub fn skiac_canvas_draw_picture( + canvas: *mut skiac_canvas, + picture: *mut skiac_picture, + matrix: *mut skiac_matrix, + paint: *mut skiac_paint + ); + pub fn skiac_paint_create() -> *mut skiac_paint; pub fn skiac_paint_clone(source: *mut skiac_paint) -> *mut skiac_paint; @@ -874,6 +893,15 @@ pub mod ffi { font_collection: *mut skiac_font_collection, output_data: *mut skiac_sk_data, ); + + // SkPictureRecorder + pub fn skiac_picture_recorder_create() -> *mut skiac_picture_recorder; + + pub fn skiac_picture_recorder_begin_recording(picture_recorder: *mut skiac_picture_recorder, x: f32, y: f32, w: f32, h: f32); + + pub fn skiac_picture_recorder_get_recording_canvas(picture_recorder: *mut skiac_picture_recorder) -> *mut skiac_canvas; + + pub fn skiac_picture_recorder_finish_recording_as_picture(picture_recorder: *mut skiac_picture_recorder) -> *mut skiac_picture; } } @@ -2253,6 +2281,12 @@ impl Canvas { ) } } + + pub fn draw_picture(&self, picture: SkPicture, matrix: &Matrix, paint: &Paint ) { + unsafe { + ffi::skiac_canvas_draw_picture(self.0, picture.0, matrix.0, paint.0); + } + } } #[derive(Debug)] @@ -3643,6 +3677,46 @@ impl Drop for SkWMemoryStream { } } + +#[derive(Debug)] +pub struct SkPicture(*mut ffi::skiac_picture); + +#[derive(Debug)] +pub struct SkPictureRecorder (pub(crate) *mut ffi::skiac_picture_recorder); + +impl SkPictureRecorder { + pub fn new() -> SkPictureRecorder { + SkPictureRecorder(unsafe { ffi::skiac_picture_recorder_create() }) + } + + pub fn begin_recording(&self, x: f32, y: f32, w: f32, h: f32) { + unsafe { ffi::skiac_picture_recorder_begin_recording(self.0, x, y, w, h); } + } + + pub fn get_recording_canvas(&self) -> Option { + let canvas_ref = unsafe { + ffi::skiac_picture_recorder_get_recording_canvas(self.0) + }; + if canvas_ref.is_null() { + return None + } + Some(Canvas(canvas_ref)) + } + pub fn finish_recording_as_picture(&self) -> Option { + let picture_ref = unsafe { + ffi::skiac_picture_recorder_finish_recording_as_picture(self.0) + }; + + if picture_ref.is_null() { + return None + } + + Some(SkPicture(picture_ref)) + } +} + + + #[inline(always)] pub(crate) fn radians_to_degrees(rad: f32) -> f32 { rad / PI * 180.0 diff --git a/src/state.rs b/src/state.rs index d5a5f371..a0d319ad 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,6 @@ use cssparser::RGBA; -use crate::sk::{ImageFilter, Matrix}; +use crate::sk::{ImageFilter, Matrix, BlendMode}; use super::{ font::Font, @@ -31,6 +31,7 @@ pub struct Context2dRenderingState { pub transform: Matrix, pub filter: Option, pub filters_string: String, + pub global_composite_operation: BlendMode } impl Default for Context2dRenderingState { @@ -59,6 +60,7 @@ impl Default for Context2dRenderingState { transform: Matrix::identity(), filter: None, filters_string: "none".to_owned(), + global_composite_operation: BlendMode::default() } } }