diff --git a/__test__/index.spec.ts b/__test__/index.spec.ts index 24dfb002..7bd62713 100644 --- a/__test__/index.spec.ts +++ b/__test__/index.spec.ts @@ -175,3 +175,39 @@ test('stroke-and-filling-jpeg', async (t) => { ctx.fill() await snapshotImage(t, t.context, 'jpeg') }) + +test('composition-destination-in', async (t) => { + const { ctx } = t.context + t.context.canvas.width = 300 + t.context.canvas.height = 300 + ctx.fillStyle = 'red' + ctx.fillRect(0, 0, 300, 300) + ctx.save() + ctx.globalCompositeOperation = 'destination-in'; + ctx.fillStyle = 'green'; + ctx.beginPath(); + ctx.arc(150, 150, 100, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + ctx.restore() + + await snapshotImage(t, t.context, 'png') +}) + +test('composition-source-in', async (t) => { + const { ctx } = t.context + t.context.canvas.width = 300 + t.context.canvas.height = 300 + ctx.fillStyle = 'red' + ctx.fillRect(0, 0, 300, 300) + ctx.save() + ctx.globalCompositeOperation = 'source-in'; + ctx.fillStyle = 'green'; + ctx.beginPath(); + ctx.arc(150, 150, 100, 0, Math.PI * 2); + ctx.closePath(); + ctx.fill(); + ctx.restore() + + await snapshotImage(t, t.context, 'png') +}) diff --git a/__test__/snapshots/composition-destination-in.png b/__test__/snapshots/composition-destination-in.png new file mode 100644 index 00000000..27aa640e Binary files /dev/null and b/__test__/snapshots/composition-destination-in.png differ diff --git a/__test__/snapshots/composition-source-in.png b/__test__/snapshots/composition-source-in.png new file mode 100644 index 00000000..6e737bfb Binary files /dev/null and b/__test__/snapshots/composition-source-in.png differ diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index d1c9ea1c..49b126f6 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); + CANVAS_CAST->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..769ea291 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,30 @@ 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 self.surface.canvas, + &paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + canvas.draw_rect(x, y, width, height, paint); + Ok(()) + }, + )?; + Ok(()) } pub fn close_path(&mut self) { @@ -194,21 +213,27 @@ 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); - + Self::render_canvas( + &mut self.surface.canvas, + &stroke_paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + if let Some(shadow_paint) = Self::shadow_blur_paint(&self.state, &stroke_paint) { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + self.state.shadow_offset_x, + self.state.shadow_offset_y, + )?; + canvas.draw_rect(x, y, w, h, &shadow_paint); + canvas.restore(); + }; + canvas.draw_rect(x, y, w, h, paint); + Ok(()) + }, + )?; Ok(()) } @@ -280,21 +305,28 @@ impl Context { 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); + Self::render_canvas( + &mut self.surface.canvas, + &fill_paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + if let Some(shadow_paint) = Self::shadow_blur_paint(&self.state, &fill_paint) { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + self.state.shadow_offset_x, + self.state.shadow_offset_y, + )?; + canvas.draw_rect(x, y, w, h, &shadow_paint); + canvas.restore(); + }; + canvas.draw_rect(x, y, w, h, paint); + Ok(()) + }, + )?; Ok(()) } @@ -317,34 +349,80 @@ impl Context { } pub fn stroke(&mut self, path: Option<&mut SkPath>) -> Result<()> { - let last_state = &self.state; - let p = match path { - Some(path) => path, - None => &self.path, - }; 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); + Self::render_canvas( + &mut self.surface.canvas, + &stroke_paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + let p: &SkPath = match &path { + Some(path) => path, + None => &self.path, + }; + if let Some(shadow_paint) = Self::shadow_blur_paint(&self.state, &stroke_paint) { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + self.state.shadow_offset_x, + self.state.shadow_offset_y, + )?; + canvas.draw_path(p, &shadow_paint); + canvas.restore(); + } + canvas.draw_path(p, paint); + Ok(()) + }, + )?; Ok(()) } + pub fn render_canvas( + surface_canvas: &mut Canvas, + paint: &Paint, + blend_mode: BlendMode, + width: f32, + height: f32, + f: F, + ) -> result::Result<(), SkError> + where + F: Fn(&mut Canvas, &Paint) -> result::Result<(), SkError>, + { + match blend_mode { + BlendMode::SourceIn + | BlendMode::SourceOut + | BlendMode::DestinationIn + | BlendMode::DestinationOut + | BlendMode::DestinationATop + | BlendMode::Source => { + let mut layer_paint = paint.clone(); + layer_paint.set_blend_mode(BlendMode::SourceOver); + let mut layer = PictureRecorder::new(); + layer.begin_recording(0.0, 0.0, width, height); + if let Some(mut canvas) = layer.get_recording_canvas() { + f(&mut canvas, &layer_paint)?; + } + if let Some(pict) = layer.finish_recording_as_picture() { + surface_canvas.save(); + surface_canvas.draw_picture(pict, &Matrix::identity(), paint); + surface_canvas.restore(); + } + Ok(()) + } + _ => { + f(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 fill_paint = self.fill_paint()?; let p = if let Some(p) = path { p.set_fill_type(fill_rule); p @@ -352,20 +430,27 @@ impl Context { self.path.set_fill_type(fill_rule); &self.path }; - 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); + Self::render_canvas( + &mut self.surface.canvas, + &fill_paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + if let Some(shadow_paint) = Self::shadow_blur_paint(&self.state, &fill_paint) { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + self.state.shadow_offset_x, + self.state.shadow_offset_y, + )?; + canvas.draw_path(p, &shadow_paint); + canvas.restore(); + } + canvas.draw_path(p, paint); + Ok(()) + }, + )?; Ok(()) } @@ -532,19 +617,15 @@ impl Context { Ok(paint) } - fn drop_shadow_paint(&self, paint: &Paint) -> Option { + fn drop_shadow_paint(state: &Context2dRenderingState, paint: &Paint) -> Option { let alpha = paint.get_alpha(); - let last_state = &self.state; - let shadow_color = &last_state.shadow_color; + let shadow_color = &state.shadow_color; let mut shadow_alpha = shadow_color.alpha; shadow_alpha = ((shadow_alpha as f32) * (alpha as f32 / 255.0)) as u8; if shadow_alpha == 0 { return None; } - if last_state.shadow_blur == 0f32 - && last_state.shadow_offset_x == 0f32 - && last_state.shadow_offset_y == 0f32 - { + if state.shadow_blur == 0f32 && state.shadow_offset_x == 0f32 && state.shadow_offset_y == 0f32 { return None; } let mut drop_shadow_paint = paint.clone(); @@ -552,12 +633,12 @@ impl Context { let r = shadow_color.red; let g = shadow_color.green; let b = shadow_color.blue; - let transform = last_state.transform.get_transform(); - let sigma_x = last_state.shadow_blur / (2f32 * transform.scale_x()); - let sigma_y = last_state.shadow_blur / (2f32 * transform.scale_y()); + let transform = state.transform.get_transform(); + let sigma_x = state.shadow_blur / (2f32 * transform.scale_x()); + let sigma_y = state.shadow_blur / (2f32 * transform.scale_y()); let shadow_effect = ImageFilter::make_drop_shadow_only( - last_state.shadow_offset_x, - last_state.shadow_offset_y, + state.shadow_offset_x, + state.shadow_offset_y, sigma_x, sigma_y, (a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | b as u32, @@ -568,18 +649,14 @@ impl Context { Some(drop_shadow_paint) } - fn shadow_blur_paint(&self, paint: &Paint) -> Option { + fn shadow_blur_paint(state: &Context2dRenderingState, paint: &Paint) -> Option { let alpha = paint.get_alpha(); - let last_state = &self.state; - let shadow_color = Self::multiply_by_alpha(&last_state.shadow_color, alpha); + let shadow_color = Self::multiply_by_alpha(&state.shadow_color, alpha); let shadow_alpha = shadow_color.alpha; if shadow_alpha == 0 { return None; } - if last_state.shadow_blur == 0f32 - && last_state.shadow_offset_x == 0f32 - && last_state.shadow_offset_y == 0f32 - { + if state.shadow_blur == 0f32 && state.shadow_offset_x == 0f32 && state.shadow_offset_y == 0f32 { return None; } let mut drop_shadow_paint = paint.clone(); @@ -587,9 +664,9 @@ impl Context { let r = shadow_color.red; let g = shadow_color.green; let b = shadow_color.blue; - let transform = last_state.transform.get_transform(); - let sigma_x = last_state.shadow_blur / (2f32 * transform.scale_x()); - let sigma_y = last_state.shadow_blur / (2f32 * transform.scale_y()); + let transform = state.transform.get_transform(); + let sigma_x = state.shadow_blur / (2f32 * transform.scale_x()); + let sigma_y = state.shadow_blur / (2f32 * transform.scale_y()); let shadow_effect = ImageFilter::make_drop_shadow_only( 0.0, 0.0, @@ -600,7 +677,7 @@ impl Context { )?; drop_shadow_paint.set_alpha(shadow_alpha); drop_shadow_paint.set_image_filter(&shadow_effect); - let blur_effect = MaskFilter::make_blur(last_state.shadow_blur / 2f32)?; + let blur_effect = MaskFilter::make_blur(state.shadow_blur / 2f32)?; drop_shadow_paint.set_mask_filter(&blur_effect); Some(drop_shadow_paint) } @@ -618,40 +695,48 @@ 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( - bitmap, - sx, - sy, - s_width, - s_height, - dx, - dy, - d_width, - d_height, - self.state.image_smoothing_enabled, - self.state.image_smoothing_quality, - &drop_shadow_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, + Self::render_canvas( + &mut self.surface.canvas, &paint, - ); - + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas: &mut Canvas, paint| { + if let Some(drop_shadow_paint) = Self::drop_shadow_paint(&self.state, paint) { + 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, + &drop_shadow_paint, + ); + } + 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(()) } @@ -664,51 +749,60 @@ impl Context { paint: &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( - 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, - &shadow_paint, - )?; - mem::drop(font); - surface.restore(); - } + let width = self.width; 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, + Self::render_canvas( + &mut self.surface.canvas, paint, + self.state.global_composite_operation, + self.width as f32, + self.height as f32, + |canvas, paint| { + if let Some(shadow_paint) = Self::shadow_blur_paint(state, paint) { + canvas.save(); + Self::apply_shadow_offset_matrix_to_canvas( + canvas, + state.shadow_offset_x, + state.shadow_offset_y, + )?; + canvas.draw_text( + text, + x, + y, + max_width, + width as f32, + state.font_style.weight, + state.font_style.stretch as i32, + state.font_style.style, + &font, + state.font_style.size, + &state.font_style.family, + state.text_baseline, + state.text_align, + state.text_direction, + &shadow_paint, + )?; + canvas.restore(); + } + canvas.draw_text( + text, + x, + y, + max_width, + width as f32, + state.font_style.weight, + state.font_style.stretch as i32, + state.font_style.style, + &font, + state.font_style.size, + &state.font_style.family, + state.text_baseline, + state.text_align, + state.text_direction, + paint, + )?; + Ok(()) + }, )?; Ok(()) } @@ -736,20 +830,20 @@ impl Context { Ok(line_metrics) } - fn apply_shadow_offset_matrix( - surface: &mut Surface, + fn apply_shadow_offset_matrix_to_canvas( + canvas: &mut Canvas, shadow_offset_x: f32, shadow_offset_y: f32, ) -> result::Result<(), SkError> { - let current_transform = surface.canvas.get_transform_matrix(); + 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 +954,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; }; } @@ -1164,10 +1259,11 @@ impl CanvasRenderingContext2D { } #[napi] - pub fn clear_rect(&mut self, x: f64, y: f64, width: f64, height: f64) { + pub fn clear_rect(&mut self, x: f64, y: f64, width: f64, height: f64) -> Result<()> { self .context - .clear_rect(x as f32, y as f32, width as f32, height as f32); + .clear_rect(x as f32, y as f32, width as f32, height as f32)?; + Ok(()) } #[napi] diff --git a/src/lib.rs b/src/lib.rs index 9f324ab6..e3ea8344 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![deny(clippy::all)] #![allow(clippy::many_single_char_names)] #![allow(clippy::too_many_arguments)] +#![allow(clippy::new_without_default)] #[macro_use] extern crate napi_derive; @@ -42,6 +43,7 @@ mod gradient; mod image; pub mod path; mod pattern; +pub mod picture_recorder; #[allow(dead_code)] mod sk; mod state; diff --git a/src/picture_recorder.rs b/src/picture_recorder.rs new file mode 100644 index 00000000..efd4d2dc --- /dev/null +++ b/src/picture_recorder.rs @@ -0,0 +1,25 @@ +use crate::sk::{Canvas, SkPicture, SkPictureRecorder}; + +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..97ee4a62 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,25 @@ 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 +2291,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 +3687,41 @@ 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..3a215a18 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,6 @@ use cssparser::RGBA; -use crate::sk::{ImageFilter, Matrix}; +use crate::sk::{BlendMode, ImageFilter, Matrix}; 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(), } } }