Quadratic Bezier Curves


An open field. A large hamper, with an attendant in a brown coat standing behind it. The attendant opens the hamper and three pigeon fanciers, (in very fast motion) leap out and run off across the field, wheeling in a curve as birds do.

Or, in other words, smooth lines.  This is not the tutorial I had intended to write, but the tutorial I had intended to write is too long in coming.  I have been working on a stick figure program in Python and need to prototype drawing curved lines using Tkinter. I figured I may as well incorporate it into a tutorial.

A Loose End

At the end of the Being Animated tutorial I set some exercises – I hope you’ve done them by now.  One of the questions I asked was why I chose a width of 21 pixels and not 20?  There is no magic in the number 21 per se.  Rather, the issue is whether or not the number is an even or an odd number.  Harking back to our discussion about pixels you should realise [sic] that pixels on the screen are not like marks on a ruler.  Marks on a ruler take up no space pixels, on the other hand, do.  If you chose an even number of pixels for something you want to centre [sic] somewhere the centre pixel will be at the centre.  Then you will have a different number of pixels on either side.  To take an extreme example, if it was two pixels wide, then one pixel would be at the mouse’s location, and one would be left hanging.  There would not be a way to balance it.  However, if it was three pixels wide, one could be at the mouse position, and one could be on either side.

Introduction

In the Being Animated tutorial we also saw how to draw a line on a Tkinter Canvas Widget.  In this tutorial we’re going to look at how to draw a curved line. I have used the code from canvasLine2B.py in that tutorial as a starting point for the code below – save this to canvasCurve1.py:

# -*- coding: utf-8 -*-
from Tkinter import *

TITLE = "Drawing a Curve"
WIDTH = 200
HEIGHT = 200
CENTREX = WIDTH/2
CENTREY = HEIGHT/2

formatString = "x: %03d, y: %03d"

class Canvassing():
  def __init__(self, parent = None):
    self.canvas = Canvas(width=WIDTH,height=HEIGHT, bg = "blue")
    self.canvas.pack()
    self.readout = Label(text="This is a label")
    self.readout.pack()
    self.canvas.bind("<Motion>",self.onMouseMotion)
    self.line = None
    self.canvas.master.wm_title(string = TITLE)
    self.points = [(CENTREX-WIDTH/4, CENTREY-HEIGHT/4),
		    (CENTREX, CENTREY)
		    ]

  def onMouseMotion(self,event):  # really should rename this as we're doing something different now
    self.readout.config(text = formatString%(event.x, event.y))
    self.canvas.delete(self.line) # first time through this will be None
    # but Tkinter is ok with that
    self.line = self.canvas.create_line(self.points, (event.x,event.y), width=2, fill = "yellow")

Canvassing()
mainloop()

You can see that I’ve:

  • removed the magic number 100 from the code, and replaced it by WIDTH and HEIGHT – capitalised variable names to indicate that they are (or should be) global constants;
  • added a list called self.points which, at the moment, contains two points;
  • change the name of the second method to onMouseMotion (because it’s what is done when there’s a movement of the mouse);
  • increased the width of the line so it is a little easier to see;
  • changed the drawing code.  Now the two points above and the current position of the mouse (event.x, event.y) are fed into create_line;
    and
  • added a title to the window.

This gives us something that looks like this (except that the second line segment follows the mouse, something this still image doesn’t do justice to):

Exercise: Try some other values of WIDTH and HEIGHT to verify that the program still works.

Exercise: Try adding more entries to self.points (in the form (x,y) with commas between them all, but none after the last one)

Drawing nodes

To demonstrate that there are three points involved I am going to add some code to draw a small circle around each point, and moving the one under the mouse pointer with the mouse:

# -*- coding: utf-8 -*-
from Tkinter import *

TITLE = "Drawing a Curve"
WIDTH = 200
HEIGHT = 200
CENTREX = WIDTH/2
CENTREY = HEIGHT/2
NODE_RADIUS = 3
NODE_COLOUR = "red"
LINE_COLOUR= "yellow"

formatString = "x: %03d, y: %03d"

class Canvassing():
  def __init__(self, parent = None):
    self.canvas = Canvas(width=WIDTH,height=HEIGHT, bg = "blue")
    self.canvas.pack()
    self.readout = Label(text="This is a label")
    self.readout.pack()
    self.canvas.bind("<Motion>",self.onMouseMotion)
    self.line = None
    self.canvas.master.wm_title(string = TITLE)
    self.points = [(CENTREX-WIDTH/4, CENTREY-HEIGHT/4),
		    (CENTREX, CENTREY)
		    ]

  def onMouseMotion(self,event):  # really should rename this as we're doing something different now
    self.readout.config(text = formatString%(event.x, event.y))
    allItems = self.canvas.find_all()
    for i in allItems:  # delete all the items on the canvas
      self.canvas.delete(i)
    # deleting everything every time is inefficient, but it doesn't matter for our purposes.
    for p in self.points:
      self.drawNode(p)
    p = (event.x, event.y)  # now repurpose p to be the point under the mouse
    self.line = self.canvas.create_line(self.points, p, width=2, fill = LINE_COLOUR)
    self.drawNode(p)

  def drawNode(self, p):
      boundingBox = (p[0]-NODE_RADIUS, p[1]+NODE_RADIUS, p[0]+NODE_RADIUS,p[1]-NODE_RADIUS)
      # mixed + and - because y runs from top to bottom not bottom to top
      self.canvas.create_oval(boundingBox, fill=NODE_COLOUR)

Canvassing()
mainloop()

This gives something similar to the picture above, but with the nodes clearly shown:

Magic Pixie Dust

Once we’ve done this, drawing a curved line is a snap.  We simply add a new parameter, smooth, to the create_line method call:

    self.line = self.canvas.create_line(self.points, p, width=2, fill = LINE_COLOUR, smooth = True)

Now you can see the curve clearly shown as well as where the points are:

About these ads

3 Responses to Quadratic Bezier Curves

  1. Same problem with the tag being eaten by the website.

  2. Pingback: Python4Kids New Tutorial: Quadratic Bezier Curves | Tutorial WPAP

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 67 other followers

%d bloggers like this: