Today: Canvas drawing, draw functions, drawing a grid, function black-box if we have time

Download draw1 Example Project

draw1.zip

Expand the .zip to get a "draw1" folder with .py files in it. Run PyCharm. Use Open... menu to open the ***folder***

Then use the "Terminal" tab at lower left to run by command line as shown below. Command line is the "pro" way to control the computer.

For more detail see guide: Command Line.

Parameters - Look Again

1. bit.paint('blue')

2. def draw_oval(canvas, x, y, width, height):

Drawing on a Canvas

Draw Canvas Functions

We'll use the CS106A "DrawCanvas" which supports these functions:

    def draw_line(x1, y1, x2, y2):
        """
        Draws a black line between points x1,y1 and x2,y2
        Optional color='red' parameter can specify a color.
        """

    def draw_rect(x, y, width, height):
        """
        Draws a 1 pixel rectangle frame with its upper left at x,y
        and covering width, height pixels.
        Takes optional color='red' parameter.
        """

    def fill_rect( x, y, width, height):
        """
        Draws a solid black rectangle with its upper left at x,y
        and covering width, height pixels.
        Takes optional color='red' parameter.
        """

    def draw_oval(x, y, width, height):
        """
        Draws a 1 pixel oval frame with its upper left bounding rect at x,y
        and covering width, height pixels.
        Takes optional color='red' parameter.
        """

    def fill_oval(x, y, width, height):
        """
        Draws a solid black oval with its upper left bounding rect at x,y
        and covering width, height pixels.
        Takes optional color='red' parameter.
        """

    def draw_string(x, y, text):
        """
        Draws a black text string with its upper left at x,y
        Takes optional color='red' parameter.
        """

Calling Canvas Functions

These are noun.verb functions on a Canvas like this:

# Create a 500x300 canvas, draw a red line on it
canvas = DrawCanvas(500, 300)
canvas.draw_line(0, 0, 100, 100, color='red')
...

Example (a) draw_oval()

The "oval" figure has a black rectangle at its outer boundary. Then a yellow oval inset by 20 pixels on 4 sides. Then 2 red lines making a big X. Running the program draws 2 copies of the figure - one upper-left, one lower-right, which helps with testing.

alt: 2 oval figures

Thinking about math to position the oval:

alt: x,y of oval is x + 20, y + 20

def draw_oval(canvas, x, y, width, height):
    """
    Given a canvas and x,y and width,height
    Draw the "oval" figure inside these bounds.
    (this code is complete)
    """
    # Draw outer rectangle
    canvas.draw_rect(x, y, width, height)

    # Draw oval set in by 20 pixels all around
    canvas.fill_oval(x + 20, y + 20, width - 40, height - 40, color='yellow')

    # Draw red lines
    # upper-left to lower-right
    canvas.draw_line(x, y, x + width - 1, y + height - 1, color='red')
    # lower-left to upper-right
    canvas.draw_line(x, y + height - 1, x + width - 1, y, color='red')
    # Note: better style would be to have a MARGIN constant = 20, and the oval line
    # is as below. We used 20 above to keep the example simple. We'll use constants
    # like this on a later homework.
    # canvas.fill_oval(x + MARGIN, y + MARGIN, width - 2 * MARGIN, height - 2 * MARGIN, color='yellow')

What are the left, right, top, bottom corner Coordinates?

Run From Command Line

The main() in this case calls draw_oval() twice, once upper left and once lower right. This tests that the x,y are handled correctly. Open the "terminal" at the lower left of PyCharm. The "$" below is the prompt, the rest you type (on Windows its "python" or "py" instead of "python3".) Close the drawing window when done, this exits the program.

$ python3 draw1.py -oval 300 200

Use The Up Arrow

In the terminal, hit the up arrow. Edit the old command and run it again easily. This is a very productive and fast way to run and vary your program.

Accessibility Screen Zoom In

Example (b) draw_lines1()

alt: 2 lines1 figures

How To Spread Points Proportionately

for i in range(n):
    y_add = (i / (n - 1)) * (height - 1)

alt:lines1 figure, y_add measures y delta from top for each line

draw_lines1() Solution

def draw_lines1(canvas, x, y, width, height, n):
    """
    Draw the lines1 figure within x,y width,height
    """
    canvas.draw_rect(x, y, width, height)
    # Figure y_add for each i in the loop
    for i in range(n):
        y_add = (i / (n - 1)) * (height - 1)  # formula: 0..1 fraction * max
        canvas.draw_line(x, y, x + width - 1, y + y_add, color='red')

Run from command line

$ python3 draw1.py -lines1 300 200 12

Example (c) draw_lines2()

alt: 2 lines2 figures

Run from the command line:

$ python3 draw1.py -lines2 300 200 12

draw_lines2() Solution Code

def draw_lines2(canvas, x, y, width, height, n):
    """
    Draw the lines2 figure within x,y width,height
    The code for lines1 is already here
    """
    canvas.draw_rect(x, y, width, height)

    for i in range(n):
        y_add = (i / (n - 1)) * (height - 1)  # formula: 0..1 fraction * max
        canvas.draw_line(x, y, x + width - 1, y + y_add, color='red')

    # loop to draw green "lines2" lines
    for i in range(n):
        pass
        y_add = (i / (n - 1)) * (height - 1)
        x_add = (i / (n - 1)) * (width - 1)
        canvas.draw_line(x, y + y_add, x + x_add, y + height - 1, color='green')

Example (d) draw_grid1()

alt: grid of rects

>>> 500 / 11
45.45454545454545    # float division with fraction
>>> 500 // 11        # int division - what we want here
45

For each row/col number, what is its x,y coord?

Solution code

def draw_grid1(width, height, n):
    """
    Creates a canvas.
    Draws a grid1 of n-by-n black rectangles
    (this code is complete)
    """
    canvas = DrawCanvas(width, height, fast_draw=True)

    # Figure sizes for all sub rects
    sub_width = width // n
    sub_height = height // n

    # Loop over row/col
    for row in range(n):
        for col in range(n):
            # Figure upper left of this sub rect
            x = col * sub_width
            y = row * sub_height
            canvas.draw_rect(x, y, sub_width, sub_height)

Run from command line:

$ python3 draw1.py -grid1 600 400 12

Revisit draw_lines2()

Here is the draw_lines2() def first line:

def draw_lines2(canvas, x, y, width, height, n):
    """
    Draw the lines2 figure within x,y width,height
    The code for lines1 is already here
    """

Example (e) draw_grid2()

draw_grid2() Solution Code

def draw_grid2(width, height, n):
    """
    Creates a canvas.
    Add code to draw the lines2 figure in each grid sub rect.
    """
    canvas = DrawCanvas(width, height, fast_draw=False)

    sub_width = width // n
    sub_height = height // n

    for row in range(n):
        for col in range(n):
            pass
            # Your code here - draw a lines2 figure in each grid rect
            x = col * sub_width
            y = row * sub_height
            # key line: call the function to draw the figure here
            draw_lines2(canvas, x, y, sub_width, sub_height, n)

Looks neat - loops + function-call = power!

alt: grid lines2 figures


Here is the "function black box" material, which can look at today if we have time, or start it next time...

Black Box - Big Picture

Theme: "Black Box" Function Model

Black-box model of a function: function runs with parameters as input, returns a value. All the complexity of its code is sealed inside. Calling it is relatively simple - provide parameters, get back an answer.

alt: function is black box with parameters in and result out

4 Functions Today

(had a tech problem in lecture, now fixed)

> Black Box Functions

1. Parameters = Input

2. return yields the result of a function

Inside the body of a function, like this:

def foo(n):
    ...
    return n * 10
    ...

Suppose the caller code looks like

   # in caller function
   a = foo(6)
   b = foo(7)

Example 1. "Winnings1" Function

in out
0  0
1  10
2  20
3  30
4  40
5  60
6  72
7  84
8  96
9  108
10 120

Winnings1 code

def winnings1(score):
    """Compute winnings1 described above."""
    if score >= 5:
        return score * 12
    
    # Run gets here: we know score < 5
    return score * 10

Winnings if-return Case Strategy

Return None Default