Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Jan 7, 2021
1 parent 558cbc6 commit f16e0db
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 11 deletions.
227 changes: 227 additions & 0 deletions __test__/draw.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,230 @@ test('getImageData', async (t) => {
ctx.putImageData(imageData, 150, 10)
await snapshotImage(t)
})

test.todo('isPointInPath')

test.todo('isPointInStroke')

test('lineTo', async (t) => {
const { ctx } = t.context
ctx.beginPath() // Start a new path
ctx.moveTo(30, 50) // Move the pen to (30, 50)
ctx.lineTo(150, 100) // Draw a line to (150, 100)
ctx.stroke() // Render the path
await snapshotImage(t)
})

test.todo('measureText')

test('moveTo', async (t) => {
const { ctx } = t.context
ctx.beginPath()
ctx.moveTo(50, 50) // Begin first sub-path
ctx.lineTo(200, 50)
ctx.moveTo(50, 90) // Begin second sub-path
ctx.lineTo(280, 120)
ctx.stroke()
await snapshotImage(t)
})

test('putImageData', async (t) => {
const { ctx } = t.context
function putImageData(
imageData: ImageData,
dx: number,
dy: number,
dirtyX: number,
dirtyY: number,
dirtyWidth: number,
dirtyHeight: number,
) {
const data = imageData.data
const height = imageData.height
const width = imageData.width
dirtyX = dirtyX || 0
dirtyY = dirtyY || 0
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height
const limitBottom = dirtyY + dirtyHeight
const limitRight = dirtyX + dirtyWidth
for (let y = dirtyY; y < limitBottom; y++) {
for (let x = dirtyX; x < limitRight; x++) {
const pos = y * width + x
ctx.fillStyle =
'rgba(' +
data[pos * 4 + 0] +
',' +
data[pos * 4 + 1] +
',' +
data[pos * 4 + 2] +
',' +
data[pos * 4 + 3] / 255 +
')'
ctx.fillRect(x + dx, y + dy, 1, 1)
}
}
}

// Draw content onto the canvas
ctx.fillRect(0, 0, 100, 100)
// Create an ImageData object from it
const imagedata = ctx.getImageData(0, 0, 100, 100)
// use the putImageData function that illustrates how putImageData works
putImageData(imagedata, 150, 0, 50, 50, 25, 25)

await snapshotImage(t)
})

test('quadraticCurveTo', async (t) => {
const { ctx } = t.context
// Quadratic Bézier curve
ctx.beginPath()
ctx.moveTo(50, 20)
ctx.quadraticCurveTo(230, 30, 50, 100)
ctx.stroke()

// Start and end points
ctx.fillStyle = 'blue'
ctx.beginPath()
ctx.arc(50, 20, 5, 0, 2 * Math.PI) // Start point
ctx.arc(50, 100, 5, 0, 2 * Math.PI) // End point
ctx.fill()

// Control point
ctx.fillStyle = 'red'
ctx.beginPath()
ctx.arc(230, 30, 5, 0, 2 * Math.PI)
ctx.fill()
await snapshotImage(t)
})

test('rect', async (t) => {
const { ctx } = t.context
ctx.fillStyle = 'yellow'
ctx.rect(10, 20, 150, 100)
ctx.fill()
await snapshotImage(t)
})

test.todo('resetTransform')

test('save-restore', async (t) => {
const { ctx } = t.context
// Save the default state
ctx.save()

ctx.fillStyle = 'green'
ctx.fillRect(10, 10, 100, 100)

// Restore the default state
ctx.restore()

ctx.fillRect(150, 40, 100, 100)

await snapshotImage(t)
})

test.todo('rotate')

test.todo('scale')

test('setLineDash', async (t) => {
const { ctx } = t.context
// Dashed line
ctx.beginPath()
ctx.setLineDash([5, 15])
ctx.moveTo(0, 50)
ctx.lineTo(300, 50)
ctx.stroke()

// Solid line
ctx.beginPath()
ctx.setLineDash([])
ctx.moveTo(0, 100)
ctx.lineTo(300, 100)
ctx.stroke()
await snapshotImage(t)
})

test('setTransform', async (t) => {
const { ctx } = t.context
ctx.setTransform(1, 0.2, 0.8, 1, 0, 0)
ctx.fillRect(0, 0, 100, 100)
await snapshotImage(t)
})

test('stroke', async (t) => {
const { ctx } = t.context
// First sub-path
ctx.lineWidth = 26
ctx.strokeStyle = 'orange'
ctx.moveTo(20, 20)
ctx.lineTo(160, 20)
ctx.stroke()

// Second sub-path
ctx.lineWidth = 14
ctx.strokeStyle = 'green'
ctx.moveTo(20, 80)
ctx.lineTo(220, 80)
ctx.stroke()

// Third sub-path
ctx.lineWidth = 4
ctx.strokeStyle = 'pink'
ctx.moveTo(20, 140)
ctx.lineTo(280, 140)
ctx.stroke()
await snapshotImage(t)
})

test('stroke-and-filling', async (t) => {
const { ctx } = t.context
ctx.lineWidth = 16
ctx.strokeStyle = 'red'

// Stroke on top of fill
ctx.beginPath()
ctx.rect(25, 25, 100, 100)
ctx.fill()
ctx.stroke()

// Fill on top of stroke
ctx.beginPath()
ctx.rect(175, 25, 100, 100)
ctx.stroke()
ctx.fill()
await snapshotImage(t)
})

test('strokeRect', async (t) => {
const { ctx } = t.context
ctx.shadowColor = '#d53'
ctx.shadowBlur = 20
ctx.lineJoin = 'bevel'
ctx.lineWidth = 15
ctx.strokeStyle = '#38f'
ctx.strokeRect(30, 30, 160, 90)
await snapshotImage(t)
})

test.todo('strokeText')

test.todo('transform')

test('translate', 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)
})
9 changes: 9 additions & 0 deletions __test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,12 @@ test('shadowOffsetY state should be ok', (t) => {
ctx.shadowOffsetY = 10
t.is(ctx.shadowOffsetY, 10)
})

test('lineDash state should be ok', (t) => {
const { ctx } = t.context
const lineDash = [1, 2, 4.5, 7]
ctx.setLineDash(lineDash)
t.deepEqual(ctx.getLineDash(), lineDash)
})

test.todo('getTransform')
Binary file added __test__/snapshots/lineTo.png
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/moveTo.png
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/putImageData.png
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/quadraticCurveTo.png
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/rect.png
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/save-restore.png
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/setLineDash.png
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/setTransform.png
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/stroke-and-filling.png
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/stroke.png
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/strokeRect.png
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/translate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ const { loadBinding } = require('@node-rs/helper')
*/
const { CanvasRenderingContext2D, CanvasElement, Path2D, ImageData } = loadBinding(__dirname, 'skia', '@napi-rs/skia')

CanvasRenderingContext2D.prototype.getImageData = function getImageData(x, y, w, h) {
const data = this._getImageData(x, y, w, h)
return new ImageData(data, w, h)
}

function createCanvas(width, height) {
const canvasElement = new CanvasElement(width, height)
const ctx = new CanvasRenderingContext2D(width, height)

CanvasRenderingContext2D.prototype.getImageData = function getImageData(x, y, w, h) {
const data = this._getImageData(x, y, w, h)
return new ImageData(data, w, h)
}

// napi can not define writable: true but enumerable: false property
Object.defineProperty(ctx, '_fillStyle', {
value: '#000',
Expand Down
34 changes: 28 additions & 6 deletions src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ impl Context {
Property::new(&env, "fill")?.with_method(fill),
Property::new(&env, "fillRect")?.with_method(fill_rect),
Property::new(&env, "_getImageData")?.with_method(get_image_data),
Property::new(&env, "getLineDash")?.with_method(get_line_dash),
Property::new(&env, "putImageData")?.with_method(put_image_data),
Property::new(&env, "quadraticCurveTo")?.with_method(quadratic_curve_to),
Property::new(&env, "rect")?.with_method(rect),
Expand Down Expand Up @@ -313,18 +314,25 @@ impl Context {
fn shadow_paint(&self, paint: &Paint) -> Option<Paint> {
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;
let shadow_color = &last_state.shadow_color;
let mut shadow_alpha = shadow_color.alpha;
shadow_alpha = ((shadow_alpha as f32) * (alpha as f32 / 255.0)) as u8;
if shadow_alpha == 0 {
return None;
}
if last_state.shadow_blur == 0f32
|| last_state.shadow_offset_x == 0f32
|| last_state.shadow_offset_y == 0f32
&& last_state.shadow_offset_x == 0f32
&& last_state.shadow_offset_y == 0f32
{
return None;
}
let mut shadow_paint = paint.clone();
shadow_paint.set_color(
shadow_color.red,
shadow_color.green,
shadow_color.blue,
shadow_color.alpha,
);
shadow_paint.set_alpha(shadow_alpha);
let blur_effect = MaskFilter::make_blur(last_state.shadow_blur / 2f32)?;
shadow_paint.set_mask_filter(&blur_effect);
Expand Down Expand Up @@ -736,6 +744,21 @@ fn get_image_data(ctx: CallContext) -> Result<JsTypedArray> {
})
}

#[js_function]
fn get_line_dash(ctx: CallContext) -> Result<JsObject> {
let this = ctx.this_unchecked::<JsObject>();
let context_2d = ctx.env.unwrap::<Context>(&this)?;

let line_dash_list = &context_2d.states.last().unwrap().line_dash_list;

let mut arr = ctx.env.create_array_with_length(line_dash_list.len())?;

for (index, a) in line_dash_list.iter().enumerate() {
arr.set_element(index as u32, ctx.env.create_double(*a as f64)?)?;
}
Ok(arr)
}

#[js_function(7)]
fn put_image_data(ctx: CallContext) -> Result<JsUndefined> {
let this = ctx.this_unchecked::<JsObject>();
Expand Down Expand Up @@ -1268,9 +1291,8 @@ fn set_shadow_blur(ctx: CallContext) -> Result<JsUndefined> {

let this = ctx.this_unchecked::<JsObject>();
let context_2d = ctx.env.unwrap::<Context>(&this)?;
let last_state = context_2d.states.last_mut().unwrap();

last_state.shadow_blur = blur as f32;
context_2d.states.last_mut().unwrap().shadow_blur = blur as f32;

ctx.env.get_undefined()
}
Expand Down

1 comment on commit f16e0db

@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: f16e0db Previous: 1f11d7d Ratio
Draw house#@napi-rs/skia 29 ops/sec (±1.82%) 27 ops/sec (±0.11%) 0.93
Draw house#node-canvas 27 ops/sec (±1.82%) 24 ops/sec (±0.35%) 0.89
Draw gradient#@napi-rs/skia 29 ops/sec (±1.65%) 26 ops/sec (±0.04%) 0.90
Draw gradient#node-canvas 27 ops/sec (±1.19%) 23 ops/sec (±0.17%) 0.85

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

Please sign in to comment.