Skip to content

Commit

Permalink
feat: support webp output
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Jun 23, 2021
1 parent 0fd133f commit 4948c49
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 42 deletions.
18 changes: 17 additions & 1 deletion __test__/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ test('drawImage-svg without width height should be empty image', async (t) => {
const image = new Image()
image.src = Buffer.from(svgContent.replace('width="128"', '').replace('height="128"', ''))
ctx.drawImage(image, 0, 0)
const output = await canvas.png()
const output = await canvas.encode('png')
const outputData = png.decoders['image/png'](output)
t.deepEqual(outputData.data, Buffer.alloc(outputData.width * outputData.height * 4, 0))
})
Expand Down Expand Up @@ -712,3 +712,19 @@ test('translate', async (t) => {
ctx.fillRect(0, 0, 80, 80)
await snapshotImage(t)
})

test('webp-output', async (t) => {
const { ctx } = 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)
await snapshotImage(t, t.context, 'webp')
})
14 changes: 11 additions & 3 deletions __test__/image-snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { ExecutionContext } from 'ava'
const png = PNG()
const jpeg = JPEG()

export async function snapshotImage<C>(t: ExecutionContext<C>, context = t.context, type: 'png' | 'jpeg' = 'png') {
export async function snapshotImage<C>(
t: ExecutionContext<C>,
context = t.context,
type: 'png' | 'jpeg' | 'avif' | 'webp' | 'heif' = 'png',
) {
// @ts-expect-error
const { canvas } = context
const image = await (type === 'png' ? canvas.png() : canvas.jpeg(100))
const ext = type === 'png' ? 'png' : 'jpg'
const image = await canvas.encode(type, 100)
const ext = type === 'jpeg' ? 'jpg' : type
const p = join(__dirname, 'snapshots', `${t.title}.${ext}`)

async function writeFailureImage() {
Expand All @@ -30,6 +34,10 @@ export async function snapshotImage<C>(t: ExecutionContext<C>, context = t.conte
t.pass()
} else {
const existed = await fs.readFile(p)
if (type !== 'png' && type !== 'jpeg') {
t.pass()
return
}
t.notThrowsAsync(async () => {
const existedPixels =
type === 'png' ? png.decoders['image/png'](existed).data : jpeg.decoders['image/jpeg'](existed).data
Expand Down
Binary file added __test__/snapshots/webp-output.webp
Binary file not shown.
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ export interface Canvas
'getContext' | 'transferControlToOffscreen' | 'addEventListener' | 'removeEventListener'
> {
getContext(contextType: '2d', contextAttributes?: { alpha: boolean }): SKRSContext2D
png(): Promise<Buffer>
jpeg(quality: number): Promise<Buffer>
encode(format: 'webp' | 'jpeg', quality: number): Promise<Buffer>
encode(format: 'png'): Promise<Buffer>
}

export function createCanvas(width: number, height: number): Canvas
Expand Down
3 changes: 3 additions & 0 deletions scripts/build-skia.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ switch (PLATFORM_NAME) {
'\\"-DSK_CODEC_DECODES_PNG\\",' +
'\\"-DSK_ENCODE_JPEG\\",' +
'\\"-DSK_CODEC_DECODES_JPEG\\",' +
'\\"-DSK_HAS_HEIF_LIBRARY\\",' +
'\\"-DSK_SHAPER_HARFBUZZ_AVAILABLE\\"'
ExtraSkiaBuildFlag = 'clang_win=\\"C:\\\\Program Files\\\\LLVM\\"'
break
Expand All @@ -100,6 +101,7 @@ switch (PLATFORM_NAME) {
'"-DSK_CODEC_DECODES_PNG",' +
'"-DSK_ENCODE_JPEG",' +
'"-DSK_CODEC_DECODES_JPEG",' +
'"-DSK_HAS_HEIF_LIBRARY",' +
'"-DSK_SHAPER_HARFBUZZ_AVAILABLE"'
ExtraSkiaBuildFlag = ['skia_use_system_freetype2=false', 'skia_use_fontconfig=false'].join(' ')
break
Expand All @@ -117,6 +119,7 @@ switch (PLATFORM_NAME) {
'"-DSK_CODEC_DECODES_PNG",' +
'"-DSK_ENCODE_JPEG",' +
'"-DSK_CODEC_DECODES_JPEG",' +
'"-DSK_HAS_HEIF_LIBRARY",' +
'"-DSK_SHAPER_HARFBUZZ_AVAILABLE"'
break
default:
Expand Down
12 changes: 6 additions & 6 deletions skia-c/skia_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,15 @@ extern "C"
}
}

void skiac_surface_jpeg_data(skiac_surface *c_surface, skiac_sk_data *data, int quality)
void skiac_surface_encode_data(skiac_surface *c_surface, skiac_sk_data *data, int format, int quality)
{
auto image = SURFACE_CAST->makeImageSnapshot();
auto jpeg_data = image->encodeToData(SkEncodedImageFormat::kJPEG, quality).release();
if (jpeg_data)
auto encoded_data = image->encodeToData((SkEncodedImageFormat)format, quality).release();
if (encoded_data)
{
data->ptr = const_cast<uint8_t *>(jpeg_data->bytes());
data->size = jpeg_data->size();
data->data = reinterpret_cast<skiac_data *>(jpeg_data);
data->ptr = const_cast<uint8_t *>(encoded_data->bytes());
data->size = encoded_data->size();
data->data = reinterpret_cast<skiac_data *>(encoded_data);
}
}

Expand Down
2 changes: 1 addition & 1 deletion skia-c/skia_c.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ extern "C"
void skiac_surface_read_pixels(skiac_surface *c_surface, skiac_surface_data *data);
bool skiac_surface_read_pixels_rect(skiac_surface *c_surface, uint8_t *data, int x, int y, int w, int h);
void skiac_surface_png_data(skiac_surface *c_surface, skiac_sk_data *data);
void skiac_surface_jpeg_data(skiac_surface *c_surface, skiac_sk_data *data, int quality);
void skiac_surface_encode_data(skiac_surface *c_surface, skiac_sk_data *data, int format, int quality);
int skiac_surface_get_alpha_type(skiac_surface *c_surface);
bool skiac_surface_save(skiac_surface *c_surface, const char *path);

Expand Down
23 changes: 17 additions & 6 deletions src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,7 @@ fn get_text_baseline(ctx: CallContext) -> Result<JsString> {
pub enum ContextData {
Png(SurfaceRef),
Jpeg(SurfaceRef, u8),
Webp(SurfaceRef, u8),
}

unsafe impl Send for ContextData {}
Expand All @@ -1869,12 +1870,22 @@ impl Task for ContextData {
"Get png data from surface failed".to_string(),
)
}),
ContextData::Jpeg(surface, quality) => surface.jpeg_data(*quality).ok_or_else(|| {
Error::new(
Status::GenericFailure,
"Get png data from surface failed".to_string(),
)
}),
ContextData::Jpeg(surface, quality) => surface
.encode_data(SkEncodedImageFormat::Jpeg, *quality)
.ok_or_else(|| {
Error::new(
Status::GenericFailure,
"Get jpeg data from surface failed".to_string(),
)
}),
ContextData::Webp(surface, quality) => surface
.encode_data(SkEncodedImageFormat::Webp, *quality)
.ok_or_else(|| {
Error::new(
Status::GenericFailure,
"Get webp data from surface failed".to_string(),
)
}),
}
}

Expand Down
42 changes: 22 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ fn init(mut exports: JsObject, env: Env) -> Result<()> {
canvas_element_constructor,
&[
Property::new(&env, "getContext")?.with_method(get_context),
Property::new(&env, "png")?.with_method(png),
Property::new(&env, "jpeg")?.with_method(jpeg),
Property::new(&env, "encode")?.with_method(encode),
Property::new(&env, "toBuffer")?.with_method(to_buffer),
Property::new(&env, "savePNG")?.with_method(save_png),
],
Expand Down Expand Up @@ -126,29 +125,32 @@ fn get_context(ctx: CallContext) -> Result<JsObject> {
Ok(ctx_js)
}

#[js_function]
fn png(ctx: CallContext) -> Result<JsObject> {
#[js_function(2)]
fn encode(ctx: CallContext) -> Result<JsObject> {
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();

ctx
.env
.spawn(ContextData::Png(ctx2d.surface.reference()))
.map(|p| p.promise_object())
}

#[js_function(1)]
fn jpeg(ctx: CallContext) -> Result<JsObject> {
let quality = ctx.get::<JsNumber>(0)?.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 task = match format.as_str()? {
"webp" => ContextData::Webp(surface_ref, quality),
"jpeg" => ContextData::Jpeg(surface_ref, quality),
"png" => ContextData::Png(surface_ref),
_ => {
return Err(Error::new(
Status::InvalidArg,
format!("{} is not valid format", format.as_str()?),
))
}
};

ctx
.env
.spawn(ContextData::Jpeg(ctx2d.surface.reference(), quality))
.map(|p| p.promise_object())
ctx.env.spawn(task).map(|p| p.promise_object())
}

#[js_function]
Expand Down
25 changes: 22 additions & 3 deletions src/sk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,10 @@ mod ffi {

pub fn skiac_surface_png_data(surface: *mut skiac_surface, data: *mut skiac_sk_data);

pub fn skiac_surface_jpeg_data(
pub fn skiac_surface_encode_data(
surface: *mut skiac_surface,
data: *mut skiac_sk_data,
format: i32,
quality: i32,
);

Expand Down Expand Up @@ -1158,6 +1159,24 @@ impl ToString for TextBaseline {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum SkEncodedImageFormat {
Bmp,
Gif,
Ico,
Jpeg,
Png,
Wbmp,
Webp,
Pm,
Tx,
Astc,
Dng,
Heif,
Avif,
}

pub struct Surface {
ptr: *mut ffi::skiac_surface,
pub(crate) canvas: Canvas,
Expand Down Expand Up @@ -1347,14 +1366,14 @@ impl SurfaceRef {
}

#[inline]
pub fn jpeg_data(&self, quality: u8) -> Option<SurfaceDataRef> {
pub fn encode_data(&self, format: SkEncodedImageFormat, quality: u8) -> Option<SurfaceDataRef> {
unsafe {
let mut data = ffi::skiac_sk_data {
ptr: ptr::null_mut(),
size: 0,
data: ptr::null_mut(),
};
ffi::skiac_surface_jpeg_data(self.0, &mut data, quality as i32);
ffi::skiac_surface_encode_data(self.0, &mut data, format as i32, quality as i32);

if data.ptr.is_null() {
None
Expand Down

1 comment on commit 4948c49

@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: 4948c49 Previous: 0fd133f Ratio
Draw house#@napi-rs/skia 24 ops/sec (±0.62%) 25 ops/sec (±1.63%) 1.04
Draw house#node-canvas 20 ops/sec (±1.22%) 22 ops/sec (±1.77%) 1.10
Draw gradient#@napi-rs/skia 24 ops/sec (±0.86%) 23 ops/sec (±2.51%) 0.96
Draw gradient#node-canvas 19 ops/sec (±0.87%) 21 ops/sec (±2.56%) 1.11

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

Please sign in to comment.