From 11e04a597d2d0153bd5a15da2aabd6bec08efb0e Mon Sep 17 00:00:00 2001 From: Yifeng Wang Date: Fri, 15 Jan 2021 10:20:22 +0800 Subject: [PATCH] feat: add image class poc --- example/image.js | 21 +++++++++++ index.d.ts | 6 ++++ index.js | 7 +++- src/image.rs | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +++ 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 example/image.js diff --git a/example/image.js b/example/image.js new file mode 100644 index 00000000..ac57b51d --- /dev/null +++ b/example/image.js @@ -0,0 +1,21 @@ +const { promises } = require('fs') +const { join } = require('path') + +const { createCanvas, Image } = require('../index') + +const canvas = createCanvas(1024, 768) + +const ctx = canvas.getContext('2d') + +async function main() { + const file = await promises.readFile(join(__dirname, 'tiger.png')) + const image = new Image() + image.src = file + // console.log(image, image.width, image.height) + // ctx.drawImage(image, 0, 0) + + const output = await canvas.png() + await promises.writeFile(join(__dirname, 'tiger-tmp.png'), output) +} + +main() diff --git a/index.d.ts b/index.d.ts index 573627c3..e49b93bf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -31,6 +31,12 @@ export class ImageData { constructor(data: Uint8ClampedArray, sw: number, sh?: number) } +export class Image { + readonly width: number + readonly height: number + src: Buffer +} + export class Path2D { constructor(path?: Path2D | string) diff --git a/index.js b/index.js index f6a37179..a6dd4ffb 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,11 @@ const { loadBinding } = require('@node-rs/helper') * loadBinding helper will load `skia.[PLATFORM].node` from `__dirname` first * If failed to load addon, it will fallback to load from `@napi-rs/skia-[PLATFORM]` */ -const { CanvasRenderingContext2D, CanvasElement, Path2D, ImageData } = loadBinding(__dirname, 'skia', '@napi-rs/skia') +const { CanvasRenderingContext2D, CanvasElement, Path2D, ImageData, Image } = loadBinding( + __dirname, + 'skia', + '@napi-rs/skia', +) CanvasRenderingContext2D.prototype.getImageData = function getImageData(x, y, w, h) { const data = this._getImageData(x, y, w, h) @@ -61,4 +65,5 @@ module.exports = { createCanvas, Path2D, ImageData, + Image, } diff --git a/src/image.rs b/src/image.rs index 22815241..6629e928 100644 --- a/src/image.rs +++ b/src/image.rs @@ -117,3 +117,97 @@ fn image_data_constructor(ctx: CallContext) -> Result { ])?; ctx.env.get_undefined() } + +#[derive(Debug, Clone)] +pub struct Image { + pub(crate) width: u32, + pub(crate) height: u32, + bitmap: *mut u8, +} + +impl Drop for Image { + fn drop(&mut self) { + let len = (self.width * self.height * 4) as usize; + unsafe { Vec::from_raw_parts(self.bitmap, len, len) }; + } +} + +impl Image { + pub fn create_js_class(env: &Env) -> Result { + env.define_class( + "Image", + image_constructor, + &vec![ + Property::new(&env, "width")? + .with_setter(set_noop) + .with_getter(get_width), + Property::new(&env, "height")? + .with_setter(set_noop) + .with_getter(get_height), + Property::new(&env, "src")? + .with_setter(set_src) + .with_getter(get_src), + ], + ) + } +} + +#[js_function] +fn image_constructor(ctx: CallContext) -> Result { + let mut initial_data = ManuallyDrop::new(vec![0u8; 0]); + let data_ptr = initial_data.as_mut_ptr(); + let image = Image { + width: 0u32, + height: 0u32, + bitmap: data_ptr, + }; + let mut this = ctx.this_unchecked::(); + ctx.env.wrap(&mut this, image)?; + ctx.env.get_undefined() +} + +#[js_function] +fn get_width(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let image = ctx.env.unwrap::(&this)?; + + ctx.env.create_double(image.width as f64) +} + +#[js_function] +fn get_height(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let image = ctx.env.unwrap::(&this)?; + + ctx.env.create_double(image.height as f64) +} + +#[js_function(1)] +fn set_noop(ctx: CallContext) -> Result { + ctx.env.get_undefined() +} + +#[js_function] +fn get_src(ctx: CallContext) -> Result { + ctx.env.get_undefined() // TODO +} + +#[js_function(1)] +fn set_src(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let mut image = ctx.env.unwrap::(&this)?; + + let src_arg = ctx.get::(0)?; + let src_data_ab = unsafe { src_arg.cast::() }.into_value()?; + if src_data_ab.typedarray_type != TypedArrayType::Uint8 { + return Err(Error::new( + Status::InvalidArg, + "Image src setter: Argument 1 does not implement interface Buffer." + .to_owned(), + )); + } + let arraybuffer_length = src_data_ab.len(); + println!("buffer length {}", arraybuffer_length); + + ctx.env.get_undefined() +} diff --git a/src/lib.rs b/src/lib.rs index db7953d1..7de5d881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,8 @@ fn init(mut exports: JsObject, env: Env) -> Result<()> { let image_data_class = image::ImageData::create_js_class(&env)?; + let image_class = image::Image::create_js_class(&env)?; + exports.set_named_property("CanvasRenderingContext2D", canvas_rendering_context2d)?; exports.set_named_property("CanvasElement", canvas_element)?; @@ -51,6 +53,8 @@ fn init(mut exports: JsObject, env: Env) -> Result<()> { exports.set_named_property("Path2D", path_class)?; exports.set_named_property("ImageData", image_data_class)?; + + exports.set_named_property("Image", image_class)?; Ok(()) }