<jupyter_start><jupyter_text>Hand drawing 4By <NAME>. Copyright February 2020. All rights reserved.Based on the code in the ipycanvas project, here:https://ipycanvas.readthedocs.io/en/latest/index.html  InstallingYou need to do a couple of pip installs to get this to work```pip install ipywidgets==7.5.1pip install ipycanvas```Do this from a terminal window on your Callysto Hub, or uncomment the lines in the following cell.<jupyter_code>#!pip install ipywidgets==7.5.1
#!pip install ipycanvas
#print('Modules installed, now restart your kernal to continue')<jupyter_output><empty_output><jupyter_text>If you want to see what version of ipywidgets you have installed, try this:``` pip freeze | grep ipywidgets```  UsageRun the two code cells below to start up the drawing.We have five drawing modes:- sketch (free hand drawing)- line- rectangle- circle- ellipseWe have a choice of filled/not-filled, line colour, and line width. We also have buttons for- Undo, to undo the last drawing command- Erase, to erase the drawing layer- Save, to save the drawing to a file (my_file.png)"Save" is a little flakey. You might need to save two or three times to get everything to save. Check your saved file, to see that it is there and has a drawing in it. Comments:We made the code the same for the different modes, to keep things simple. However,  switch statement is needed in the Mouse Move event, as each drawing mode does something different here. In version 4, we clean up the code.  To do:- Fix the save command. It is flakey.- Add an option to change the name of file to save as.- Add a load button, to load in a new background image.<jupyter_code>from ipywidgets import Image

from ipywidgets import ColorPicker, IntSlider, RadioButtons, Checkbox, Button, link, AppLayout, HBox, VBox

from ipycanvas import Canvas, MultiCanvas, hold_canvas
width = 500
height = 500

canvas = MultiCanvas(3, width=width, height=height, sync_image_data=False)

old_layer = Canvas(width=width, height=height, sync_image_data=False)

save_layer = Canvas(width=width, height=height, sync_image_data=True)

image = Image.from_file('Bob.png')

#old_layer = canvas[0]
background_layer = canvas[0]
drawing_layer = canvas[1]
interaction_layer = canvas[2]

drawing = False
start = None

## What to do on the mouse action

## On mouse down, we record the start position, and begin a path
def on_mouse_down(x,y):
    global drawing,start
    drawing = True
    start = (x,y)
    with hold_canvas(canvas):
        old_layer.clear()
        old_layer.draw_image(drawing_layer, 0, 0, width, height) ## save the current image for undo, if needed
        interaction_layer.clear()
        interaction_layer.begin_path() ## not all modes need this, but it doesn't hurt
        interaction_layer.move_to(x,y) ## Sketching mode needs this.
    

## On mouse up, we just copy the interaction layer to the final drawing layer
def on_mouse_up(x,y):
    global drawing, old_layer, drawing_layer, interaction_layer
    drawing = False
#    with hold_canvas(canvas):
    drawing_layer.draw_image(interaction_layer, 0, 0, width, height)
    interaction_layer.clear()


## We need a switch statement to do different things on mouse move

## sketching mode

def sketch_omm(x,y,fill):
    with hold_canvas(canvas):
        interaction_layer.line_to(x,y)
        if fill:
            interaction_layer.fill()
        else:
            interaction_layer.stroke()

# line drawing mode

def line_omm(x,y,fill):
    with hold_canvas(canvas):
        interaction_layer.clear()
        interaction_layer.begin_path()
        interaction_layer.move_to(start[0],start[1])
        interaction_layer.line_to(x,y)
        interaction_layer.stroke()
        interaction_layer.close_path()

# rect drawing mode

def rect_omm(x,y,fill):
    with hold_canvas(canvas):
        interaction_layer.clear()
        interaction_layer.begin_path()
        interaction_layer.rect(start[0],start[1],x-start[0],y-start[1])
        if fill:
            interaction_layer.fill()
        else:
            interaction_layer.stroke()
        interaction_layer.close_path()


# circle drawing mode

def circ_omm(x,y,fill):
    r = abs(complex( start[0]-x, start[1]-y ))
    with hold_canvas(canvas):
        interaction_layer.clear()
        interaction_layer.begin_path()
        interaction_layer.arc(start[0],start[1], r, 0, 6.29)
        if fill:
            interaction_layer.fill()
        else:
            interaction_layer.stroke()
        interaction_layer.close_path()

# ellipse drawing mode

def ellp_omm(x,y,fill):
    r1 = abs(start[0]-x)
    r2 = abs(start[1]-y)
    ratio = (r2+1)/(r1+1)  # avoid zeros
    with hold_canvas(canvas):
        interaction_layer.clear()
        interaction_layer.save()
        interaction_layer.scale(1,ratio) # this turns a circle (arc) into an ellipse
        interaction_layer.begin_path()
        interaction_layer.arc(start[0],start[1]/ratio, r1, 0, 6.29)
        interaction_layer.restore()
        if fill:
            interaction_layer.fill()
        else:
            interaction_layer.stroke()
        interaction_layer.close_path()


# This a Python way to create a switch statement. We use a dictionary of functions
switch_omm = {
    'sketch': sketch_omm,
    'line': line_omm,
    'rectangle': rect_omm,
    'circle': circ_omm,
    'ellipse': ellp_omm
    }

# default function in case the switch statement fails
def default(x,y,fill):
    return

# Here we use the switch dictionary, to use a particular drawing mode
def on_mouse_move(x, y):
    if not drawing:
        return
    switch_omm.get(mode_button.value,default)(x,y,check.value)
    
#
def undo(b):
 #   with hold_canvas(canvas):
        interaction_layer.clear()
        interaction_layer.draw_image(drawing_layer, 0, 0, width, height) # current into temp storage
        drawing_layer.clear()
        drawing_layer.draw_image(old_layer, 0, 0, width, height) # drawing gets the old image
        old_layer.clear()
        old_layer.draw_image(interaction_layer, 0, 0, width, height) # recover the temp storage
        interaction_layer.clear()
    
def erase(b):
        old_layer.clear()
        old_layer.draw_image(drawing_layer,0,0,width,height)
        drawing_layer.clear()
    
def save(b):
    with hold_canvas(save_layer):
        save_layer.clear()
        save_layer.draw_image(background_layer, 0, 0, width, height) ## save the current image for undo, if needed
        save_layer.draw_image(drawing_layer, 0, 0, width, height) ## save the current image for undo, if needed
    save_layer.to_file('my_file.png')
    
interaction_layer.on_mouse_down(on_mouse_down)
interaction_layer.on_mouse_up(on_mouse_up)
interaction_layer.on_mouse_move(on_mouse_move)

background_layer.draw_image(image, 0, 0, width, height)

save_layer.draw_image(background_layer, 0, 0, width, height) ## save the current image for undo, if needed


old_layer.stroke_style = 'Black'
old_layer.fill_style = 'Black'
old_layer.line_width = 6


mode_button = RadioButtons(
    options=['sketch', 'line', 'rectangle','circle','ellipse'],
    value='sketch', 
    layout={'width': 'max-content'}, 
    description='Draw:',
    disabled=False
)
slider = IntSlider(description='Line width:', value=3, min=1, max=20)
picker = ColorPicker(description='Color:', value='Black')
check = Checkbox(description='Filled',value=False)
erase_button = Button(description="Erase")
undo_button = Button(description="Undo")
save_button = Button(description="Save to File")

undo_button.on_click(undo)
erase_button.on_click(erase)
save_button.on_click(save)

link((picker, 'value'), (drawing_layer, 'stroke_style'))
link((picker, 'value'), (interaction_layer, 'stroke_style'))
link((picker, 'value'), (drawing_layer, 'fill_style'))
link((picker, 'value'), (interaction_layer, 'fill_style'))
link((slider, 'value'), (drawing_layer, 'line_width'))
link((slider, 'value'), (interaction_layer, 'line_width'))

AppLayout(center=canvas, \
          footer=HBox([mode_button, VBox([slider, picker, check]),VBox([undo_button,erase_button,save_button])]) )<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10514596734289955, 'fraction_numerical': 0.009648688767936665, 'mean_word_length': 4.006191950464396, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 10, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '2', 'dir': 'jupyter-structured-clean-dedup', 'id': '618785', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2548, 'n_tokens_neox': 2528, 'n_words': 724, 'path': 'hand_drawing_4.ipynb', 'repo_name': 'misterhay/Drawing-tools', 'size': '8095'}