The WCK PixelCanvas Widget

June 10, 2003 | Fredrik Lundh

The following widget implements a simple drawing area, on top of the WCK library. Calls to the draw methods update an image memory (a pixmap object), which is used to update the screen when necessary.

See below for usage examples and a screenshot.

 
The WCK PixelCanvas Widget
from WCK import Widget

class PixelCanvas(Widget):

    ui_option_width = 640
    ui_option_height = 480

    def __init__(self, master, **options):
        self.pixmap = self.size = None
        self.ui_init(master, options)

    def ui_handle_config(self):
        return int(self.ui_option_width), int(self.ui_option_height)

    def ui_handle_resize(self, width, height):
        if (width, height) != self.size:
            background = self.ui_brush(self.ui_option_background)
            pixmap = self.ui_pixmap(width, height)
            pixmap.rectangle((0, 0, width, height), background)
            if self.pixmap:
                pixmap.paste(self.pixmap)
            self.pixmap = pixmap
            self.size = width, height

    def ui_handle_clear(self, draw, x0, y0, x1, y1):
        pass

    def ui_handle_repair(self, draw, x0, y0, x1, y1):
        if self.pixmap:
            draw.paste(self.pixmap)
        else:
            background = self.ui_brush(self.ui_option_background)
            draw.rectangle((x0, y0, x1, y1), background)

    def draw_line(self, xy, stroke="black", stroke_width=1):
        if self.pixmap:
            if stroke: stroke = self.ui_pen(stroke, stroke_width)
            self.pixmap.line(xy, stroke)
            self.ui_damage()

    def draw_rect(self, xy, fill=None, stroke=None, stroke_width=1):
        if self.pixmap:
            if fill: fill = self.ui_brush(fill)
            if stroke: stroke = self.ui_pen(stroke, stroke_width)
            self.pixmap.rectangle(xy, fill, stroke)
            self.ui_damage()

    def draw_ellipse(self, xy, fill=None, stroke=None, stroke_width=1):
        if self.pixmap:
            if fill: fill = self.ui_brush(fill)
            if stroke: stroke = self.ui_pen(stroke, stroke_width)
            self.pixmap.ellipse(xy, fill, stroke)
            self.ui_damage()

    def draw_polygon(self, xy, fill=None, stroke=None, stroke_width=1):
        if self.pixmap:
            if fill: fill = self.ui_brush(fill)
            if stroke: stroke = self.ui_pen(stroke, stroke_width)
            self.pixmap.polygon(xy, fill, stroke)
            self.ui_damage()

    def draw_text(self, xy, text, fill="black", font="helvetica"):
        if self.pixmap:
            self.pixmap.text(xy, text, self.ui_font(fill, font))
            self.ui_damage()

    def draw_image(self, xy, image):
        if self.pixmap:
            self.pixmap.paste(self.ui_image(image), xy)

Example #

 
from Tkinter import *

root = Tk()
root.title("WCK PixelCanvas")

canvas = PixelCanvas(root, background="white")
canvas.pack()

canvas.update()

canvas.draw_line((10, 10, 410, 410), "red")
canvas.draw_line((10, 410, 410, 10), "blue", 10)
canvas.draw_rect((120, 120, 220, 220), "green")
canvas.draw_ellipse((20, 20, 130, 130), "gold", "black")
canvas.draw_polygon((200, 270, 250, 150, 300, 270), "orange", "blue", 2)
canvas.draw_image((280, 280), PhotoImage(file="lena.gif"))
canvas.draw_text((50, 200), "WCK PixelCanvas", font="Helvetica 30")

root.mainloop()

Note the call to update; calls to the draw methods made before the widget has been displayed are lost. You don’t need to call this method if you’re calling the drawing methods from within your Tkinter program (for example, in response to a button press or a menu choice).

The above example creates a window looking something like:

Possible Improvements #

The widget currently ignores drawing operations that are made before the widget has been displayed. Maybe the widget should collect such calls in a list, and use that list to update the pixmap when it is first created. In the drawing methods, you could do something like:

 
    def draw_line(self, xy, stroke="black", stroke_width=1):
        if self.pixmap:
            if stroke: stroke = self.ui_pen(stroke, stroke_width)
            self.pixmap.line(xy, stroke)
            self.ui_damage()
        else:
            self.displaylist.append(self.draw_line, (xy, stroke, stroke_width))

To render the list, do:

    displaylist = self.displaylist
    self.displaylist = []
    for method, args in displaylist:
        method(*args)

Another problem is that the widget resizes the pixmap on each call to ui_handle_resize. If the user makes the widget very small, and changes the size back again, the contents will be lost. Maybe the widget should limit the minimum size to the current width and height settings?

    def ui_handle_resize(self, width, height):
        width = max(width, int(self.ui_option_width))
        height = max(height, int(self.ui_option_height))
        if (width, height) != self.size:
            ...

Or maybe the canvas should always be this size, and be drawn in the center of the actual window?

 

A Django site. rendered by a django application. hosted by webfaction.