From 5b09b4b92712bbce9468f0b874aed3ec7b87a716 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Fri, 20 Nov 2020 18:00:33 +0800 Subject: [PATCH] feat: simple example --- .gitignore | 2 +- .prettierignore | 3 + Cargo.toml | 3 - example/simple.js | 28 ++ example/simple.png | Bin 0 -> 4297 bytes index.js | 16 ++ simple-test.js | 5 - skia | 2 +- skia-c/skia_c.cpp | 80 +++++- skia-c/skia_c.hpp | 23 +- src/ctx.rs | 664 +++++++++++++++++++++++++++++++++++++++++---- src/error.rs | 17 ++ src/gradient.rs | 66 ++++- src/image.rs | 21 +- src/lib.rs | 57 ++-- src/pattern.rs | 30 ++ src/sk.rs | 290 ++++++++++++++++++-- src/state.rs | 49 ++++ yarn.lock | 2 +- 19 files changed, 1223 insertions(+), 135 deletions(-) create mode 100644 example/simple.js create mode 100644 example/simple.png delete mode 100644 simple-test.js create mode 100644 src/error.rs create mode 100644 src/pattern.rs create mode 100644 src/state.rs diff --git a/.gitignore b/.gitignore index 84c87c4b..5030abc0 100644 --- a/.gitignore +++ b/.gitignore @@ -119,4 +119,4 @@ dist /target Cargo.lock -*.node +*.node \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index eb5a316c..ef486f88 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,4 @@ target +depot_tools +skia +skia-c diff --git a/Cargo.toml b/Cargo.toml index 802e2e94..9fc1f4ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,6 @@ napi = {git = "https://github.com/napi-rs/napi-rs"} napi-derive = {git = "https://github.com/napi-rs/napi-rs"} thiserror = "1" -[target.'cfg(all(unix, not(target_env = "musl")))'.dependencies] -jemallocator = {version = "0.3", features = ["disable_initial_exec_tls"]} - [build-dependencies] cc = "1" napi-build = {git = "https://github.com/napi-rs/napi-rs"} diff --git a/example/simple.js b/example/simple.js new file mode 100644 index 00000000..164c87be --- /dev/null +++ b/example/simple.js @@ -0,0 +1,28 @@ +const { writeFileSync } = require('fs') +const { join } = require('path') + +const { createCanvas } = require('../index') + +const canvas = createCanvas(1024, 768) + +const ctx = canvas.getContext('2d') + +ctx.lineWidth = 10 + +// Wall +ctx.strokeRect(75, 140, 150, 110) + +ctx.fillStyle = '#03a9f4' + +// Door +ctx.fillRect(130, 190, 40, 60) + +// Roof +ctx.beginPath() +ctx.moveTo(50, 140) +ctx.lineTo(150, 60) +ctx.lineTo(250, 140) +ctx.closePath() +ctx.stroke() + +writeFileSync(join(__dirname, 'simple.png'), canvas.toBuffer()) diff --git a/example/simple.png b/example/simple.png new file mode 100644 index 0000000000000000000000000000000000000000..85c7a08f254bf4c87e8917d79ba702d7012131b3 GIT binary patch literal 4297 zcmeHJYfuwc6y695g(3_NuNuIPQ#vh*j+Ck$Acz!!GSEQ`kf2?aLS>2+d5##8fhm@b zgVIivA_8Lvc@&5|0u;(->DW4hV1PC}N_Ep(h7g6733MfEvc2~P`>Q|utIhp!_uO;O z<2&Cydv`0|k2~Vz@QMS5VNTIe5eXP(3q%{tehYXE47fRghiz_nbfP^N7wpqM!!Q?F zbi|>=qS~bgA?JRL^t2t1?+v#L{=7qb^niDLY?Q>o_s~Fv+kyNEUi7sr)0iuH|7GLQ zn`z>~!|q-U6=#J!5b{4Zs(kA9fReN~A0E^As;38sGhmT%@NmXdz&RLc}^ypP9eU4xUb2JJm71M>}FZD|- zbD{u-!1wk2XdydGr9c4ep09I}7G`9qYZ{GWisPd!)sg-Zt~;wy2o8)4 zGn$#7Mo^`Nw`f5*@>-6hHoR&( z4HCDU0k)smNVt3@Y7eqd$9_)yHQ~Q4RS&t=}mA3g(ZJM2>LP+ zSlr8KesU59!+AzCAAu$Bh!)x0w)UT+fcn-=TAgBv>lsJY7vF|mz4rzPF0!4F4Ue~Tu?E_i z$$&HlWKKCjPWG0Y?Gmj}hAer+>t7_scgFhLAtpOFBTHSJT^p8Mt0t;T(p+ zdjhj{u`V_^koA4-O!7;fFRAZNWIVg7Dp_9~(***0+D73ehPi~n8RNlM+q(ESIe1tZ zN^TCW3=OlVGC-qx2cy>fC!)1uI-A&09e?Ng18@vm=fPC~zEC^hEW_XzWn%#)>##Cl oWx&dSl>sXQRtEkD4B+)Do-6Ns-di(0@LwK_em5?nBOEXN3r`S5fdBvi literal 0 HcmV?d00001 diff --git a/index.js b/index.js index e9f4667e..e98061b5 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,22 @@ const { CanvasRenderingContext2D, CanvasElement } = loadBinding(__dirname, 'skia function createCanvas(width, height) { const canvasElement = new CanvasElement(width, height) const ctx = new CanvasRenderingContext2D(width, height) + + // napi can not define writable: true but enumerable: false property + Object.defineProperty(ctx, '_fillStyle', { + value: '#000', + configurable: false, + enumerable: false, + writable: true, + }) + + Object.defineProperty(ctx, '_strokeStyle', { + value: '#000', + configurable: false, + enumerable: false, + writable: true, + }) + Object.defineProperty(canvasElement, 'ctx', { value: ctx, enumerable: false, diff --git a/simple-test.js b/simple-test.js deleted file mode 100644 index 29372105..00000000 --- a/simple-test.js +++ /dev/null @@ -1,5 +0,0 @@ -const { sync } = require('./index') - -console.assert(sync(0) === 100, 'Simple test failed') - -console.info('Simple test passed') diff --git a/skia b/skia index 48934885..b05f8069 160000 --- a/skia +++ b/skia @@ -1 +1 @@ -Subproject commit 489348851cca51b23f522734b6db3c785ffdfaed +Subproject commit b05f80697afd6e8cb2389b77c4e20d88caa30393 diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index 68bb2e93..e53d3e4a 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -19,6 +19,7 @@ #define PAINT_CAST reinterpret_cast(c_paint) #define PATH_CAST reinterpret_cast(c_path) #define MATRIX_CAST reinterpret_cast(c_matrix) +#define MASK_FILTER_CAST reinterpret_cast(c_mask_filter) extern "C" { @@ -92,6 +93,19 @@ extern "C" return false; } + void skiac_surface_png_data(skiac_surface *c_surface, skiac_surface_data *data) + { + auto image = SURFACE_CAST->makeImageSnapshot(); + auto png_data = image->encodeToData(); + data->ptr = nullptr; + data->size = 0; + if (png_data) + { + data->ptr = const_cast(png_data->bytes()); + data->size = png_data->size(); + } + } + void skiac_surface_destroy(skiac_surface *c_surface) { // SkSurface is ref counted. @@ -143,7 +157,7 @@ extern "C" if (SURFACE_CAST->peekPixels(&pixmap)) { data->ptr = static_cast(pixmap.writable_addr()); - data->size = static_cast(pixmap.computeByteSize()); + data->size = pixmap.computeByteSize(); } } @@ -184,6 +198,12 @@ extern "C" CANVAS_CAST->translate(dx, dy); } + skiac_matrix *skiac_canvas_get_total_transform_matrix(skiac_canvas *c_canvas) + { + auto martix = CANVAS_CAST->getTotalMatrix(); + return reinterpret_cast(new SkMatrix(martix)); + } + skiac_transform skiac_canvas_get_total_transform(skiac_canvas *c_canvas) { return conv_to_transform(CANVAS_CAST->getTotalMatrix()); @@ -272,6 +292,12 @@ extern "C" return reinterpret_cast(new SkPaint()); } + skiac_paint *skiac_paint_clone(skiac_paint *c_paint) + { + auto cloned_paint = new SkPaint(*PAINT_CAST); + return reinterpret_cast(cloned_paint); + } + void skiac_paint_destroy(skiac_paint *c_paint) { // Will unref() Shader and PathEffect. @@ -332,6 +358,14 @@ extern "C" PAINT_CAST->setPathEffect(pathEffect); } + void skiac_paint_set_mask_filter(skiac_paint *c_paint, skiac_mask_filter *c_mask_filter) + { + sk_sp maskFilter(reinterpret_cast(c_mask_filter)); + maskFilter->ref(); + + PAINT_CAST->setMaskFilter(maskFilter); + } + void skiac_paint_set_style(skiac_paint *c_paint, int style) { PAINT_CAST->setStyle((SkPaint::Style)style); @@ -342,6 +376,11 @@ extern "C" PAINT_CAST->setStrokeWidth(width); } + float skiac_paint_get_stroke_width(skiac_paint *c_paint) + { + return PAINT_CAST->getStrokeWidth(); + } + void skiac_paint_set_stroke_cap(skiac_paint *c_paint, int cap) { PAINT_CAST->setStrokeCap((SkPaint::Cap)cap); @@ -419,6 +458,11 @@ extern "C" PATH_CAST->cubicTo(x1, y1, x2, y2, x3, y3); } + void skiac_path_quad_to(skiac_path *c_path, float cpx, float cpy, float x, float y) + { + PATH_CAST->quadTo(cpx, cpy, x, y); + } + void skiac_path_close(skiac_path *c_path) { PATH_CAST->close(); @@ -583,6 +627,11 @@ extern "C" return reinterpret_cast(new SkMatrix()); } + skiac_matrix *skiac_matrix_clone(skiac_matrix *c_matrix) + { + return reinterpret_cast(new SkMatrix(*MATRIX_CAST)); + } + void skiac_matrix_pre_translate(skiac_matrix *c_matrix, float dx, float dy) { MATRIX_CAST->preTranslate(dx, dy); @@ -597,4 +646,33 @@ extern "C" { return MATRIX_CAST->invert(reinterpret_cast(inverse)); } + + skiac_transform skiac_matrix_to_transform(skiac_matrix *c_matrix) + { + return conv_to_transform(*MATRIX_CAST); + } + + void skiac_matrix_destroy(skiac_matrix *c_matrix) + { + delete MATRIX_CAST; + } + + skiac_mask_filter *skiac_mask_filter_make_blur(float radius) + { + auto mask_filter = SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, radius, false).release(); + if (mask_filter) + { + return reinterpret_cast(mask_filter); + } + else + { + return nullptr; + } + } + + void skiac_mask_filter_destroy(skiac_mask_filter *c_mask_filter) + { + auto mask_filter = MASK_FILTER_CAST; + SkSafeUnref(mask_filter); + } } diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 8ac461d9..5a6e0944 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ typedef struct skiac_path skiac_path; typedef struct skiac_shader skiac_shader; typedef struct skiac_path_effect skiac_path_effect; typedef struct skiac_matrix skiac_matrix; +typedef struct skiac_mask_filter skiac_mask_filter; struct skiac_transform { @@ -39,7 +41,7 @@ struct skiac_point struct skiac_surface_data { uint8_t *ptr; - uint32_t size; + size_t size; }; extern "C" @@ -59,6 +61,7 @@ extern "C" int skiac_surface_get_width(skiac_surface *c_surface); int skiac_surface_get_height(skiac_surface *c_surface); void skiac_surface_read_pixels(skiac_surface *c_surface, skiac_surface_data *data); + void skiac_surface_png_data(skiac_surface *c_surface, skiac_surface_data *data); int skiac_surface_get_alpha_type(skiac_surface *c_surface); bool skiac_surface_save(skiac_surface *c_surface, const char *path); @@ -70,6 +73,7 @@ extern "C" void skiac_canvas_scale(skiac_canvas *c_canvas, float sx, float sy); void skiac_canvas_translate(skiac_canvas *c_canvas, float dx, float dy); skiac_transform skiac_canvas_get_total_transform(skiac_canvas *c_canvas); + skiac_matrix *skiac_canvas_get_total_transform_matrix(skiac_canvas *c_canvas); void skiac_canvas_draw_color(skiac_canvas *c_canvas, float r, float g, float b, float a); void skiac_canvas_draw_path(skiac_canvas *c_canvas, skiac_path *c_path, skiac_paint *c_paint); void skiac_canvas_draw_rect( @@ -97,6 +101,7 @@ extern "C" // Paint skiac_paint *skiac_paint_create(); + skiac_paint *skiac_paint_clone(skiac_paint *c_paint); void skiac_paint_destroy(skiac_paint *c_paint); void skiac_paint_set_style(skiac_paint *c_paint, int style); void skiac_paint_set_color(skiac_paint *c_paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a); @@ -107,11 +112,13 @@ extern "C" int skiac_paint_get_blend_mode(skiac_paint *c_paint); void skiac_paint_set_shader(skiac_paint *c_paint, skiac_shader *c_shader); void skiac_paint_set_stroke_width(skiac_paint *c_paint, float width); + float skiac_paint_get_stroke_width(skiac_paint *c_paint); void skiac_paint_set_stroke_cap(skiac_paint *c_paint, int cap); void skiac_paint_set_stroke_join(skiac_paint *c_paint, int join); void skiac_paint_set_stroke_miter(skiac_paint *c_paint, float miter); float skiac_paint_get_stroke_miter(skiac_paint *c_paint); void skiac_paint_set_path_effect(skiac_paint *c_paint, skiac_path_effect *c_path_effect); + void skiac_paint_set_mask_filter(skiac_paint *c_paint, skiac_mask_filter *c_mask_filter); // Path skiac_path *skiac_path_create(); @@ -126,6 +133,7 @@ extern "C" void skiac_path_cubic_to( skiac_path *c_path, float x1, float y1, float x2, float y2, float x3, float y3); + void skiac_path_quad_to(skiac_path *c_path, float cpx, float cpy, float x, float y); void skiac_path_close(skiac_path *c_path); void skiac_path_add_rect(skiac_path *c_path, float l, float t, float r, float b); void skiac_path_add_circle(skiac_path *c_path, float x, float y, float r); @@ -166,13 +174,26 @@ extern "C" void skiac_shader_destroy(skiac_shader *c_shader); + // Matrix skiac_matrix *skiac_matrix_create(); + skiac_matrix *skiac_matrix_clone(skiac_matrix *c_matrix); + void skiac_matrix_pre_translate(skiac_matrix *c_matrix, float dx, float dy); void skiac_matrix_pre_rotate(skiac_matrix *c_matrix, float degrees); bool skiac_matrix_invert(skiac_matrix *c_matrix, skiac_matrix *inverse); + + skiac_transform skiac_matrix_to_transform(skiac_matrix *c_matrix); + + void skiac_matrix_destroy(skiac_matrix *c_matrix); + + // MaskFilter + + skiac_mask_filter *skiac_mask_filter_make_blur(float radius); + + void skiac_mask_filter_destroy(skiac_mask_filter *c_mask_filter); } #endif // SKIA_CAPI_H diff --git a/src/ctx.rs b/src/ctx.rs index eb01bc07..01260cf2 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -1,14 +1,16 @@ use std::convert::TryInto; use std::f32::consts::PI; +use std::mem; +use std::result; use std::str::FromStr; -use napi::{ - CallContext, Env, Error, JsBoolean, JsFunction, JsNumber, JsObject, JsString, JsUndefined, - Property, Result, Status, -}; +use napi::*; -use super::gradient::CanvasGradient; -use super::sk::*; +use crate::error::SkError; +use crate::gradient::CanvasGradient; +use crate::pattern::Pattern; +use crate::sk::*; +use crate::state::Context2dRenderingState; impl From for Error { fn from(err: SkError) -> Error { @@ -17,11 +19,15 @@ impl From for Error { } pub struct Context { - surface: Surface, + pub(crate) surface: Surface, path: Path, paint: Paint, + pub(crate) states: Vec, } +unsafe impl Send for Context {} +unsafe impl Sync for Context {} + impl Context { #[inline(always)] pub fn create_js_class(env: &Env) -> Result { @@ -40,17 +46,37 @@ impl Context { Property::new(&env, "globalCompositeOperation")? .with_getter(get_global_composite_operation) .with_setter(set_global_composite_operation), + Property::new(&env, "lineWidth")? + .with_setter(set_line_width) + .with_getter(get_line_width), + Property::new(&env, "fillStyle")? + .with_setter(set_fill_style) + .with_getter(get_fill_style), // methods Property::new(&env, "arc")?.with_method(arc), Property::new(&env, "arcTo")?.with_method(arc_to), Property::new(&env, "beginPath")?.with_method(begin_path), Property::new(&env, "bezierCurveTo")?.with_method(bezier_curve_to), - Property::new(&env, "rect")?.with_method(rect), - Property::new(&env, "save")?.with_method(save), - Property::new(&env, "restore")?.with_method(restore), Property::new(&env, "clearRect")?.with_method(clear_rect), + Property::new(&env, "clip")?.with_method(clip), + Property::new(&env, "closePath")?.with_method(close_path), Property::new(&env, "createLinearGradient")?.with_method(create_linear_gradient), Property::new(&env, "createRadialGradient")?.with_method(create_radial_gradient), + Property::new(&env, "lineTo")?.with_method(line_to), + Property::new(&env, "moveTo")?.with_method(move_to), + Property::new(&env, "fill")?.with_method(fill), + Property::new(&env, "fillRect")?.with_method(fill_rect), + Property::new(&env, "quadraticCurveTo")?.with_method(quadratic_curve_to), + Property::new(&env, "rect")?.with_method(rect), + Property::new(&env, "restore")?.with_method(restore), + Property::new(&env, "save")?.with_method(save), + Property::new(&env, "scale")?.with_method(scale), + Property::new(&env, "stroke")?.with_method(stroke), + Property::new(&env, "strokeRect")?.with_method(stroke_rect), + Property::new(&env, "translate")?.with_method(translate), + // getter setter method + Property::new(&env, "getTransform")?.with_method(get_current_transform), + Property::new(&env, "setTransform")?.with_method(set_current_transform), ], ) } @@ -59,10 +85,13 @@ impl Context { pub fn new(width: u32, height: u32) -> Result { let surface = Surface::new_rgba(width, height) .ok_or(Error::from_reason("Create skia surface failed".to_owned()))?; + let mut states = Vec::new(); + states.push(Context2dRenderingState::default()); Ok(Context { surface, path: Path::new(), paint: Paint::default(), + states, }) } @@ -88,34 +117,35 @@ impl Context { #[inline(always)] pub fn arc_to(&mut self, ctrl_x: f32, ctrl_y: f32, to_x: f32, to_y: f32, radius: f32) { - self.scoot(ctrl_x, ctrl_y); self.path.arc_to_tangent(ctrl_x, ctrl_y, to_x, to_y, radius); } #[inline(always)] pub fn begin_path(&mut self) { let new_sub_path = Path::new(); - self.path = new_sub_path; + mem::drop(mem::replace(&mut self.path, new_sub_path)); } #[inline(always)] pub fn bezier_curve_to(&mut self, cp1x: f32, cp1y: f32, cp2x: f32, cp2y: f32, x: f32, y: f32) { - self.scoot(cp1x, cp1y); self.path.cubic_to(cp1x, cp1y, cp2x, cp2y, x, y); } #[inline(always)] pub fn clip(&mut self, path: Option<&mut Path>, fill_rule: FillType) { - let mut clip = match path { + let clip = match path { Some(path) => path, - None => { - &mut self.path - } + None => &mut self.path, }; clip.set_fill_type(fill_rule); self.surface.canvas.set_clip_path(clip); } + #[inline(always)] + pub fn close_path(&mut self) { + self.path.close(); + } + #[inline(always)] pub fn rect(&mut self, x: f32, y: f32, width: f32, height: f32) { self.path.add_rect(x, y, width, height); @@ -124,11 +154,17 @@ impl Context { #[inline(always)] pub fn save(&mut self) { self.surface.save(); + self.states.push(self.states.last().unwrap().clone()); } #[inline(always)] pub fn restore(&mut self) { self.surface.restore(); + if self.states.len() > 1 { + if let Some(state) = self.states.pop() { + mem::drop(state); + }; + } } #[inline(always)] @@ -159,36 +195,6 @@ impl Context { CanvasGradient::create_radial_gradient(x0, y0, r0, x1, y1, r1) } - #[inline(always)] - pub fn set_miter_limit(&mut self, miter: f32) { - self.paint.set_stroke_miter(miter); - } - - #[inline(always)] - pub fn get_miter_limit(&self) -> f32 { - self.paint.get_stroke_miter() - } - - #[inline(always)] - pub fn get_global_alpha(&self) -> f32 { - (self.paint.get_alpha() as f32) / 100.0 - } - - #[inline(always)] - pub fn set_global_alpha(&mut self, alpha: u8) { - self.paint.set_alpha(alpha); - } - - #[inline(always)] - pub fn get_global_composite_operation(&self) -> BlendMode { - self.paint.get_blend_mode() - } - - #[inline(always)] - pub fn set_global_composite_operation(&mut self, blend: BlendMode) { - self.paint.set_blend_mode(blend); - } - #[inline(always)] pub fn ellipse( &mut self, @@ -268,10 +274,300 @@ impl Context { } #[inline(always)] - fn scoot(&mut self, x: f32, y: f32) { - if self.path.is_empty() { - self.path.move_to(x, y); + pub fn line_to(&mut self, x: f32, y: f32) { + self.path.line_to(x, y); + } + + #[inline(always)] + pub fn move_to(&mut self, x: f32, y: f32) { + self.path.move_to(x, y); + } + + #[inline(always)] + pub fn quadratic_curve_to(&mut self, cpx: f32, cpy: f32, x: f32, y: f32) { + self.path.quad_to(cpx, cpy, x, y); + } + + #[inline(always)] + pub fn translate(&mut self, x: f32, y: f32) { + let mut inverted = Matrix::identity(); + inverted.pre_translate(-x, -y); + self.path.transform_matrix(&inverted); + self.surface.canvas.translate(x, y); + } + + #[inline(always)] + 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_paint(&stroke_paint) { + let surface = &mut self.surface; + let last_state = self.states.last().unwrap(); + 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); + + Ok(()) + } + + #[inline(always)] + 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_paint(&fill_paint) { + let surface = &mut self.surface; + let last_state = self.states.last().unwrap(); + 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); + + Ok(()) + } + + #[inline(always)] + pub fn set_miter_limit(&mut self, miter: f32) { + self.paint.set_stroke_miter(miter); + } + + #[inline(always)] + pub fn get_miter_limit(&self) -> f32 { + self.paint.get_stroke_miter() + } + + #[inline(always)] + pub fn get_global_alpha(&self) -> f32 { + (self.paint.get_alpha() as f32) / 255.0 + } + + #[inline(always)] + pub fn set_global_alpha(&mut self, alpha: u8) { + self.paint.set_alpha(alpha); + } + + #[inline(always)] + pub fn get_global_composite_operation(&self) -> BlendMode { + self.paint.get_blend_mode() + } + + #[inline(always)] + pub fn set_global_composite_operation(&mut self, blend: BlendMode) { + self.paint.set_blend_mode(blend); + } + + #[inline(always)] + pub fn set_transform(&mut self, transform: Transform) { + self.surface.canvas.set_transform(transform); + } + + #[inline(always)] + pub fn scale(&mut self, x: f32, y: f32) { + self.surface.canvas.scale(x, y); + } + + #[inline(always)] + pub fn stroke(&mut self, path: Option<&Path>) -> Result<()> { + let p = path.unwrap_or(&self.path); + let stroke_paint = self.stroke_paint()?; + if let Some(shadow_paint) = self.shadow_paint(&stroke_paint) { + let surface = &mut self.surface; + let last_state = self.states.last().unwrap(); + 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); + Ok(()) + } + + #[inline(always)] + pub fn fill( + &mut self, + path: Option<&mut Path>, + fill_rule: FillType, + ) -> result::Result<(), SkError> { + let p = if let Some(p) = path { + p.set_fill_type(fill_rule); + p + } else { + self.path.set_fill_type(fill_rule); + &self.path + }; + let fill_paint = self.fill_paint()?; + if let Some(shadow_paint) = self.shadow_paint(&fill_paint) { + let surface = &mut self.surface; + let last_state = self.states.last().unwrap(); + 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.draw_path(p, &fill_paint); + Ok(()) + } + + #[inline(always)] + pub fn set_line_width(&mut self, width: f32) { + self.paint.set_stroke_width(width); + } + + #[inline(always)] + pub fn get_line_width(&mut self) -> f32 { + self.paint.get_stroke_width() + } + + #[inline(always)] + pub fn set_fill_style(&mut self, pattern: Pattern) -> result::Result<(), SkError> { + let last_state = self.states.last_mut().unwrap(); + match &pattern { + Pattern::Color(color, _) => { + self + .paint + .set_color(color.red, color.green, color.blue, color.alpha); + } + Pattern::Gradient(gradient) => { + let current_transform = self.surface.canvas.get_transform(); + self + .paint + .set_shader(&gradient.get_shader(¤t_transform)?); + } + // TODO, image pattern + Pattern::ImagePattern(image) => {} + }; + last_state.fill_style = pattern; + Ok(()) + } + + #[inline(always)] + fn fill_paint(&self) -> result::Result { + let mut paint = self.paint.clone(); + paint.set_style(PaintStyle::Fill); + let last_state = self.states.last().unwrap(); + match &last_state.fill_style { + Pattern::Color(c, _) => { + let mut color = c.clone(); + color.alpha = + ((color.alpha as f32) * (self.paint.get_alpha() as f32 / 255.0)).round() as u8; + paint.set_color(color.red, color.green, color.blue, color.alpha); + } + Pattern::Gradient(g) => { + let current_transform = self.surface.canvas.get_transform(); + let shader = g.get_shader(¤t_transform)?; + paint.set_color(0, 0, 0, self.paint.get_alpha()); + paint.set_shader(&shader); + } + // TODO, image pattern + Pattern::ImagePattern(p) => {} + }; + if last_state.line_dash_list.len() != 0 { + let path_effect = PathEffect::new_dash_path( + last_state.line_dash_list.as_slice(), + last_state.line_dash_offset, + ) + .ok_or(SkError::Generic(format!( + "Make line dash path effect failed" + )))?; + paint.set_path_effect(&path_effect); + } + Ok(paint) + } + + #[inline(always)] + fn stroke_paint(&self) -> result::Result { + let mut paint = self.paint.clone(); + paint.set_style(PaintStyle::Stroke); + let last_state = self.states.last().unwrap(); + match &last_state.stroke_style { + Pattern::Color(c, _) => { + let mut color = c.clone(); + color.alpha = + ((color.alpha as f32) * (self.paint.get_alpha() as f32 / 255.0)).round() as u8; + paint.set_color(color.red, color.green, color.blue, color.alpha); + } + Pattern::Gradient(g) => { + let current_transform = self.surface.canvas.get_transform(); + let shader = g.get_shader(¤t_transform)?; + paint.set_color(0, 0, 0, self.paint.get_alpha()); + paint.set_shader(&shader); + } + // TODO, image pattern + Pattern::ImagePattern(p) => {} + }; + if last_state.line_dash_list.len() != 0 { + let path_effect = PathEffect::new_dash_path( + last_state.line_dash_list.as_slice(), + last_state.line_dash_offset, + ) + .ok_or(SkError::Generic(format!( + "Make line dash path effect failed" + )))?; + paint.set_path_effect(&path_effect); + } + Ok(paint) + } + + #[inline(always)] + fn shadow_paint(&self, paint: &Paint) -> Option { + let alpha = paint.get_alpha(); + let last_state = self.states.last().unwrap(); + let mut shadow_alpha = last_state.shadow_color.alpha; + shadow_alpha = shadow_alpha * 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 + { + return None; + } + let mut shadow_paint = paint.clone(); + shadow_paint.set_alpha(shadow_alpha); + let blur_effect = MaskFilter::make_blur(last_state.shadow_blur / 2f32)?; + shadow_paint.set_mask_filter(&blur_effect); + Some(shadow_paint) + } + + #[inline(always)] + fn apply_shadow_offset_matrix( + surface: &mut Surface, + shadow_offset_x: f32, + shadow_offset_y: f32, + ) -> result::Result<(), SkError> { + let current_transform = surface.canvas.get_transform_matrix(); + let invert = current_transform + .invert() + .ok_or(SkError::Generic("Invert matrix failed".to_owned()))?; + surface.canvas.concat(invert.into_transform()); + let mut shadow_offset = current_transform.clone(); + shadow_offset.pre_translate(shadow_offset_x, shadow_offset_y); + surface.canvas.concat(shadow_offset.into_transform()); + surface.canvas.concat(current_transform.into_transform()); + Ok(()) } } @@ -304,14 +600,18 @@ fn arc(ctx: CallContext) -> Result { let radius: f64 = ctx.get::(2)?.try_into()?; let start_angle: f64 = ctx.get::(3)?.try_into()?; let end_angle: f64 = ctx.get::(4)?.try_into()?; - let from_end = ctx.get::(5)?; + let from_end = if ctx.length == 6 { + ctx.get::(5)?.get_value()? + } else { + false + }; context_2d.arc( center_x as f32, center_y as f32, radius as f32, start_angle as f32, end_angle as f32, - from_end.get_value()?, + from_end, ); ctx.env.get_undefined() } @@ -370,6 +670,21 @@ fn bezier_curve_to(ctx: CallContext) -> Result { ctx.env.get_undefined() } +#[js_function(4)] +fn quadratic_curve_to(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let cpx: f64 = ctx.get::(0)?.try_into()?; + let cpy: f64 = ctx.get::(1)?.try_into()?; + let x: f64 = ctx.get::(2)?.try_into()?; + let y: f64 = ctx.get::(3)?.try_into()?; + + context_2d.quadratic_curve_to(cpx as f32, cpy as f32, x as f32, y as f32); + + ctx.env.get_undefined() +} + #[js_function(2)] fn clip(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); @@ -383,7 +698,10 @@ fn clip(ctx: CallContext) -> Result { } else { let path = ctx.get::(0)?; let rule = ctx.get::(1)?; - context_2d.clip(Some(ctx.env.unwrap::(&path)?), FillType::from_str(rule.into_utf8()?.as_str()?)?); + context_2d.clip( + Some(ctx.env.unwrap::(&path)?), + FillType::from_str(rule.into_utf8()?.as_str()?)?, + ); }; ctx.env.get_undefined() @@ -403,6 +721,26 @@ fn rect(ctx: CallContext) -> Result { ctx.env.get_undefined() } +#[js_function(2)] +fn fill(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + if ctx.length == 0 { + context_2d.fill(None, FillType::Winding)?; + } else if ctx.length == 1 { + let fill_rule_js = ctx.get::(0)?.into_utf8()?; + context_2d.fill(None, FillType::from_str(fill_rule_js.as_str()?)?)?; + } else { + let path_js = ctx.get::(0)?; + let fill_rule_js = ctx.get::(1)?.into_utf8()?; + let path = ctx.env.unwrap::(&path_js)?; + context_2d.fill(Some(path), FillType::from_str(fill_rule_js.as_str()?)?)?; + }; + + ctx.env.get_undefined() +} + #[js_function] fn save(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); @@ -460,6 +798,41 @@ fn create_radial_gradient(ctx: CallContext) -> Result { radial_gradient.into_js_instance(ctx.env) } +#[js_function] +fn close_path(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.close_path(); + ctx.env.get_undefined() +} + +#[js_function(2)] +fn line_to(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + + context_2d.line_to(x as f32, y as f32); + + ctx.env.get_undefined() +} + +#[js_function(2)] +fn move_to(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + + context_2d.move_to(x as f32, y as f32); + + ctx.env.get_undefined() +} + #[js_function(1)] fn set_miter_limit(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); @@ -477,6 +850,36 @@ fn get_miter_limit(ctx: CallContext) -> Result { ctx.env.create_double(context_2d.get_miter_limit() as f64) } +#[js_function(4)] +fn stroke_rect(ctx: CallContext) -> Result { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + let w: f64 = ctx.get::(2)?.try_into()?; + let h: f64 = ctx.get::(3)?.try_into()?; + + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.stroke_rect(x as f32, y as f32, w as f32, h as f32)?; + + ctx.env.get_undefined() +} + +#[js_function(4)] +fn fill_rect(ctx: CallContext) -> Result { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + let w: f64 = ctx.get::(2)?.try_into()?; + let h: f64 = ctx.get::(3)?.try_into()?; + + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.fill_rect(x as f32, y as f32, w as f32, h as f32)?; + + ctx.env.get_undefined() +} + #[js_function(1)] fn set_global_alpha(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); @@ -527,3 +930,158 @@ fn get_global_composite_operation(ctx: CallContext) -> Result { .env .create_string(context_2d.get_global_composite_operation().as_str()) } + +#[js_function] +fn get_current_transform(ctx: CallContext) -> Result { + let mut transform_object = ctx.env.create_object()?; + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let current_transform = context_2d.surface.canvas.get_transform(); + + transform_object.set_named_property("a", ctx.env.create_double(current_transform.a as f64)?)?; + transform_object.set_named_property("b", ctx.env.create_double(current_transform.b as f64)?)?; + transform_object.set_named_property("c", ctx.env.create_double(current_transform.c as f64)?)?; + transform_object.set_named_property("d", ctx.env.create_double(current_transform.d as f64)?)?; + transform_object.set_named_property("e", ctx.env.create_double(current_transform.e as f64)?)?; + transform_object.set_named_property("f", ctx.env.create_double(current_transform.f as f64)?)?; + Ok(transform_object) +} + +#[js_function(6)] +fn set_current_transform(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let transform = if ctx.length == 1 { + let transform_object = ctx.get::(0)?; + let a: f64 = transform_object + .get_named_property::("a")? + .try_into()?; + let b: f64 = transform_object + .get_named_property::("b")? + .try_into()?; + let c: f64 = transform_object + .get_named_property::("c")? + .try_into()?; + let d: f64 = transform_object + .get_named_property::("d")? + .try_into()?; + let e: f64 = transform_object + .get_named_property::("e")? + .try_into()?; + let f: f64 = transform_object + .get_named_property::("f")? + .try_into()?; + Transform::new(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32) + } else if ctx.length == 6 { + let a: f64 = ctx.get::(0)?.try_into()?; + let b: f64 = ctx.get::(1)?.try_into()?; + let c: f64 = ctx.get::(2)?.try_into()?; + let d: f64 = ctx.get::(3)?.try_into()?; + let e: f64 = ctx.get::(4)?.try_into()?; + let f: f64 = ctx.get::(5)?.try_into()?; + Transform::new(a as f32, b as f32, c as f32, d as f32, e as f32, f as f32) + } else { + return Err(Error::new( + Status::InvalidArg, + format!("Invalid argument length in setTransform"), + )); + }; + + context_2d.set_transform(transform); + + ctx.env.get_undefined() +} + +#[js_function(2)] +fn scale(ctx: CallContext) -> Result { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.scale(x as f32, y as f32); + + ctx.env.get_undefined() +} + +#[js_function(1)] +fn stroke(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let path = if ctx.length == 0 { + None + } else { + let js_path = ctx.get::(0)?; + let path = ctx.env.unwrap::(&js_path)?; + Some(path) + }; + + context_2d.stroke(path.as_deref())?; + + ctx.env.get_undefined() +} + +#[js_function(2)] +fn translate(ctx: CallContext) -> Result { + let x: f64 = ctx.get::(0)?.try_into()?; + let y: f64 = ctx.get::(1)?.try_into()?; + + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.translate(x as f32, y as f32); + + ctx.env.get_undefined() +} + +#[js_function(1)] +fn set_line_width(ctx: CallContext) -> Result { + let width: f64 = ctx.get::(0)?.try_into()?; + + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + context_2d.set_line_width(width as f32); + + ctx.env.get_undefined() +} + +#[js_function] +fn get_line_width(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + ctx.env.create_double(context_2d.get_line_width() as f64) +} + +#[js_function(1)] +fn set_fill_style(ctx: CallContext) -> Result { + let mut this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&this)?; + + let js_fill_style = ctx.get::(0)?; + + match js_fill_style.get_type()? { + ValueType::String => { + let js_color = + unsafe { JsString::from_raw_unchecked(ctx.env.raw(), js_fill_style.raw()) }.into_utf8()?; + context_2d.set_fill_style(Pattern::from_color(js_color.as_str()?)?)?; + } + // TODO, image and gradient + ValueType::External => {} + _ => return Err(Error::new(Status::InvalidArg, format!("Invalid fillStyle"))), + } + + this.set_named_property("_fillStyle", js_fill_style)?; + + ctx.env.get_undefined() +} + +#[js_function] +fn get_fill_style(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + this.get_named_property("_fillStyle") +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..ac08dc23 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,17 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SkError { + #[error("[`{0}`] is not valid Blend value")] + StringToBlendError(String), + #[error("[`{0}`] is not valid FillRule value")] + StringToFillRuleError(String), + #[error("[`{0}`] is not valid TextAlign value")] + StringToTextAlignError(String), + #[error("[`{0}`] is not valid TextBaseline value")] + StringToTextBaselineError(String), + #[error("[`{0}`] is not valid FilterQuality value")] + StringToFilterQualityError(String), + #[error("[`{0}`]")] + Generic(String), +} diff --git a/src/gradient.rs b/src/gradient.rs index 9a9bdc7b..d47f8943 100644 --- a/src/gradient.rs +++ b/src/gradient.rs @@ -1,12 +1,14 @@ use std::convert::TryInto; +use std::result; use cssparser::{Color as CSSColor, Parser, ParserInput}; use napi::{ CallContext, Env, Error, JsNumber, JsObject, JsString, JsUndefined, Property, Result, Status, }; -use super::sk::*; +use crate::{error::SkError, sk::*}; +#[derive(Debug, Clone)] pub enum CanvasGradient { Linear(LinearGradient), Radial(TwoPointConicalGradient), @@ -75,16 +77,60 @@ impl CanvasGradient { } #[inline(always)] - pub(crate) fn shader(self) -> Result { + pub(crate) fn get_shader( + &self, + current_transform: &Transform, + ) -> result::Result { match self { - Self::Linear(linear_gradient) => Ok(Shader::new_linear_gradient(&linear_gradient).ok_or( - Error::from_reason("Create linear gradient failed".to_owned()), - )?), - Self::Radial(radial_gradient) => Ok( - Shader::new_two_point_conical_gradient(&radial_gradient).ok_or(Error::from_reason( - "Create radial gradient failed".to_owned(), - ))?, - ), + Self::Linear(ref linear_gradient) => { + let (x1, y1) = linear_gradient.start_point; + let (x2, y2) = linear_gradient.end_point; + let pt_arr: [f32; 4] = [x1, y1, x2, y2]; + let pts = current_transform.map_points(&pt_arr); + let sx1 = pts[0]; + let sy1 = pts[1]; + let sx2 = pts[2]; + let sy2 = pts[3]; + Ok( + Shader::new_linear_gradient(&LinearGradient { + start_point: (sx1, sy1), + end_point: (sx2, sy2), + base: linear_gradient.base.clone(), + }) + .ok_or(SkError::Generic("Create linear gradient failed".to_owned()))?, + ) + } + Self::Radial(ref radial_gradient) => { + let (x1, y1) = radial_gradient.start; + let (x2, y2) = radial_gradient.end; + let (r1, r2) = radial_gradient.start; + let pt_arr: [f32; 4] = [x1, y1, x2, y2]; + let pts = current_transform.map_points(&pt_arr); + let sx1 = pts[0]; + let sy1 = pts[1]; + let sx2 = pts[2]; + let sy2 = pts[3]; + + let sx = current_transform.a; + let sy = current_transform.e; + let scale_factor = (f32::abs(sx) + f32::abs(sy)) / 2f32; + + let sr1 = r1 * scale_factor; + let sr2 = r2 * scale_factor; + + let new_radial_gradient = TwoPointConicalGradient { + start: (sx1, sy1), + end: (sx2, sy2), + start_radius: sr1, + end_radius: sr2, + base: radial_gradient.base.clone(), + }; + + Ok( + Shader::new_two_point_conical_gradient(&new_radial_gradient) + .ok_or(SkError::Generic("Create radial gradient failed".to_owned()))?, + ) + } } } } diff --git a/src/image.rs b/src/image.rs index ddf8e538..b9c8fe7f 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,21 +1,4 @@ -use std::path::Path; - -use napi::{Env, Error, JsBufferValue, JsObject, Ref, Result, Task}; - +#[derive(Debug, Clone)] pub struct Image { - data: Ref, -} - -impl Task for Image { - type Output = Vec; - type JsValue = JsObject; - - fn compute(&mut self) -> Result { - Ok(vec![]) - } - - fn resolve(self, env: Env, output: Self::Output) -> Result { - self.data.unref(env)?; - env.create_object() - } + data: Vec, } diff --git a/src/lib.rs b/src/lib.rs index 48b0cd44..48ba13ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,32 +3,33 @@ extern crate napi_derive; use napi::*; +use ctx::Context; + mod ctx; +mod error; mod gradient; mod image; +mod pattern; mod sk; +mod state; -#[cfg(all(unix, not(target_env = "musl")))] -#[global_allocator] -static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; - -register_module!(skia, init); - -fn init(module: &mut Module) -> Result<()> { - let canvas_element = module.env.define_class( +#[module_exports] +fn init(mut exports: JsObject, env: Env) -> Result<()> { + let canvas_element = env.define_class( "CanvasElement", canvas_element_constructor, - &[Property::new(&module.env, "getContext")?.with_method(get_context)], + &[ + Property::new(&env, "getContext")?.with_method(get_context), + Property::new(&env, "toBuffer")?.with_method(to_buffer), + Property::new(&env, "savePNG")?.with_method(save_png), + ], )?; - let canvas_rendering_context2d = ctx::Context::create_js_class(&module.env)?; - module - .exports - .set_named_property("CanvasRenderingContext2D", canvas_rendering_context2d)?; + let canvas_rendering_context2d = ctx::Context::create_js_class(&env)?; + + exports.set_named_property("CanvasRenderingContext2D", canvas_rendering_context2d)?; - module - .exports - .set_named_property("CanvasElement", canvas_element)?; + exports.set_named_property("CanvasElement", canvas_element)?; Ok(()) } @@ -54,3 +55,27 @@ fn get_context(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); this.get_named_property("ctx") } + +#[js_function] +fn to_buffer(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let ctx_js = this.get_named_property::("ctx")?; + let ctx2d = ctx.env.unwrap::(&ctx_js)?; + + ctx + .env + .create_buffer_with_data(ctx2d.surface.png_data().to_vec()) + .map(|b| b.into_raw()) +} + +#[js_function(1)] +fn save_png(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let path = ctx.get::(0)?; + let ctx_js = this.get_named_property::("ctx")?; + let ctx2d = ctx.env.unwrap::(&ctx_js)?; + + ctx2d.surface.save_png(path.into_utf8()?.as_str()?); + + ctx.env.get_undefined() +} diff --git a/src/pattern.rs b/src/pattern.rs new file mode 100644 index 00000000..19787236 --- /dev/null +++ b/src/pattern.rs @@ -0,0 +1,30 @@ +use cssparser::{Color as CSSColor, Parser, ParserInput, RGBA}; + +use crate::error::SkError; +use crate::gradient::CanvasGradient; +use crate::image::Image; + +#[derive(Debug, Clone)] +pub enum Pattern { + Color(RGBA, String), + Gradient(CanvasGradient), + ImagePattern(Image), +} + +impl Pattern { + #[inline(always)] + pub fn from_color(color_str: &str) -> Result { + let mut parser_input = ParserInput::new(color_str); + let mut parser = Parser::new(&mut parser_input); + let color = CSSColor::parse(&mut parser) + .map_err(|e| SkError::Generic(format!("Invalid color {:?}", e)))?; + match color { + CSSColor::CurrentColor => { + return Err(SkError::Generic( + "Color should not be `currentcolor` keyword".to_owned(), + )) + } + CSSColor::RGBA(rgba) => Ok(Pattern::Color(rgba, color_str.to_owned())), + } + } +} diff --git a/src/sk.rs b/src/sk.rs index 90e56482..07d16939 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -1,8 +1,9 @@ use std::ops::{Deref, DerefMut}; +use std::ptr; use std::slice; use std::str::FromStr; -use thiserror::Error; +use crate::error::SkError; mod ffi { #[repr(C)] @@ -47,6 +48,12 @@ mod ffi { _unused: [u8; 0], } + #[repr(C)] + #[derive(Copy, Clone, Debug)] + pub struct skiac_mask_filter { + _unused: [u8; 0], + } + #[repr(C)] #[derive(Copy, Clone, Debug)] pub struct skiac_transform { @@ -69,7 +76,7 @@ mod ffi { #[derive(Copy, Clone, Debug)] pub struct skiac_surface_data { pub ptr: *mut u8, - pub size: u32, + pub size: usize, } extern "C" { @@ -101,6 +108,11 @@ mod ffi { pub fn skiac_surface_read_pixels(surface: *mut skiac_surface, data: *mut skiac_surface_data); + pub fn skiac_surface_png_data( + surface: *mut skiac_surface, + data: *mut skiac_surface_data, + ) -> *const u8; + pub fn skiac_surface_get_alpha_type(surface: *mut skiac_surface) -> i32; pub fn skiac_canvas_clear(canvas: *mut skiac_canvas, color: u32); @@ -117,6 +129,8 @@ mod ffi { pub fn skiac_canvas_get_total_transform(canvas: *mut skiac_canvas) -> skiac_transform; + pub fn skiac_canvas_get_total_transform_matrix(canvas: *mut skiac_canvas) -> *mut skiac_matrix; + pub fn skiac_canvas_draw_color(canvas: *mut skiac_canvas, r: f32, g: f32, b: f32, a: f32); pub fn skiac_canvas_draw_path( @@ -157,7 +171,6 @@ mod ffi { pub fn skiac_canvas_reset_transform(canvas: *mut skiac_canvas); pub fn skiac_canvas_clip_rect(canvas: *mut skiac_canvas, x: f32, y: f32, w: f32, h: f32); - pub fn skiac_canvas_clip_path(canvas: *mut skiac_canvas, path: *mut skiac_path); pub fn skiac_canvas_save(canvas: *mut skiac_canvas); @@ -166,6 +179,8 @@ mod ffi { pub fn skiac_paint_create() -> *mut skiac_paint; + pub fn skiac_paint_clone(source: *mut skiac_paint) -> *mut skiac_paint; + pub fn skiac_paint_destroy(paint: *mut skiac_paint); pub fn skiac_paint_set_style(paint: *mut skiac_paint, style: i32); @@ -184,6 +199,8 @@ mod ffi { pub fn skiac_paint_set_stroke_width(paint: *mut skiac_paint, width: f32); + pub fn skiac_paint_get_stroke_width(paint: *mut skiac_paint) -> f32; + pub fn skiac_paint_set_stroke_cap(paint: *mut skiac_paint, cap: i32); pub fn skiac_paint_set_stroke_join(paint: *mut skiac_paint, join: i32); @@ -196,11 +213,17 @@ mod ffi { path_effect: *mut skiac_path_effect, ); + pub fn skiac_paint_set_mask_filter( + paint: *mut skiac_paint, + mask_filter: *mut skiac_mask_filter, + ); + pub fn skiac_path_create() -> *mut skiac_path; pub fn skiac_path_clone(path: *mut skiac_path) -> *mut skiac_path; - pub fn skiac_path_op(c_path_one: *mut skiac_path, c_path_two: *mut skiac_path, op: i32) -> bool; + pub fn skiac_path_op(c_path_one: *mut skiac_path, c_path_two: *mut skiac_path, op: i32) + -> bool; pub fn skiac_path_destroy(path: *mut skiac_path); @@ -240,6 +263,8 @@ mod ffi { y3: f32, ); + pub fn skiac_path_quad_to(path: *mut skiac_path, cpx: f32, cpy: f32, x: f32, y: f32); + pub fn skiac_path_close(path: *mut skiac_path); pub fn skiac_path_add_rect(path: *mut skiac_path, l: f32, t: f32, r: f32, b: f32); @@ -293,20 +318,22 @@ mod ffi { pub fn skiac_matrix_create() -> *mut skiac_matrix; + pub fn skiac_matrix_clone(matrix: *mut skiac_matrix) -> *mut skiac_matrix; + pub fn skiac_matrix_pre_translate(matrix: *mut skiac_matrix, dx: f32, dy: f32); pub fn skiac_matrix_pre_rotate(matrix: *mut skiac_matrix, degrees: f32); pub fn skiac_matrix_invert(matrix: *mut skiac_matrix, inverse: *mut skiac_matrix) -> bool; - } -} -#[derive(Error, Debug)] -pub enum SkError { - #[error("[`{0}`] is not valid Blend value")] - StringToBlendError(String), - #[error("[`{0}`] is not valid FillRule value")] - StringToFillRuleError(String), + pub fn skiac_matrix_to_transform(matrix: *mut skiac_matrix) -> skiac_transform; + + pub fn skiac_matrix_destroy(matrix: *mut skiac_matrix); + + pub fn skiac_mask_filter_make_blur(radius: f32) -> *mut skiac_mask_filter; + + pub fn skiac_mask_filter_destroy(mask_filter: *mut skiac_mask_filter); + } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] @@ -537,6 +564,30 @@ pub enum FilterQuality { High = 3, } +impl FilterQuality { + pub fn as_str(&self) -> &'static str { + match self { + Self::High => "high", + Self::Low => "low", + Self::Medium => "medium", + Self::None => "", + } + } +} + +impl FromStr for FilterQuality { + type Err = SkError; + + fn from_str(s: &str) -> Result { + match s { + "low" => Ok(Self::Low), + "medium" => Ok(Self::Medium), + "high" => Ok(Self::High), + _ => Err(SkError::StringToFilterQualityError(s.to_owned())), + } + } +} + /// Describes how to interpret the alpha component of a pixel. /// /// A pixel may be opaque, or alpha, describing multiple levels of transparency. @@ -561,16 +612,97 @@ pub enum AlphaType { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub enum PathOp { - Difference, // subtract the op path from the first path - Intersect, // intersect the two paths - Union, // union (inclusive-or) the two paths - XOR, // exclusive-or the two paths - ReverseDifference, // subtract the first path from the op path + Difference, // subtract the op path from the first path + Intersect, // intersect the two paths + Union, // union (inclusive-or) the two paths + XOR, // exclusive-or the two paths + ReverseDifference, // subtract the first path from the op path +} + +#[derive(Debug, Clone)] +pub enum TextAlign { + Left, + Right, + Center, + Start, + End, +} + +impl TextAlign { + pub fn as_str(&self) -> &'static str { + match self { + Self::Start => "start", + Self::Center => "center", + Self::End => "end", + Self::Left => "left", + Self::Right => "right", + } + } +} + +impl FromStr for TextAlign { + type Err = SkError; + + fn from_str(s: &str) -> Result { + match s { + "center" => Ok(TextAlign::Center), + "end" => Ok(TextAlign::End), + "left" => Ok(TextAlign::Left), + "right" => Ok(TextAlign::Right), + "start" => Ok(TextAlign::Start), + _ => Err(SkError::StringToTextAlignError(s.to_owned())), + } + } +} + +#[derive(Debug, Clone)] +pub enum TextBaseline { + Top, + Hanging, + Middle, + Alphabetic, + Ideographic, + Bottom, +} + +impl FromStr for TextBaseline { + type Err = SkError; + + fn from_str(s: &str) -> Result { + match s { + "top" => Ok(Self::Top), + "hanging" => Ok(Self::Hanging), + "middle" => Ok(Self::Middle), + "alphabetic" => Ok(Self::Alphabetic), + "bottom" => Ok(Self::Bottom), + "ideographic" => Ok(Self::Ideographic), + _ => Err(SkError::StringToTextBaselineError(s.to_owned())), + } + } +} + +impl TextBaseline { + pub fn as_str(&self) -> &'static str { + match self { + Self::Bottom => "bottom", + Self::Alphabetic => "alphabetic", + Self::Hanging => "hanging", + Self::Ideographic => "ideographic", + Self::Middle => "middle", + Self::Top => "top", + } + } +} + +impl ToString for TextBaseline { + fn to_string(&self) -> String { + self.as_str().to_owned() + } } pub struct Surface { ptr: *mut ffi::skiac_surface, - pub (crate) canvas: Canvas, + pub(crate) canvas: Canvas, } impl Surface { @@ -661,6 +793,19 @@ impl Surface { } } + #[inline] + pub fn png_data(&self) -> &[u8] { + unsafe { + let mut data = ffi::skiac_surface_data { + ptr: ptr::null_mut(), + size: 0, + }; + ffi::skiac_surface_png_data(self.ptr, &mut data); + + slice::from_raw_parts(data.ptr, data.size) + } + } + #[inline] pub fn data(&self) -> SurfaceData { SurfaceData { @@ -672,7 +817,7 @@ impl Surface { pub fn data_mut(&mut self) -> SurfaceDataMut { unsafe { let mut data = ffi::skiac_surface_data { - ptr: std::ptr::null_mut(), + ptr: ptr::null_mut(), size: 0, }; ffi::skiac_surface_read_pixels(self.ptr, &mut data); @@ -814,6 +959,11 @@ impl Canvas { unsafe { ffi::skiac_canvas_get_total_transform(self.0).into() } } + #[inline] + pub fn get_transform_matrix(&self) -> Matrix { + Matrix(unsafe { ffi::skiac_canvas_get_total_transform_matrix(self.0) }) + } + #[inline] pub fn reset_transform(&mut self) { unsafe { @@ -909,8 +1059,15 @@ impl Canvas { } } +#[derive(Debug)] pub struct Paint(*mut ffi::skiac_paint); +impl Clone for Paint { + fn clone(&self) -> Self { + Paint(unsafe { ffi::skiac_paint_clone(self.0) }) + } +} + impl Paint { #[inline] pub fn new() -> Paint { @@ -976,6 +1133,11 @@ impl Paint { } } + #[inline] + pub fn get_stroke_width(&mut self) -> f32 { + unsafe { ffi::skiac_paint_get_stroke_width(self.0) } + } + #[inline] pub fn set_stroke_cap(&mut self, cap: StrokeCap) { unsafe { @@ -1008,13 +1170,26 @@ impl Paint { ffi::skiac_paint_set_path_effect(self.0, path_effect.0); } } + + #[inline] + pub fn set_mask_filter(&mut self, mask_filter: &MaskFilter) { + unsafe { + ffi::skiac_paint_set_mask_filter(self.0, mask_filter.0); + } + } } impl Default for Paint { fn default() -> Self { let mut paint = Self::new(); - paint.set_color(0, 0, 0, 0); + paint.set_color(255, 255, 255, 255); paint.set_stroke_miter(10.0); + paint.set_anti_alias(true); + paint.set_stroke_cap(StrokeCap::Butt); + paint.set_stroke_join(StrokeJoin::Miter); + paint.set_stroke_width(1.0); + paint.set_blend_mode(BlendMode::SourceOver); + paint.set_alpha(255); paint } } @@ -1043,7 +1218,7 @@ impl Path { #[inline] pub fn op(&self, other: &Path, op: PathOp) -> bool { - unsafe { ffi::skiac_path_op(self.0, other.0, op as i32) } + unsafe { ffi::skiac_path_op(self.0, other.0, op as i32) } } #[inline] @@ -1104,6 +1279,13 @@ impl Path { } } + #[inline] + pub fn quad_to(&mut self, cpx: f32, cpy: f32, x: f32, y: f32) { + unsafe { + ffi::skiac_path_quad_to(self.0, cpx, cpy, x, y); + } + } + #[inline] pub fn close(&mut self) { unsafe { @@ -1150,6 +1332,7 @@ impl Drop for Path { } } +#[derive(Debug, Clone)] pub struct Gradient { pub colors: Vec, pub positions: Vec, @@ -1157,12 +1340,14 @@ pub struct Gradient { pub transform: Transform, } +#[derive(Debug, Clone)] pub struct LinearGradient { pub start_point: (f32, f32), pub end_point: (f32, f32), pub base: Gradient, } +#[derive(Debug, Clone)] pub struct TwoPointConicalGradient { pub start: (f32, f32), pub start_radius: f32, @@ -1307,10 +1492,14 @@ impl Matrix { unsafe { ffi::skiac_matrix_pre_rotate(self.0, degrees) }; } - #[must_use] + #[inline(always)] + pub fn into_transform(self) -> Transform { + unsafe { ffi::skiac_matrix_to_transform(self.0) }.into() + } + #[inline(always)] pub fn invert(&self) -> Option { - let mut m = Matrix::identity(); + let m = Matrix::identity(); if unsafe { ffi::skiac_matrix_invert(self.0, m.0) } { Some(m) } else { @@ -1319,6 +1508,18 @@ impl Matrix { } } +impl Clone for Matrix { + fn clone(&self) -> Self { + Matrix(unsafe { ffi::skiac_matrix_clone(self.0) }) + } +} + +impl Drop for Matrix { + fn drop(&mut self) { + unsafe { ffi::skiac_matrix_destroy(self.0) }; + } +} + #[derive(Copy, Clone, PartialEq, Debug)] pub struct Transform { pub a: f32, @@ -1333,6 +1534,26 @@ impl Transform { pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self { Transform { a, b, c, d, e, f } } + + #[inline] + pub fn map_points(&self, pt_arr: &[f32]) -> Vec { + let mut i = 0usize; + let mut result_arr = Vec::with_capacity(pt_arr.len() + 2); + while i < pt_arr.len() { + let x = pt_arr[i]; + let y = pt_arr[i + 1]; + // Gx+Hy+I + let denom = 1f32; + // Ax+By+C + let x_trans = self.a * x + self.b * y + self.c; + // Dx+Ey+F + let y_trans = self.d * x + self.e * y + self.e; + result_arr[i] = x_trans / denom; + result_arr[i + 1] = y_trans / denom; + i += 2; + } + result_arr + } } impl Default for Transform { @@ -1362,7 +1583,7 @@ impl From for ffi::skiac_transform { } } -impl <'a> From<&'a Transform> for ffi::skiac_transform { +impl<'a> From<&'a Transform> for ffi::skiac_transform { #[inline] fn from(ts: &'a Transform) -> Self { ffi::skiac_transform { @@ -1375,3 +1596,24 @@ impl <'a> From<&'a Transform> for ffi::skiac_transform { } } } + +#[repr(transparent)] +#[derive(Debug)] +pub struct MaskFilter(*mut ffi::skiac_mask_filter); + +impl MaskFilter { + pub fn make_blur(radius: f32) -> Option { + let raw_ptr = unsafe { ffi::skiac_mask_filter_make_blur(radius) }; + if raw_ptr.is_null() { + None + } else { + Some(MaskFilter(raw_ptr)) + } + } +} + +impl Drop for MaskFilter { + fn drop(&mut self) { + unsafe { ffi::skiac_mask_filter_destroy(self.0) }; + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 00000000..9aeb1e3b --- /dev/null +++ b/src/state.rs @@ -0,0 +1,49 @@ +use cssparser::RGBA; + +use super::pattern::Pattern; +use super::sk::{BlendMode, FilterQuality, Paint, TextAlign, TextBaseline}; + +#[derive(Debug, Clone)] +pub struct Context2dRenderingState { + pub line_dash_list: Vec, + pub stroke_style: Pattern, + pub fill_style: Pattern, + pub shadow_offset_x: f32, + pub shadow_offset_y: f32, + pub shadow_blur: f32, + pub shadow_color: RGBA, + pub global_alpha: f32, + pub line_dash_offset: f32, + pub global_composite_operation: BlendMode, + pub image_smoothing_enabled: bool, + pub image_smoothing_quality: FilterQuality, + pub paint: Paint, + pub font: String, + pub text_align: TextAlign, + pub text_baseline: TextBaseline, +} + +impl Default for Context2dRenderingState { + fn default() -> Context2dRenderingState { + Context2dRenderingState { + line_dash_list: Vec::new(), + stroke_style: Pattern::Color(RGBA::new(0, 0, 0, 255), "#000".to_owned()), + fill_style: Pattern::Color(RGBA::new(0, 0, 0, 255), "#000".to_owned()), + shadow_offset_x: 0f32, + shadow_offset_y: 0f32, + shadow_blur: 0f32, + shadow_color: RGBA::new(0, 0, 0, 0), + /// 0.0 ~ 1.0 + global_alpha: 1.0, + /// A float specifying the amount of the line dash offset. The default value is 0.0. + line_dash_offset: 0.0, + global_composite_operation: BlendMode::SourceOver, + image_smoothing_enabled: true, + image_smoothing_quality: FilterQuality::Low, + paint: Paint::default(), + font: "10px monospace".to_owned(), + text_align: TextAlign::Start, + text_baseline: TextBaseline::Alphabetic, + } + } +} diff --git a/yarn.lock b/yarn.lock index f5b1b368..132907a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1042,7 +1042,7 @@ debug@^2.6.9: debug@^4.0.1, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2"