Skip to content

Commit

Permalink
feat: support raw pixels output
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Jul 13, 2021
1 parent 0a1e92d commit f502548
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 16 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ jobs:
apt-get install -y nodejs && \
npm install -g pnpm && \
pnpm install --ignore-scripts && \
pnpm test && \
npm test && \
ls -la
"
Expand Down Expand Up @@ -391,7 +391,7 @@ jobs:
apk add nodejs npm && \
npm install -g pnpm && \
pnpm install --ignore-scripts && \
pnpm test
npm test
"
- name: Test failed
Expand Down Expand Up @@ -437,7 +437,7 @@ jobs:
apt-get install -y nodejs && \
npm install -g pnpm && \
pnpm install --ignore-scripts && \
pnpm test && \
npm test && \
ls -la
"
Expand Down
20 changes: 20 additions & 0 deletions __test__/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,3 +804,23 @@ test('webp-output', async (t) => {
ctx.fillRect(0, 0, 80, 80)
await snapshotImage(t, t.context, 'webp')
})

test('raw output', async (t) => {
const { ctx, canvas } = t.context
// Moved square
ctx.translate(110, 30)
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 80, 80)

// Reset current transformation matrix to the identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0)

// Unmoved square
ctx.fillStyle = 'gray'
ctx.fillRect(0, 0, 80, 80)

const output = canvas.data()
const pngFromCanvas = await canvas.encode('png')
const pngOutput = png.decoders['image/png'](pngFromCanvas)
t.deepEqual(output, pngOutput.data)
})
2 changes: 1 addition & 1 deletion __test__/image-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const jpeg = JPEG()
export async function snapshotImage<C>(
t: ExecutionContext<C>,
context = t.context,
type: 'png' | 'jpeg' | 'avif' | 'webp' | 'heif' = 'png',
type: 'png' | 'jpeg' | 'webp' = 'png',
differentRatio = 0.01,
) {
// @ts-expect-error
Expand Down
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,11 @@ export interface Canvas {
width: number
height: number
getContext(contextType: '2d', contextAttributes?: { alpha: boolean }): SKRSContext2D
toBuffer(): Buffer
encodeSync(format: 'webp' | 'jpeg', quality: number): Buffer
encodeSync(format: 'png'): Buffer
toBuffer(mime: 'image/png' | 'image/jpeg' | 'image/webp'): Buffer
// raw pixels
data(): Buffer
encode(format: 'webp' | 'jpeg', quality: number): Promise<Buffer>
encode(format: 'png'): Promise<Buffer>
}
Expand Down
2 changes: 1 addition & 1 deletion src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1938,7 +1938,7 @@ impl Task for ContextData {
output.0.ptr,
output.0.size,
output,
|data_ref: Self::Output, _| data_ref.unref(),
|data_ref: Self::Output, _| mem::drop(data_ref),
)
.map(|value| value.into_raw())
}
Expand Down
103 changes: 94 additions & 9 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#[macro_use]
extern crate napi_derive;

use napi::*;
use std::convert::TryInto;
use std::mem;

use napi::*;

use ctx::{Context, ContextData};
use font::{init_font_regexp, FONT_REGEXP};
Expand Down Expand Up @@ -41,8 +43,10 @@ fn init(mut exports: JsObject, env: Env) -> Result<()> {
&[
Property::new(&env, "getContext")?.with_method(get_context),
Property::new(&env, "encode")?.with_method(encode),
Property::new(&env, "encodeSync")?.with_method(encode_sync),
Property::new(&env, "toBuffer")?.with_method(to_buffer),
Property::new(&env, "savePNG")?.with_method(save_png),
Property::new(&env, "data")?.with_method(data),
],
)?;

Expand Down Expand Up @@ -153,15 +157,101 @@ fn encode(ctx: CallContext) -> Result<JsObject> {
ctx.env.spawn(task).map(|p| p.promise_object())
}

#[js_function]
#[js_function(2)]
fn encode_sync(ctx: CallContext) -> Result<JsBuffer> {
let format = ctx.get::<JsString>(0)?.into_utf8()?;
let quality = if ctx.length == 1 {
100
} else {
ctx.get::<JsNumber>(1)?.get_uint32()? as u8
};
let this = ctx.this_unchecked::<JsObject>();
let ctx_js = this.get_named_property::<JsObject>("ctx")?;
let ctx2d = ctx.env.unwrap::<Context>(&ctx_js)?;
let surface_ref = ctx2d.surface.reference();

if let Some(data_ref) = match format.as_str()? {
"webp" => surface_ref.encode_data(sk::SkEncodedImageFormat::Webp, quality),
"jpeg" => surface_ref.encode_data(sk::SkEncodedImageFormat::Jpeg, quality),
"png" => surface_ref.png_data(),
_ => {
return Err(Error::new(
Status::InvalidArg,
format!("{} is not valid format", format.as_str()?),
))
}
} {
unsafe {
ctx
.env
.create_buffer_with_borrowed_data(
data_ref.0.ptr,
data_ref.0.size,
data_ref,
|data: SurfaceDataRef, _| mem::drop(data),
)
.map(|b| b.into_raw())
}
} else {
Err(Error::new(
Status::InvalidArg,
format!("encode {} output failed", format.as_str()?),
))
}
}

#[js_function(2)]
fn to_buffer(ctx: CallContext) -> Result<JsBuffer> {
let mime = ctx.get::<JsString>(0)?.into_utf8()?;
let quality = if ctx.length == 1 {
100
} else {
ctx.get::<JsNumber>(1)?.get_uint32()? as u8
};
let this = ctx.this_unchecked::<JsObject>();
let ctx_js = this.get_named_property::<JsObject>("ctx")?;
let ctx2d = ctx.env.unwrap::<Context>(&ctx_js)?;
let surface_ref = ctx2d.surface.reference();

if let Some(data_ref) = match mime.as_str()? {
"image/webp" => surface_ref.encode_data(sk::SkEncodedImageFormat::Webp, quality),
"image/jpeg" => surface_ref.encode_data(sk::SkEncodedImageFormat::Jpeg, quality),
"image/png" => surface_ref.png_data(),
_ => {
return Err(Error::new(
Status::InvalidArg,
format!("{} is not valid mime", mime.as_str()?),
))
}
} {
unsafe {
ctx
.env
.create_buffer_with_borrowed_data(
data_ref.0.ptr,
data_ref.0.size,
data_ref,
|data: SurfaceDataRef, _| mem::drop(data),
)
.map(|b| b.into_raw())
}
} else {
Err(Error::new(
Status::InvalidArg,
format!("encode {} output failed", mime.as_str()?),
))
}
}

#[js_function]
fn data(ctx: CallContext) -> Result<JsBuffer> {
let this = ctx.this_unchecked::<JsObject>();
let ctx_js = this.get_named_property::<JsObject>("ctx")?;
let ctx2d = ctx.env.unwrap::<Context>(&ctx_js)?;

let surface_ref = ctx2d.surface.reference();

let data_ref = surface_ref.png_data().ok_or_else(|| {
let (ptr, size) = surface_ref.data().ok_or_else(|| {
Error::new(
Status::GenericFailure,
"Get png data from surface failed".to_string(),
Expand All @@ -170,12 +260,7 @@ fn to_buffer(ctx: CallContext) -> Result<JsBuffer> {
unsafe {
ctx
.env
.create_buffer_with_borrowed_data(
data_ref.0.ptr,
data_ref.0.size,
data_ref,
|data: SurfaceDataRef, _| data.unref(),
)
.create_buffer_with_borrowed_data(ptr, size, 0, noop_finalize)
.map(|value| value.into_raw())
}
}
Expand Down
19 changes: 18 additions & 1 deletion src/sk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,7 @@ impl Surface {
}
}

#[inline]
pub fn save_png(&self, path: &str) -> bool {
let c_path = std::ffi::CString::new(path).unwrap();
unsafe { ffi::skiac_surface_save(self.ptr, c_path.as_ptr()) }
Expand Down Expand Up @@ -1400,6 +1401,20 @@ impl SurfaceRef {
}
}

#[inline]
pub fn data(&self) -> Option<(*const u8, usize)> {
let mut data = ffi::skiac_surface_data {
ptr: ptr::null_mut(),
size: 0,
};
unsafe { ffi::skiac_surface_read_pixels(self.0, &mut data) };
if data.ptr.is_null() {
None
} else {
Some((data.ptr, data.size))
}
}

#[inline]
pub fn encode_data(&self, format: SkEncodedImageFormat, quality: u8) -> Option<SurfaceDataRef> {
unsafe {
Expand Down Expand Up @@ -1455,8 +1470,10 @@ impl SurfaceDataRef {
pub fn slice(&self) -> &'static [u8] {
unsafe { slice::from_raw_parts(self.0.ptr, self.0.size) }
}
}

pub fn unref(self) {
impl Drop for SurfaceDataRef {
fn drop(&mut self) {
unsafe { ffi::skiac_sk_data_destroy(self.0.data) }
}
}
Expand Down

1 comment on commit f502548

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: f502548 Previous: 0a1e92d Ratio
Draw house#skia-canvas 24 ops/sec (±0.26%) 28 ops/sec (±0.07%) 1.17
Draw house#node-canvas 18 ops/sec (±0.3%) 21 ops/sec (±0.23%) 1.17
Draw house#@napi-rs/skia 22 ops/sec (±1%) 25 ops/sec (±0.21%) 1.14
Draw gradient#skia-canvas 23 ops/sec (±0.39%) 27 ops/sec (±0.15%) 1.17
Draw gradient#node-canvas 18 ops/sec (±0.59%) 20 ops/sec (±0.26%) 1.11
Draw gradient#@napi-rs/skia 20 ops/sec (±0.37%) 25 ops/sec (±0.05%) 1.25

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.