From ca688f653cc93a65f45f3a8d5b4beef87d5796b3 Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Tue, 3 Sep 2024 10:11:11 -0700 Subject: [PATCH 01/10] Add stroke path to Path2D interface per html canvas spec (#1) * add stroke(path: Path2D) form to canvas --------- Co-authored-by: Chris Lee-Messer --- ipycanvas/canvas.py | 20 ++++++++++++++++---- src/widget.ts | 10 ++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/ipycanvas/canvas.py b/ipycanvas/canvas.py index 7105bfb..768d78f 100644 --- a/ipycanvas/canvas.py +++ b/ipycanvas/canvas.py @@ -60,6 +60,7 @@ "beginPath", "closePath", "stroke", + "strokePath", "fillPath", "fill", "moveTo", @@ -1242,10 +1243,21 @@ def close_path(self): This method doesn't draw anything to the canvas directly. You can render the path using the stroke() or fill() methods. """ self._canvas_manager.send_draw_command(self, COMMANDS["closePath"]) - - def stroke(self): - """Stroke (outlines) the current path with the current ``stroke_style``.""" - self._canvas_manager.send_draw_command(self, COMMANDS["stroke"]) + + # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke + # stroke(), stroke(path) + def stroke(self, path2d: Path2D = None): + """Stroke (outlines) the current path with the current ``stroke_style``. + If @path2d is passed, that Path2D object will be rendered with the + current ``stroke_style``""" + + if isinstance(path2d, Path2D): + self._canvas_manager.send_draw_command( + self, + COMMANDS["strokePath"], [widget_serialization["to_json"](path2d, None)], + ) + else: + self._canvas_manager.send_draw_command(self, COMMANDS["stroke"]) def fill(self, rule_or_path="nonzero"): """Fill the current path with the current ``fill_style`` and given the rule, or fill the given Path2D. diff --git a/src/widget.ts b/src/widget.ts index 9bc7732..77d2e9c 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -85,6 +85,7 @@ const COMMANDS = [ 'beginPath', 'closePath', 'stroke', + 'strokePath', 'fillPath', 'fill', 'moveTo', @@ -279,6 +280,8 @@ export class CanvasManagerModel extends WidgetModel { case 'strokePolygon': this.currentCanvas.strokePolygon(args, buffers); break; + case 'strokePath': + await this.currentCanvas.strokePath(args, buffers); case 'fillPath': await this.currentCanvas.fillPath(args, buffers); break; @@ -1054,7 +1057,14 @@ export class CanvasModel extends DOMWidgetModel { this.ctx.closePath(); this.ctx.stroke(); } + async strokePath(args: any[], buffers: any) { + const [serializedPath] = args; + + const path = await unpack_models(serializedPath, this.widget_manager); + this.ctx.stroke(path.value); + } + async fillPath(args: any[], buffers: any) { const [serializedPath] = args; From 50c05a0cbddae6d74215506870551aa7cb498dbe Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Wed, 4 Sep 2024 21:12:35 -0700 Subject: [PATCH 02/10] add visual test of canvas.stroke(path) --- ui-tests/tests/notebooks/ipycanvas.ipynb | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ui-tests/tests/notebooks/ipycanvas.ipynb b/ui-tests/tests/notebooks/ipycanvas.ipynb index c4e955e..6b5e029 100644 --- a/ui-tests/tests/notebooks/ipycanvas.ipynb +++ b/ui-tests/tests/notebooks/ipycanvas.ipynb @@ -989,6 +989,38 @@ "canvas.fill_rect(0, 0, canvas.width, canvas.height)\n", "canvas" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7be13b55", + "metadata": {}, + "outputs": [], + "source": [ + "# test canvas.stroke(path: Path2D)\n", + "from ipycanvas import Path2D, Canvas\n", + "\n", + "canvas = Canvas(width=320, height=320)\n", + "\n", + "canvas.fill_style = \"green\"\n", + "canvas.stroke_style = \"black\"\n", + "canvas.line_width = 2\n", + "\n", + "# this path is from:\n", + "# https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths\n", + "# under the Arcs section. There is no equivalent to fill-opacity:\"0.5\"\n", + "# so the filled arcs are opaque compared to the example\n", + "p = Path2D(\"\"\"\n", + " M 10 315\n", + " L 110 215\n", + " A 30 50 0 0 1 162.55 162.45\n", + " L 172.55 152.45\n", + " A 30 50 -45 0 1 215.1 109.9\n", + " L 315 10\"\"\"\n", + ")\n", + "canvas.stroke(p)\n", + "canvas\n" + ] } ], "metadata": { From c441fa796c8914464949b7c2cec6df07657ab4b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Sep 2024 07:11:27 +0000 Subject: [PATCH 03/10] Update Playwright Snapshots --- .../ipycanvas-ipynb-cell-37-linux.png | Bin 0 -> 4402 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ui-tests/tests/ipycanvas.test.ts-snapshots/ipycanvas-ipynb-cell-37-linux.png diff --git a/ui-tests/tests/ipycanvas.test.ts-snapshots/ipycanvas-ipynb-cell-37-linux.png b/ui-tests/tests/ipycanvas.test.ts-snapshots/ipycanvas-ipynb-cell-37-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..3eb5a1943d8c5a788c2662d70846919f034d4f4d GIT binary patch literal 4402 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 Date: Thu, 5 Sep 2024 08:40:14 -0700 Subject: [PATCH 04/10] black ipycanvas/canvas.py --- ipycanvas/canvas.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ipycanvas/canvas.py b/ipycanvas/canvas.py index ee3999d..98919ed 100644 --- a/ipycanvas/canvas.py +++ b/ipycanvas/canvas.py @@ -1247,18 +1247,19 @@ def close_path(self): This method doesn't draw anything to the canvas directly. You can render the path using the stroke() or fill() methods. """ self._canvas_manager.send_draw_command(self, COMMANDS["closePath"]) - + # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke # stroke(), stroke(path) def stroke(self, path2d: Path2D = None): """Stroke (outlines) the current path with the current ``stroke_style``. - If @path2d is passed, that Path2D object will be rendered with the + If @path2d is passed, that Path2D object will be rendered with the current ``stroke_style``""" if isinstance(path2d, Path2D): self._canvas_manager.send_draw_command( self, - COMMANDS["strokePath"], [widget_serialization["to_json"](path2d, None)], + COMMANDS["strokePath"], + [widget_serialization["to_json"](path2d, None)], ) else: self._canvas_manager.send_draw_command(self, COMMANDS["stroke"]) From d10f638ba8520ea1b5eca528fd83ffb09dde4a91 Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Thu, 5 Sep 2024 15:52:09 -0700 Subject: [PATCH 05/10] fix bug falling through to fillPath-add missing break --- src/widget.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget.ts b/src/widget.ts index c160bae..1ef52af 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -282,6 +282,7 @@ export class CanvasManagerModel extends WidgetModel { break; case 'strokePath': await this.currentCanvas.strokePath(args, buffers); + break; case 'fillPath': await this.currentCanvas.fillPath(args, buffers); break; @@ -1058,17 +1059,16 @@ export class CanvasModel extends DOMWidgetModel { this.ctx.closePath(); this.ctx.stroke(); } + async strokePath(args: any[], buffers: any) { const [serializedPath] = args; - const path = await unpack_models(serializedPath, this.widget_manager); this.ctx.stroke(path.value); } - + async fillPath(args: any[], buffers: any) { const [serializedPath] = args; - const path = await unpack_models(serializedPath, this.widget_manager); this.ctx.fill(path.value); From aa16f268ca706a2759b769a638a0833be43f733c Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Thu, 5 Sep 2024 16:20:58 -0700 Subject: [PATCH 06/10] initial documentation stroke(Path2D) --- docs/drawing_paths.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/drawing_paths.rst b/docs/drawing_paths.rst index 7e6803c..173d901 100644 --- a/docs/drawing_paths.rst +++ b/docs/drawing_paths.rst @@ -6,8 +6,8 @@ There are two ways for creating and drawing a path in ipycanvas. Using Path2D ------------ -You can define a Path2D given an SVG path. Note that once the path is created, it is read only, you cannot dynamically change the path value. -Using the Path2D class is very useful and efficient when you want to reuse the same path multiple times. +You can define a Path2D given an SVG path. Note that once the path is created, it is read only, you cannot dynamically change the path value. This means they do not (yet) support Path2D methods like Path2D.move_to, draw_line, etc. +Using the Path2D class is very useful and efficient when you want to reuse the same path multiple times. Path2D objects may be stroked or filled. See https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths for documentation about SVG paths. @@ -37,6 +37,11 @@ See https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths for document canvas.fill_style = "blue" canvas.fill(path4) + # draw a sinusoidal curve using quadratic bezier curves + path5 = Path2D("M 10 150 Q 52.5 10 95 150 T 180 150") + canvas.line_width = 2.5 + canvas.stroke_style = 'black' + canvas.stroke(path5) canvas .. image:: images/path2d.png From e1985dc56130cb2cc4ffb4d73e2d136c73016532 Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Thu, 5 Sep 2024 16:42:41 -0700 Subject: [PATCH 07/10] update initial stroke(path) uitest to replicate SVG fig --- ui-tests/tests/notebooks/ipycanvas.ipynb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui-tests/tests/notebooks/ipycanvas.ipynb b/ui-tests/tests/notebooks/ipycanvas.ipynb index 6b5e029..8751fe5 100644 --- a/ui-tests/tests/notebooks/ipycanvas.ipynb +++ b/ui-tests/tests/notebooks/ipycanvas.ipynb @@ -1006,10 +1006,10 @@ "canvas.stroke_style = \"black\"\n", "canvas.line_width = 2\n", "\n", - "# this path is from:\n", + "# This more complicated path is from\n", "# https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths\n", "# under the Arcs section. There is no equivalent to fill-opacity:\"0.5\"\n", - "# so the filled arcs are opaque compared to the example\n", + "# instead use global_alpha for the fill.\n", "p = Path2D(\"\"\"\n", " M 10 315\n", " L 110 215\n", @@ -1018,8 +1018,11 @@ " A 30 50 -45 0 1 215.1 109.9\n", " L 315 10\"\"\"\n", ")\n", + "canvas.global_alpha = 0.5\n", + "canvas.fill(p)\n", + "canvas.global_alpha = 1.0\n", "canvas.stroke(p)\n", - "canvas\n" + "canvas" ] } ], From b6ef638b3d657435fdda1b7ce7ce78e30ae668cc Mon Sep 17 00:00:00 2001 From: Chris Lee-Messer Date: Thu, 5 Sep 2024 22:56:21 -0700 Subject: [PATCH 08/10] black format code block in drawing_paths.rst --- docs/drawing_paths.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/drawing_paths.rst b/docs/drawing_paths.rst index 173d901..4471a10 100644 --- a/docs/drawing_paths.rst +++ b/docs/drawing_paths.rst @@ -40,7 +40,7 @@ See https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths for document # draw a sinusoidal curve using quadratic bezier curves path5 = Path2D("M 10 150 Q 52.5 10 95 150 T 180 150") canvas.line_width = 2.5 - canvas.stroke_style = 'black' + canvas.stroke_style = "black" canvas.stroke(path5) canvas From bc62c6751bb9d64af834ed7847dbaf8fb1819438 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 6 Sep 2024 07:06:28 +0000 Subject: [PATCH 09/10] Update Playwright Snapshots --- .../ipycanvas-ipynb-cell-37-linux.png | Bin 4402 -> 4958 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ui-tests/tests/ipycanvas.test.ts-snapshots/ipycanvas-ipynb-cell-37-linux.png b/ui-tests/tests/ipycanvas.test.ts-snapshots/ipycanvas-ipynb-cell-37-linux.png index 3eb5a1943d8c5a788c2662d70846919f034d4f4d..2b2033c6a66aeb17e91d9a51b0693ba7966a8791 100644 GIT binary patch literal 4958 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 literal 4402 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 Date: Fri, 6 Sep 2024 09:21:24 -0700 Subject: [PATCH 10/10] drawing paths suggested doc edit --- docs/drawing_paths.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/drawing_paths.rst b/docs/drawing_paths.rst index 4471a10..b993762 100644 --- a/docs/drawing_paths.rst +++ b/docs/drawing_paths.rst @@ -6,7 +6,7 @@ There are two ways for creating and drawing a path in ipycanvas. Using Path2D ------------ -You can define a Path2D given an SVG path. Note that once the path is created, it is read only, you cannot dynamically change the path value. This means they do not (yet) support Path2D methods like Path2D.move_to, draw_line, etc. +You can define a Path2D given an SVG path. Note that once the path is created, it is read only, you cannot dynamically change the path value. ``ipycanvas`` does not (yet) support Path2D methods like Path2D.move_to, draw_line, etc. Using the Path2D class is very useful and efficient when you want to reuse the same path multiple times. Path2D objects may be stroked or filled. See https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths for documentation about SVG paths.