Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Hey there,

I have a notebook for handwritten digit classification, and I want to allow the user to draw their own custom digit to classify it.
Therefore, I need a canvas, which is the reason for using ipycanvas.

To accomplish this, I use the events to "follow the mouse pointer." However, my current approach is too slow and laggy, so the canvas updates are not in real-time.

This is my current approach:

from ipycanvas import Canvas
from ipywidgets import Output
import math

canvas = Canvas(width=200, height=200)
canvas.stroke_rect(0, 0, 200, 200)

x_prev = 0
y_prev = 0

out = Output()

@out.capture()
def handle_mouse_move(x, y):
    global x_prev
    global y_prev

    # My approach to prevent updates for every small movements
    if (x - x_prev) ** 2 + (y - y_prev) ** 2 <= 2 ** 2:
        return
    
    canvas.begin_path();
    
    canvas.move_to(x_prev, y_prev);
    canvas.arc(x, y, 15, 0, math.pi * 2, False)

    canvas.fill();

    x_prev = x;
    y_prev = y;


canvas.on_mouse_move(handle_mouse_move)

canvas

Am I using the underlying HTML canvas API wrong, or do you have any other suggestions?
Thanks in advance.

Best regards,
Paul

You must be logged in to vote

Instead of filling arcs, I use the method line_to(...) with a modified line width and cap.
This enables "real-time" drawing without lags or gaps.

from ipycanvas import Canvas
from ipywidgets import Output


canvas = Canvas(width=400, height=400)

canvas.stroke_rect(0, 0, 400, 400)

canvas.line_width = 28
canvas.line_cap = "round"

x_prev = 0
y_prev = 0

out = Output()

@out.capture()
def handle_mouse_move(x, y):    
    global x_prev
    global y_prev
    
    canvas.begin_path();
    
    canvas.move_to(x_prev, y_prev);
    canvas.line_to(x, y)

    canvas.stroke();
    
    x_prev = x;
    y_prev = y;


canvas.on_mouse_move(handle_mouse_move)

canvas

Replies: 2 comments · 2 replies

Comment options

Thanks for opening a discussion :)

Some comments:

  • You should use hold_canvas whenever you use multiple methods in a row
  • You can simplify the arc drawing in your code into a single call to fill_arc, this is a handy ipycanvas method for filling one arc and it's not part of the HTML canvas API. This comment invalidates the one above, because you then have one method call.

Your code becomes the following, and it seems to run faster than your first version:

from ipycanvas import Canvas
from ipywidgets import Output
import math

canvas = Canvas(width=200, height=200)
canvas.stroke_rect(0, 0, 200, 200)

x_prev = 0
y_prev = 0

out = Output()

@out.capture()
def handle_mouse_move(x, y):
    global x_prev
    global y_prev

    # My approach to prevent updates for every small movements
    if (x - x_prev) ** 2 + (y - y_prev) ** 2 <= 2 ** 2:
        return
    
    canvas.fill_arc(x, y, 15, 0, math.pi * 2, False)

    x_prev = x;
    y_prev = y;


canvas.on_mouse_move(handle_mouse_move)

canvas
You must be logged in to vote
1 reply
@Paul2708
Comment options

Thank you for taking a look at my code! Indeed, it runs faster. However, I encountered the problem that moving too fast with the curser skips some gaps.
Therefore, I came up with another approach that works.

Comment options

Instead of filling arcs, I use the method line_to(...) with a modified line width and cap.
This enables "real-time" drawing without lags or gaps.

from ipycanvas import Canvas
from ipywidgets import Output


canvas = Canvas(width=400, height=400)

canvas.stroke_rect(0, 0, 400, 400)

canvas.line_width = 28
canvas.line_cap = "round"

x_prev = 0
y_prev = 0

out = Output()

@out.capture()
def handle_mouse_move(x, y):    
    global x_prev
    global y_prev
    
    canvas.begin_path();
    
    canvas.move_to(x_prev, y_prev);
    canvas.line_to(x, y)

    canvas.stroke();
    
    x_prev = x;
    y_prev = y;


canvas.on_mouse_move(handle_mouse_move)

canvas
You must be logged in to vote
1 reply
@martinRenou
Comment options

Nice! You can improve your code further with using stroke_line instead of building a path:

from ipycanvas import Canvas
from ipywidgets import Output


canvas = Canvas(width=400, height=400)

canvas.stroke_rect(0, 0, 400, 400)

canvas.line_width = 28
canvas.line_cap = "round"

x_prev = 0
y_prev = 0

out = Output()

@out.capture()
def handle_mouse_move(x, y):    
    global x_prev
    global y_prev

    canvas.stroke_line(x_prev, y_prev, x, y)
    
    x_prev = x;
    y_prev = y;


canvas.on_mouse_move(handle_mouse_move)

canvas
Answer selected by Paul2708
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
💬
Q&A
Labels
None yet
2 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.