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.
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?