Skip to content

Commit

Permalink
feat: scale svg image if need
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Aug 11, 2021
1 parent a042630 commit 72c404c
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 27 deletions.
12 changes: 12 additions & 0 deletions __test__/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,18 @@ test('drawImage-svg-with-only-viewBox', async (t) => {
await snapshotImage(t)
})

test('drawImage-svg-resize', async (t) => {
const { ctx, canvas } = t.context
const filePath = './resize.svg'
const file = await promises.readFile(join(__dirname, filePath))
const image = new Image()
image.src = file
image.width = 100
image.height = 100
ctx.drawImage(image, 0, 0)
await snapshotImage(t, { canvas, ctx }, 'png', 0.1)
})

test.skip('drawImage-svg-with-css', async (t) => {
const { ctx } = t.context
const filePath = './css-style.svg'
Expand Down
6 changes: 0 additions & 6 deletions __test__/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,6 @@ test('properties should be readonly', (t) => {
message: /(Cannot assign to read only property)|(Cannot set property)/,
}

// @ts-expect-error
t.throws(() => (image.width = 114), expectation)

// @ts-expect-error
t.throws(() => (image.height = 514), expectation)

// @ts-expect-error
t.throws(() => (image.naturalWidth = 1919), expectation)

Expand Down
4 changes: 4 additions & 0 deletions __test__/resize.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added __test__/snapshots/drawImage-svg-resize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ export class ImageData {
}

export class Image {
readonly width: number
readonly height: number
width: number
height: number
readonly naturalWidth: number
readonly naturalHeight: number
readonly complete: boolean
Expand Down
12 changes: 10 additions & 2 deletions skia-c/skia_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ extern "C"
return reinterpret_cast<skiac_bitmap *>(bitmap);
}

skiac_bitmap *skiac_bitmap_make_from_svg(const uint8_t *data, size_t length)
skiac_bitmap *skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height)
{
auto svg_stream = new SkMemoryStream(data, length, false);
auto svg_dom = SkSVGDOM::MakeFromStream(*svg_stream);
Expand All @@ -1128,7 +1128,15 @@ extern "C"
}
svg_dom->setContainerSize(svg_container_size);
}
auto imageinfo = SkImageInfo::Make(svg_container_size.width(), svg_container_size.height(), kRGBA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType);
auto image_w = svg_container_size.width();
auto image_h = svg_container_size.height();
if (width > 0 && height > 0)
{
svg_root->setTransform(SkMatrix::Scale(width / image_w, height / image_h));
image_w = width;
image_h = height;
}
auto imageinfo = SkImageInfo::Make(image_w, image_h, kRGBA_8888_SkColorType, SkAlphaType::kOpaque_SkAlphaType);
auto bitmap = new SkBitmap();
bitmap->allocPixels(imageinfo);
auto sk_svg_canvas = new SkCanvas(*bitmap);
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 @@ -381,7 +381,7 @@ extern "C"

// Bitmap
skiac_bitmap *skiac_bitmap_make_from_buffer(const uint8_t *ptr, size_t size);
skiac_bitmap *skiac_bitmap_make_from_svg(const uint8_t *data, size_t length);
skiac_bitmap *skiac_bitmap_make_from_svg(const uint8_t *data, size_t length, float width, float height);
skiac_bitmap *skiac_bitmap_make_from_image_data(uint8_t *ptr, size_t width, size_t height, size_t row_bytes, size_t size, int ct, int at);
uint32_t skiac_bitmap_get_width(skiac_bitmap *c_bitmap);
uint32_t skiac_bitmap_get_height(skiac_bitmap *c_bitmap);
Expand Down
8 changes: 8 additions & 0 deletions src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,12 @@ fn draw_image(ctx: CallContext) -> Result<JsUndefined> {
let image_js = ctx.get::<JsObject>(0)?;
let image = ctx.env.unwrap::<Image>(&image_js)?;

let data = image_js
.get_named_property_unchecked::<JsBuffer>("_src")?
.into_value()?;

image.regenerate_bitmap_if_need(data);

// SVG with 0 width or 0 height
if image.bitmap.is_none() {
return ctx.env.get_undefined();
Expand Down Expand Up @@ -946,6 +952,8 @@ fn draw_image(ctx: CallContext) -> Result<JsUndefined> {
)?;
}

image.need_regenerate_bitmap = false;

ctx.env.get_undefined()
}

Expand Down
115 changes: 101 additions & 14 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,32 @@ fn image_data_constructor(ctx: CallContext) -> Result<JsUndefined> {
ctx.env.get_undefined()
}

pub struct Image {
pub bitmap: Option<Bitmap>,
pub complete: bool,
pub alt: String,
pub(crate) struct Image {
pub(crate) bitmap: Option<Bitmap>,
pub(crate) complete: bool,
pub(crate) alt: String,
width: f64,
height: f64,
pub(crate) need_regenerate_bitmap: bool,
pub(crate) is_svg: bool,
}

impl Image {
#[inline(always)]
pub(crate) fn regenerate_bitmap_if_need<D>(&mut self, data: D)
where
D: AsRef<[u8]>,
{
if !self.need_regenerate_bitmap || !self.is_svg {
return;
}
self.bitmap = Bitmap::from_svg_data_with_custom_size(
data.as_ref().as_ptr(),
data.as_ref().len(),
self.width as f32,
self.height as f32,
);
}
}

impl Image {
Expand All @@ -134,15 +156,15 @@ impl Image {
&vec![
Property::new(env, "width")?
.with_getter(get_width)
.with_property_attributes(PropertyAttributes::Enumerable),
.with_setter(set_width),
Property::new(env, "height")?
.with_getter(get_height)
.with_property_attributes(PropertyAttributes::Enumerable),
.with_setter(set_height),
Property::new(env, "naturalWidth")?
.with_getter(get_width)
.with_getter(get_natural_width)
.with_property_attributes(PropertyAttributes::Enumerable),
Property::new(env, "naturalHeight")?
.with_getter(get_height)
.with_getter(get_natural_height)
.with_property_attributes(PropertyAttributes::Enumerable),
Property::new(env, "complete")?
.with_getter(get_complete)
Expand All @@ -164,6 +186,10 @@ fn image_constructor(ctx: CallContext) -> Result<JsUndefined> {
complete: false,
bitmap: None,
alt: "".to_string(),
width: -1.0,
height: -1.0,
need_regenerate_bitmap: false,
is_svg: false,
};
let mut this = ctx.this_unchecked::<JsObject>();
this.set_named_property("_src", ctx.env.get_undefined()?)?;
Expand All @@ -178,17 +204,63 @@ fn get_width(ctx: CallContext) -> Result<JsNumber> {

ctx
.env
.create_double(image.bitmap.as_ref().unwrap().width as f64)
.create_double(if image.width <= 0.0 { 0.0 } else { image.width })
}

#[js_function]
fn get_natural_width(ctx: CallContext) -> Result<JsNumber> {
let this = ctx.this_unchecked::<JsObject>();
let image = ctx.env.unwrap::<Image>(&this)?;

ctx
.env
.create_double(image.bitmap.as_ref().map(|b| b.width).unwrap_or(0) as f64)
}

#[js_function(1)]
fn set_width(ctx: CallContext) -> Result<JsUndefined> {
let width = ctx.get::<JsNumber>(0)?.get_double()?;
let this = ctx.this_unchecked::<JsObject>();
let image = ctx.env.unwrap::<Image>(&this)?;
if (width - image.width).abs() > f64::EPSILON {
image.width = width;
image.need_regenerate_bitmap = true;
}
ctx.env.get_undefined()
}

#[js_function]
fn get_height(ctx: CallContext) -> Result<JsNumber> {
let this = ctx.this_unchecked::<JsObject>();
let image = ctx.env.unwrap::<Image>(&this)?;

ctx.env.create_double(if image.height <= 0.0 {
0.0
} else {
image.height
})
}

#[js_function]
fn get_natural_height(ctx: CallContext) -> Result<JsNumber> {
let this = ctx.this_unchecked::<JsObject>();
let image = ctx.env.unwrap::<Image>(&this)?;

ctx
.env
.create_double(image.bitmap.as_ref().unwrap().height as f64)
.create_double(image.bitmap.as_ref().map(|b| b.height).unwrap_or(0) as f64)
}

#[js_function(1)]
fn set_height(ctx: CallContext) -> Result<JsUndefined> {
let height = ctx.get::<JsNumber>(0)?.get_double()?;
let this = ctx.this_unchecked::<JsObject>();
let image = ctx.env.unwrap::<Image>(&this)?;
if (image.height - height).abs() > f64::EPSILON {
image.height = height;
image.need_regenerate_bitmap = true;
}
ctx.env.get_undefined()
}

#[js_function]
Expand Down Expand Up @@ -248,12 +320,27 @@ fn set_src(ctx: CallContext) -> Result<JsUndefined> {
}
}
image.complete = true;
image.is_svg = is_svg;
if is_svg {
image.bitmap = Bitmap::from_svg_data(src_data.as_ptr(), length);
let bitmap = Bitmap::from_svg_data(src_data.as_ptr(), length);
if let Some(b) = bitmap.as_ref() {
if (image.width - -1.0).abs() < f64::EPSILON {
image.width = b.width as f64;
}
if (image.height - -1.0).abs() < f64::EPSILON {
image.height = b.height as f64;
}
}
image.bitmap = bitmap;
} else {
image
.bitmap
.get_or_insert(Bitmap::from_buffer(src_data.as_ptr() as *mut u8, length));
let bitmap = Bitmap::from_buffer(src_data.as_ptr() as *mut u8, length);
if (image.width - -1.0).abs() < f64::EPSILON {
image.width = bitmap.width as f64;
}
if (image.height - -1.0).abs() < f64::EPSILON {
image.height = bitmap.height as f64;
}
image.bitmap = Some(bitmap)
}

this.set_named_property("_src", src_data.into_raw())?;
Expand Down
30 changes: 28 additions & 2 deletions src/sk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,12 @@ mod ffi {

pub fn skiac_bitmap_make_from_buffer(ptr: *mut u8, size: usize) -> *mut skiac_bitmap;

pub fn skiac_bitmap_make_from_svg(data: *const u8, size: usize) -> *mut skiac_bitmap;
pub fn skiac_bitmap_make_from_svg(
data: *const u8,
size: usize,
width: f32,
height: f32,
) -> *mut skiac_bitmap;

pub fn skiac_bitmap_make_from_image_data(
ptr: *mut u8,
Expand Down Expand Up @@ -2934,7 +2939,7 @@ impl Bitmap {
#[inline]
pub fn from_svg_data(data: *const u8, size: usize) -> Option<Self> {
unsafe {
let bitmap = ffi::skiac_bitmap_make_from_svg(data, size);
let bitmap = ffi::skiac_bitmap_make_from_svg(data, size, -1.0, -1.0);

if bitmap.is_null() {
return None;
Expand All @@ -2947,6 +2952,27 @@ impl Bitmap {
}
}

#[inline(always)]
pub fn from_svg_data_with_custom_size(
data: *const u8,
size: usize,
width: f32,
height: f32,
) -> Option<Self> {
unsafe {
let bitmap = ffi::skiac_bitmap_make_from_svg(data, size, width, height);

if bitmap.is_null() {
return None;
}
Some(Bitmap {
width: width as usize,
height: height as usize,
bitmap,
})
}
}

#[inline]
pub fn from_image_data(
ptr: *mut u8,
Expand Down

1 comment on commit 72c404c

@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: 72c404c Previous: 6a9f3bd Ratio
Draw house#skia-canvas 24 ops/sec (±1.25%) 19.3 ops/sec (±0.96%) 0.80
Draw house#node-canvas 19 ops/sec (±1.16%) 20.1 ops/sec (±1.36%) 1.06
Draw house#@napi-rs/skia 22 ops/sec (±1.13%) 20.5 ops/sec (±1.64%) 0.93
Draw gradient#skia-canvas 22 ops/sec (±1.03%) 18 ops/sec (±1.56%) 0.82
Draw gradient#node-canvas 18 ops/sec (±0.9%) 19 ops/sec (±1.66%) 1.06
Draw gradient#@napi-rs/skia 21 ops/sec (±0.89%) 20 ops/sec (±1.15%) 0.95

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

Please sign in to comment.