diff --git a/__test__/draw.spec.ts b/__test__/draw.spec.ts index 5a21fd3a..9d344c89 100644 --- a/__test__/draw.spec.ts +++ b/__test__/draw.spec.ts @@ -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) +}) diff --git a/__test__/index.spec.ts b/__test__/index.spec.ts index 97f0480a..d29431c0 100644 --- a/__test__/index.spec.ts +++ b/__test__/index.spec.ts @@ -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') diff --git a/__test__/snapshots/lineTo.png b/__test__/snapshots/lineTo.png new file mode 100644 index 00000000..52ff23f9 Binary files /dev/null and b/__test__/snapshots/lineTo.png differ diff --git a/__test__/snapshots/moveTo.png b/__test__/snapshots/moveTo.png new file mode 100644 index 00000000..ae1a19ab Binary files /dev/null and b/__test__/snapshots/moveTo.png differ diff --git a/__test__/snapshots/putImageData.png b/__test__/snapshots/putImageData.png new file mode 100644 index 00000000..344f92c8 Binary files /dev/null and b/__test__/snapshots/putImageData.png differ diff --git a/__test__/snapshots/quadraticCurveTo.png b/__test__/snapshots/quadraticCurveTo.png new file mode 100644 index 00000000..3b95af06 Binary files /dev/null and b/__test__/snapshots/quadraticCurveTo.png differ diff --git a/__test__/snapshots/rect.png b/__test__/snapshots/rect.png new file mode 100644 index 00000000..e3918844 Binary files /dev/null and b/__test__/snapshots/rect.png differ diff --git a/__test__/snapshots/save-restore.png b/__test__/snapshots/save-restore.png new file mode 100644 index 00000000..6d22f84b Binary files /dev/null and b/__test__/snapshots/save-restore.png differ diff --git a/__test__/snapshots/setLineDash.png b/__test__/snapshots/setLineDash.png new file mode 100644 index 00000000..4bb2352f Binary files /dev/null and b/__test__/snapshots/setLineDash.png differ diff --git a/__test__/snapshots/setTransform.png b/__test__/snapshots/setTransform.png new file mode 100644 index 00000000..0291e0fd Binary files /dev/null and b/__test__/snapshots/setTransform.png differ diff --git a/__test__/snapshots/stroke-and-filling.png b/__test__/snapshots/stroke-and-filling.png new file mode 100644 index 00000000..20cea4e8 Binary files /dev/null and b/__test__/snapshots/stroke-and-filling.png differ diff --git a/__test__/snapshots/stroke.png b/__test__/snapshots/stroke.png new file mode 100644 index 00000000..8a9005ed Binary files /dev/null and b/__test__/snapshots/stroke.png differ diff --git a/__test__/snapshots/strokeRect.png b/__test__/snapshots/strokeRect.png new file mode 100644 index 00000000..61b2605c Binary files /dev/null and b/__test__/snapshots/strokeRect.png differ diff --git a/__test__/snapshots/translate.png b/__test__/snapshots/translate.png new file mode 100644 index 00000000..79c4bdbf Binary files /dev/null and b/__test__/snapshots/translate.png differ diff --git a/index.js b/index.js index 70af6884..f6a37179 100644 --- a/index.js +++ b/index.js @@ -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', diff --git a/src/ctx.rs b/src/ctx.rs index e45cea81..92a7626b 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -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), @@ -313,18 +314,25 @@ impl Context { fn shadow_paint(&self, paint: &Paint) -> Option { 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); @@ -736,6 +744,21 @@ fn get_image_data(ctx: CallContext) -> Result { }) } +#[js_function] +fn get_line_dash(ctx: CallContext) -> Result { + let this = ctx.this_unchecked::(); + let context_2d = ctx.env.unwrap::(&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 { let this = ctx.this_unchecked::(); @@ -1268,9 +1291,8 @@ fn set_shadow_blur(ctx: CallContext) -> Result { let this = ctx.this_unchecked::(); let context_2d = ctx.env.unwrap::(&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() }