Python Graphics

[ LiB ]

Python Graphics

Choosing a graphics toolkit may be the most difficult choice when creating a game. There are hundreds of graphic kits to choose from and each is very different in style and language. This chapter only covers a handful of the graphics libraries available for Python programming, and goes through samples in only a few of the available optionsmainly the popular kits available for developing cross-platform.

Specifically, more coverage of Tkinter is given in this section, as Tkinter comes bundled with Python, is cross platform, and is commonly used as a GUI for Python programs. Pygame is probably the most popular Python game library in use today, and Pygame graphic calls have already been covered in some detail. A few OpenGL samples in Python are also examined at the end of this chapter.

NOTE

A number of commercial art tools are programmable with in Python scripts. Some of the more recognizable tools include Blender, Poser, Lightflow, and Softimage XSI. Each of these tools has a Python interface. Blender (i.e. gameBlender) uses Python as a scripting language, the Poser Pro pack includes a Python-scripting agent, Lightflow has a Python extension module, and Softimage is scriptable via Python.

For the aspiring developer, there are also many other graphic options available. Here, for starters, is a short list of Python GUI libraries and graphics kits:

  • The Standard Window Interface. STDWIN used to be the most commonly used GUI for Python, but is now largely unsupported. The library was meant to be a platform-independent interface to C-based Windows systems, but the module no longer exists in Python 2.0 or above, and I mention it mainly for legacy. It runs under UNIX and Mac, but was never ported to Windows .

  • The Wxpython library. Provides support for the wxWindows-portable GUI class library. Wxpythin uses the Lesser Gnu Public License and functions like a wrapper to the C++ wxWindows library. It is relatively cross platform, but not quite as portable as Tkinter.

  • The Pythonwin library. Pythonwin is also included in many standard Python distributions, but applications designed with it will only run on Windows. Pythonwin is a wrapper to the Microsoft Foundation Class Library, and provides features of the Windows user interface.

  • Wpy. An object-oriented, cross-platform class library system also based on the Microsoft Foundation Classes. Wpy is built to be simple and portable.

  • PyKDE. A set of Python bindings for the KDE classes written by Phil Thompson. PyKDE requires Sip to run.

  • PyGTK. A free software GUI toolkit that has a large number of widgets oriented towards the X Window System. PyGTK is distributed under the Lesser Gnu Public License and was developed for the GTK widget and GNOME libraries. The library is object-oriented and comes with lots of good samples.

  • GNOME Python. A set of bindings for the GNOME libraries that use PyGTK (which comes bundled with the package).

  • Wafepython. Wafe is short for Widget Athena Front End, and is a package for developing applications with high-level graphical user interfaces in Tcl. WafePython implements an interface between Tcl, the X Toolkit, the Athena Widget Set, the Motif Widget Set, and a few other classes and widget packages thrown in for good measure.

  • PyFLTK. FLTK stands for Fast Light Toolkit; it's a C++ GUI toolkit for UNIX, OpenGL and Win32. PyFLTK was originally created to build in-house apps for Digital Domain. Bill Spitzak is the original author and received permission from the company to distribute it under the Lesser Gnu Public License. Other developers have done more work on the toolkit since then, and the project has been moved to Sourceforge .

  • Fox Python. FXPy is a C++ toolkit for developing GUIs that runs on UNIX and Windows; it is distributed under the Lesser Gnu Public License. Fox's emphasis is on speed and ease of use. It uses techniques for increasing drawing speed and minimizing memory, and most controls can be built with a single line of code. Fox supports drag and drop, OpenGL widgets, 3D graphics, and tooltips.

  • Python X. An extension that binds Python together with Motif, which is a set of user interface guidelines set by the Open Software Foundation. Motif is actually over a decade old, and there are many books covering its use, but it has been somewhat in decline for a while.

  • The Python Computer Graphics Kit. A collection of Python modules for 3D computer-graphics images. The kit mainly focuses on Pixar's RenderMan interface, but some modules can also be used for OpenGL programs or non-RenderMan-compliant renderers.

  • Vpython. A free and open-source 3D programming library designed "for ordinary mortals ." The idea behind Vpython is ease of use and simplicity.

  • Zoe. A bare-bones OpenGL graphics engine written completely in Python. Zoe includes only basic 3D features, and focuses on creating 3D wire- frames for prototyping or rapid development.

  • The PyUI Library. An interface library written entirely in Python for Python. It can run on desktop Windows or in a 3D hardware-accelerated environment and is meant to be portable. PyUI was originally slated to build user interfaces for games . PyUI is owned by Sean Riley of Ninjaneering (see Chapter 5 for more information on Ninjaneering) and utilizes Python 2.1, Pygame, PyOpenGL, the Python Imaging Library, and the ActiveState win32 extensions.

  • PyQT. Qt for Windows is a C++ cross-platform GUI toolkit distributed by TrollTech, who have a free non-commercial version license and a pay commercial license. PyQT is a set of Python bindings to the C++ QT Toolkit, originally produced by the Kompany and now under River Bank Computing. The GUI toolkit runs on Windows, Mac OS X, and UNIX.

NOTE

TIP

GUIs are created with graphical elements called widgets, which are typically scrollbars, buttons , text fields, etc. Widgets are normally found within a window, which con trols the layout of the widgets.

Python also has a few basic built-in tools for graphics and image handling. These are included under its Multimedia Services modules, which are listed in Table 4.3.

Table 4.3. Python Multimedia Graphic Services

Module

Use

colorsys

Converting between RGB and other color systems

imageop

Manipulating raw image data

imghdr

Determining the type of image contained in a file or bytestream

rgbimg

Reading and writing image files in SGI RGB format


The imageop module can operate on 8- or 32-bit pixel images and has methods for cropping, scaling, dithering, and converting the image at a raw level. Colorsys can be used to convert RGB, HLS, HSV, and YIQ color systems. Python's imghdr can recognize a number of different image formats (as shown in Table 4.4) and is also extendable to allow even more types.

Table 4.4. Image Formats

Value

Image format

rgb

SGI ImgLib Files

gif

GIF 87a and 89a Files

pbm

Portable Bitmap Files

pgm

Portable Graymap Files

ppm

Portable Pixmap Files

tiff

TIFF Files

rast

Sun Raster Files

xbm

X Bitmap Files

jpeg

JPEG data in JFIF Format

bmp

BMP Files

png

Portable Network Graphics


The Tkinter Library

In the last chapter you built a small display box using Tkinter. Here you'll explore GUI creation with Tkinter in more depth. As you recall, Tkinter is an object-oriented interface that works on multiple platforms and is designed to be extensible so that it can be used to import third-party widgets.

Widgets

Tkinter comes with only a handful of standard widgets. Each widget has a standard set of methods and also supports a large set of general methods, so they are capable of a wide coverage. There is a lot more to widgets than what's listed in Chapter 3 (reprinted here as Table 4.5 for easy reference). This is because each of these components has its own place and use within a GUI, and therefore has its own components and methods associated with it.

Table 4.5. Tkinter Widget Components

Component

Function

Button

Creates a button that triggers an event when clicked

Canvas

Displays text or images

Checkbutton

Creates a Boolean checkbutton

Entry

Creates a line that accepts keyboard input

Frame

Creates the outlying window's edge

Label

Displays text as labels for components

Listbox

Creates a list of options

Menu

Creates a multiple-selection display

Menubutton

Creates a pop-up or pull-down style menu

Radiobutton

Creates a single option button

Scale

Creates a slider that can choose from a range

Scrollbar

Creates a scrollbar for other components

Text

Creates a multiple-line box that accepts user input

Toplevel

A widget container like Frame but with its own top-level window


NOTE

Tcl/TK

TK is a toolkit that handles the creation of windows, GUI events (widgets), and user interaction. The TK toolkit is provided as an extension for Tcl. Tkinter is an interface to Tcl; without the interface it would take hundreds of lines of code to do even simple things like open a window or create a button.

Many languages use or are capable of using TK. Tkinter is Python's behind-the-scenes director of the TK GUI toolkit, and Tcl is the behind-the-scenes director that Tkinter uses to communicate to TK. Both TK and Tcl are open-source developments that are under development at scriptics (the Tcl developer exchange can be found at http://dev. scriptics .com).

Button

Clickable buttons are probably the most widely used widget in any interface, and Tkinter has a many options available for button components; these are listed in Table 4.6.

Table 4.6. Button Properties

Property

Function

activebackground

Sets the background color

activeforeground

Sets the foreground color

bitmap

Displays a given bitmap as the button

default

Identifies the default button

disabledforeground

Sets a foreground color used when button is disabled (grayed out)

image

Sets an image to display in the widget (precedes bitmap)

state

Defines the button state (as NORMAL , ACTIVE , or DISABLED )

takefocus

Indicates whether the Tab key can be used to reach this button

text

Defines the text to display within the button

underline

An offset applied on text displayed to identify which character must be underlined

wraplength

Determines distance when text should be wrapped to the next line


Buttons also have their own special methods: flash() is a method which reverses and resets the foreground and background attributes, and invoke() is a method that executes the function defined in a command.

I used a button widget in the last chapter's GUI sample, inititated by the following code and looking like Figure 4.8 (a short Hello_Button.py sample is also given in this chapter's code section on the CD):

 Button(window, text='Exit', command=window.quit).pack(side=BOTTOM) 

This can be broken down into basic components. Button() is used to create the button, and the parameters placed within the Button() parentheses, ( window, text='Exit', command=win dow.quit ), define what the button can do. The pack() method extends Button() and defines where the button should be placed within the window, in this case side=BOTTOM .

Canvas

The Canvas widget component is used to draw everything from arcs to bitmaps to polygons. It is used as a way to customize graphical items, and resembles an artist's blank canvas, ready to be painted . A canvas in Tkinter, of course, has its own properties; these are listed in Table 4.7.

Hello_Canvas.py is given on the CD as a sample that produces a large widget surface, as shown in Figure 4.10.

Figure 4.10. Sample Canvas widget

graphic/04fig10.gif


Figure 4.9. The widget at work

graphic/04fig09.gif


Table 4.7. Canvas Properties

Property

Function

arc

Creates an arc or an arc item

bitmap

Creates a bitmap item

image

Creates an image item

line

Creates a line item

oval

Creates a circle or ellipse at the given coordinates

polygon

Creates a polygon item (three or more vertices) with the given coordinates

rectangle

Creates a rectangle item with the given coordinates

text

Creates a text item at the given position with the given options

window

Embeds a window widget to the canvas


Checkbutton

A Checkbutton is basically a box that can either be checked or unchecked; an example is shown in Figure 4.11 and a sample is included in the CD's source code as Hello_Checkbutton.py . Checkbuttons can have an on value and an off value set for whether the box is checked, and have a handful of methods available, as shown in Table 4.8.

Figure 4.11. A sample checkbutton

graphic/04fig11.gif


Table 4.8. Checkbutton Methods

Method

Function

select()

Selects the checkbutton and sets the value of the variable to onvalue

flash()

Reverses and resets the foreground/background colors

invoke()

Executes a function defined by command()

toggle()

Reverses the state of a button (i.e. off becomes on)


Entry

The Entry widget is designed to let users enter a single line of text within a frame or window. A sample Hello_Entry.py is included on the CD.

Frame

A Frame widget is used to group , arrange, and organize other widgets. It uses rectangular screen areas and padding to put them into view for a GUI. A sample Hello_Frame.py is included on the CD.

Label

A Label widget is a box that displays text or images. The Label widget allows you to create and update these displays, and a demonstration is given as Hello_Label.py on the CD.

Listbox

A Listbox widget creates lists of text items that can be selected by the user. Listboxes have three properties:

  • height. The number of rows in the list. Setting height to 0 allows listbox to automatically resize to the number of entries.

  • selectmode. Defines the type of list being created. This can be SINGLE , EXTENDED , MULTIPLE , or BROWSE .

  • width. The Number of characters in each row, which can also be automatically resized with the setting 0.

The Listbox widget also has a number of methods associated with it, as shown in Table 4.9.

Table 4.9. Listbox Methods

Method

Function

delete()

Deletes a given row, or the rows between the given row and lastrow

get()

Gets the string that starts at the given row

insert()

Inserts the given string at the given row

see()

Makes the row visible to the user

select_clear()

Clears the selection

select_set()

Selects the rows starting at startrow and ending at endrow


A Listbox example is on the CD as Hello_Listbox.py .

Menu

There are three types of Menu widgets: pop-up, toplevel, and pull-down. There are also special menu widget item types such as radio menu items and check menu items. A sample menu is given as Hello_Menu.py . Menus , of course, have their own methods, as listed in Table 4.10:

Table 4.10. Menu Methods

Methods

Function

add_command()

Adds a menu item

add_radiobutton()

Creates a radio button menu item

add_checkbutton()

Creates a check button menu item

add_cascade()

Creates a new hierarchical menu

add_separator()

Adds a separator line to the menu

add()

Adds a specified type of menu item

delete()

Deletes the menu items from startindex to endindex

entryconfig()

Modifies a menu item

index()

Returns the index number to the given menu item


These methods have their very own options available to them, as shown in Table 4.11.

Menubutton

Menubuttons can be used to display menus, but are in decline since the Menu widget has been expanded to include most of the Menubutton functionality.

Message

Message is very similar to the Label widget, and is used to create a multiple line non -editable object that displays text.

Radiobutton

Radio button widgets are multiple-choice buttons. Each group of radio buttons must be associated to the same variable, and each Radiobutton must represent a single value at any given time. Radiobuttons have their own properties:

  • command. Function to be called when the button is clicked.

  • variable. Variable to updated when button is clicked.

  • value. Defines the value that is stored in the variable when button is clicked.

Table 4.11. Menu Widget Method Options

Option

Function

accelerator

A keyboard alternative to a menu option

command

Names the callback function when the menu item is selected

indicatorOn

Adds a switch next to the menu options

label

Defines the text of the menu items

selectColor

Switches color (with indicatorOn )

state

Defines menu item status ( normal , active , or disabled )

onvalue

Values to be stored in the variable property

offvalue

Values to be stored in the variable property

tearOff

Creates a clickable separator at the top of the menu

underline

Defines the index position of the character to be underlined

variable

Variable used to store a value


Radiobuttons also have their own special methods:

  • flash(). Reverses forground and background colors.

  • invoke(). Executes command function.

  • select(). Selects the radio button.

A Radiobutton is shown in Figure 4.12 and a sample is included in the CD samples as Hello_Radiobutton.py .

Figure 4.12. A radio button

graphic/04fig12.gif


Scale

A scale widget is a graphical slider object that allows a user to select values from a scale. Scale has its own unique methods:

  • get(). Gets the current scale value.

  • set(). Sets the scale to a specified value.

Hello_Scale.py is included on the CD as a sample and Figure 4.13 displays the output of the sample code.

Figure 4.13. A Scale sample widget

graphic/04fig13.gif


Scrollbar

A scrollbar widget is used to select from a vertical scroller and works with listbox , text , and canvas . Scrollbar in Tkinter has the same methods available as scale :

  • set(). Defines fractions between 0 and 1 that delimit the view.

  • get(). Returns the current scrollbar configuration settings.

A sample scrollbar is incuded on the CD ( Hello_Scrollbar ) and also illustrated in Figure 4.14.

Figure 4.14. A scale sample widget

graphic/04fig14.gif


Text

Text allows the editing and formatting of multiple lines of text and has a number of available methods, as listed in Table 4.12.

Table 4.12. Text Methods

Method

Function

delete()

Deletes specified character(s)

get()

Returns specific character(s)

index()

Returns absolute value of an index

insert()

Inserts string at a specified index

see()

Returns true if the text located at a given index is visible


There are also a few available attributes for text:

  • state. Sets text to editable or non-editable with the flags normal or disabled .

  • tabs. Provides a list of strings and identifies table stops on the Text widget.

Text widgets support bookmark positions , called Marks; the naming of regions of texts , called Tabs; and specific locations, called Indexes, to help them organize text. Each of these threeMarks, Tabs, and Locationshas access to specified methods.

Toplevel

The Toplevel widgets are directly managed by the window manager; its methods are listed in Table 4.13.

Universal Widget Methods

All widgets in Tkinter also have standard universal options for defining things they have in common. They all use a similar syntax, and are listed in Table 4.14.

There are also methods inherited from the base Tk classes that are provided for all Tkinter widgets, including the toplevel object created by the Tk() method. These always apply to the widget that makes the method call, and are listed in Table 4.15. Take notice of the idea of focus with these methods. The window or widget that is in focus is the one that is toplevel to the viewer.

Table 4.13. Toplevel Methods

Method

Function

aspect()

Controls the relation between height and width

client()

Used in X windows to define WM_CLIENT_MACHINE

colormapwindows()

Used in X windows to define WM_COLORMAP_WINDOWS

command()

In X defines WM_COMMAND

deiconify()

Displays the window

frame()

Returns the window identifier

focusmodel()

Sets the focus model

geopmetry()

Changes the window's geometry

group()

Adds given window to the window group

iconbitmap()

Defines a bitmap for when the window is iconified

iconify()

Turns the window into an icon

iconmask()

Defines an icon bitmap for when the window is iconified

iconname()

Defines an icon name for when the window is iconified

iconposition()

Defines a suggestion for where the icon goes when the window is iconified

iconwindow()

Defines the icon window that should be used as an icon

maxsize()

Defines the maximum size for the window

minsize()

Defines the minimum size for the window

overrideredirect()

Defines a flag different from 0, and tells the window manager not to add a title or borders to the window

positionfrom()

Defines the position controller

protocol()

Registers a function with a callback

resizable()

Defines resize flags

sizefrom()

Defines size controller

state()

Returns the current state of the window, being normal, iconic, withdrawn, or icon

title()

Defines the window title

transient()

Turns window into a temporary window for the given master which is automatically hidden

withdrawn()

Removes the window from the screen


Table 4.14. Standard Tkinter Widget Options

Standard Widget Option

Properties

height

Defines height in number of characters or pixels

width

Defines width in pixels or number of characters

background or bg

Defines background color

foreground or fg

Defines foreground color

relief

Defines border style

highlightcolor

Defines color used to draw the highlight region when widget has keyboard focus

highlightbackground

Defines color used to draw the highlight region when widget does not have keyboard focus

highlightthickness

Defines highlight region width in pixels

borderwidth or bd

Width of widget relief border in pixels

text

Contains widget caption text, formatted by foreground and font

justify

Sets LEFT , RIGHT , or CENTER for text captions

font

Can define font family, font size, and font values like bold, underline, and overstrike

command

Associates a widget with a Python function

variable

Maps widget to a variable

anchor

Defines location of a widget within a window or of text within a widget

padx

Defines padding on the x-axis to border

pady

Defines the padding on the y-axis to border

cursor

Defines mouse pointer when moved over widget


NOTE

CAUTION

Colors can vary from platform to platform. For instance, the Windows operating system has system color settings for windows in the Control Panel, while the UNIX X Window System keeps them in an xrgb text file. This could cause GUI color choices to change slightly (or radically ) from one operating system to the next.

Table 4.15. Tkinter Widget Methods

Method

Function

cget()

Returns a string that contains the current configuration value for a given option

config()

Sets the values for one or more options

configure()

Same as config()

destroy()

Destroys the widget

focus()

Sets the widget to a keyboard focus

focus_set()

As focus()

focus_display()

Returns the name of the window that contains the widget and has focus

focus_force()

Gives keyboard focus to the widget

focus_get()

Returns the identity of the window that has focus

focus_lastfor()

Returns the window that last had focus

getvar()

Returns the value of a Tkinter variable

grab_set()

Grabs all events for the entire screen for the widget

grab_release()

Releases grab on a widget

grab_set_global()

Returns none , local , or global depending upon the grab value set to a window

keys()

Returns all options available for a widget as a tuple

lift()

Moves a widget to the top of the window stack

tkraise()

Same as lift()

lower()

Moves a widget to the bottom of the windows stack

mainloop()

Activates the mainloop event

quit()

Quits the mainloop event

setvar()

Sets a value to a given Tkinter variable

update()

Processes all queued tasks

update_idletasks()

Processes all pending idle tasks

tk_focusNext()

Returns the next widget that should have keyboard focus

tk_focusPrev()

Returns the previous widget that should have keyboard focus

wait_variable()

Creates a local event that waits for the given Tkinter variable to change

wait_visibility()

Creates a local event that waits for the given widget to become visible

wait_window()

Creates a local event that waits for a given widget to be destroyed


There are also specific methods for all widgets that work within windows. For ease of reference, they begin with a winfo (short for Window Information). These methods are listed in Table 4.16.

Table 4.16. Widget Window Information Methods

Method

Function

winfo_cells()

Returns the number of cells in the widgets color map

winfo_children()

Returns a list of widget instances

winfo_class()

Returns the Tkinter class name for widget

winfo_colormapfull()

Returns true if a widget's colormap is full

winfor_containing()

Returns the identity of the widget at the given x + y coordinates

winfo_depth()

Returns bit depth of the widget (8, 16, 24, or 32 bits per pixel)

winfo_exists()

Returns true if a Tk window corresponds to the given widget

winfo_fpixels()

Returns the result of the conversion of the given distance to the corresponding number of pixels (in floating point value)

winfo_geometry()

Returns a string showing the widget coordination in pixels

winfo_height()

Returns pixel height

winfo_width()

Returns pixel width

winfo_id()

Returns window identity

winfo_ismapped()

Returns true if a widget is mapped by the window system

winfo_manager()

Returns the name of the geometry manager

winfo_name()

Returns widget name

winfo_parent()

Returns widget parent

winfo_pathname()

Returns pathname of widget

winfo_pixels()

Same as winfo_fpixels() except returns a regular integer instead of a floating point value

winfo_pointerx()

Returns the x coordinate of the mouse pointer in pixels (must be in widget window)

winfo_pointery()

Returns the y coordinate of the mouse pointer in pixels (must be in widget window)

winfo_reqheight()

Returns minimum height required by widget to be displayed

winfo_reqwidth()

Returns minimum width required by widget to be displayed

winfo_rootx()

Returns the pixel coordinates of a widget's upper-left corner

winfo_rooty()

Returns the pixel coordinates of a widget's upper-left corner

winfo_screen()

Returns the screen name for the current window

winfo_screencells()

Returns the number of cells in the default color map for widget's screen

winfo_screendepth()

Returns the bit depth of the window target

winfo_screenheight()

Returns the height of a widget screen in pixels

winfor_screenwidth()

Returns width of widget screen in pixels

winfo_screenmmheight()

Returns screen height but in millimeters

winfo_screenmmwidth()

Returns screen width but in millimeters

winfo_screenvisual()

Returns the default visual class used for widget's screen (i.e. grayscale, truecolor , staticcolor, and so on)

winfo_toplevel()

Returns the widget instance of the top-level window containing the widget

winfo_visual()

Returns the visual class used for the widget (grayscale, truecolor, staticcolor, etc.)

winfo_x()

Returns x axis pixel coordinates corresponding to the widget's upper-left corner, relative to upper-left corner of the parent

winfo_y()

Returns y axis pixel coordinates corresponding to the widget's upper-left corner, relative to upper-left corner of parent


Tkinter Geometry

Tkinter widgets have specific geometry management methods that are used to organize widgets in their area. These methods are organized in three classes that help a UI designer develop an interface. The methods are pack() , grid() , and place() .

Using these methods is fairly effortless. First you create a widget. In the last chapter you created a widget frame called window :

 Import Tkinter * window = frame() 

After you have a widget, you can simply and easily apply pack() , grid() , or place() directly on it:

 window.pack() window.grid() window.place() 

Using these three methods is very important in organizing a GUI interface, so I'll cover each one in the next subsections.

pack()

The pack() method is used to organize widgets in blocks before placing them in the parent widget. pack() adds a widget to a frame or window based on the order that the widgets are packed. If you don't specify how the widgets are to be packed, they are simply placed top to bottom in the available space. You can, however, specify placement with options like anchor or side . The pack() method has a few built-in methods, shown in Table 4.17.

Table 4.17. pack() Method Options

Option

Use

Expand

Expands a widget to use up available space

Fill

Defines how a widget should fill a parcel or frame

Ipadx

Used with fill to define space in pixels around an object

Ipady

Used with fill to define space in pixels around an object

Padx

Defines space in pixels between widgets

Pady

Defines space in pixels between widgets

Side

Defines where you want to place the widget ( chosen from TOP , BOTTOM , LEFT , and RIGHT )


NOTE

TIP

The default is to use pixels to define measurement in pack() , but you can define different measurements, such as onscreen centimeters (c), onscreen millimeters (m), inches (i), and printer points (p). You specify which measurement to use by adding the letters to the options measurements:

 # this specifies padding to be in inches window.pack(padx=4i, pady=5y) 

grid()

The grid() method is used to organize widgets via a table within the parent widget. grid() creates a grid pattern (go figure) within a frame, and then allocates space to each cell in the grid to hold a widget. This grid starts are location (0,0) at the top left of the window. Grid() has a few methods, outlined in Table 4.18.

Table 4.18. grid() Method Options

Option

Use Example

Column

Specifies the column number

Columnspan

To make a widget span multiple (default is 1 column)

Row

Specifies the row number

Rowspan

To make a widget span multiple rows (default is one row)


place()

The place() method is used to place widgets in specific a specific position in the parent widget. place() allows you to set the exact position and size of each widget, in terms of absolute or relative coordinates. The place() method can use the options listed in Table 4.19.

Table 4.19. place() Method Options

Option

use

Anchor

Defines coordinates by (by compass: N , S , E , W , NE , NW , SE , SW , or CENTER ). Default value is NW

Bordermode

Defines INSIDE or OUTSIDE

Height

Defines widget height in pixels

In

Places widget in a position relative to the given widget ( in_ )

Relheight

Defines relative height in reference to in_

Relwidth

Defines relative width in reference to in_

Rely

Defines relative position in, reference to in_

Relx

Defines relative position in reference to in_

Width

Defines widget width in pixels

Y

Define absolute position of widget on y-axis, default 0

X

Define absolute position of widget on x-axis, default 0


Tkinter Events

Events in Tkinter are user events like keyboard presses and mouse movements. Tkinter handles events by creating bindings for specific objects. You can bind events to a widget, to the widget's Toplevel window, to a widget's class, or to an entire application.

Once an event has been bound to a widget, you specify a callback, which is a function that is called when the event happens. Let's say you had a function called My_Event :

 def My_Event():         //does something here 

Let's say you want My_Event to be called by a widget button called My_Button :

 My_Button = Button() 

The My_Button widget can call My_Event by simply including a command option on one line:

 My_Button['command'] = My_Event 

You can assign events to keyboards and mouse presses as well, as shown in Table 4.20 and Table 4.21.

Table 4.20. Tkinter Mouse Events

Event

Effect

<Button -1>

Mouse button (left) is pressed over widget

<Button -2>

Mouse button (middle) is pressed over widget

<Button -3>

Mouse button (right) is pressed over widget

<B1 Motion>

Mouse is moved with the button held down (dragged)

<ButtonRelease -1>

Mouse button is released

<Double Button - 1>

A double click

<Enter>

Mouse pointer enters widget

<Leave>

Mouse pointer leaves widget


Table 4.21. Tkinter Keyboard Events

Event

Effect

<Alt -x>

Pressed Alt and another key

<Control -X>

Pressed Ctrl and another key

<Escape>

Pressed the Esc key

<key>

Press any key (carries the character pressed via a callback)

<Return>

Pressed the Enter key

<Shift -X>

Pressed Shift and another key


The object that originated the callback exposes the attributes for events. These attributes are listed in Table 4.22.

Table 4.22. Tkinter Event Attributes

Object

Attribute

Char

Character code of pressed key

Height

New height of a widget in pixels

Keycode

Key code of a pressed key

Keysym

Key symbol of a pressed key

Num

The mouse button number associated with an event (usually 1, 2, or 3)

Type

The event type

Widget

The widget instance

Width

New width of a widget in pixels

X

The current position in pixels of the mouse on the x-axis

X_root

The current x-axis position of the mouse in pixels relative to the upper-left corner of the screen

Y

The current position in pixels of the mouse on the y-axis

Y_root

The current y-axis position of the mouse in pixels relative to the upper-left corner of the screen


NOTE

TIP

For Tkinter mouse events, you will often find <Button 1 > replaced with <ButtonPress-1> or <1> , all of which are correct syntactically. These changes work for the middle and right-side buttons as well.

For Tkinter keyboard events, most keys can be represented by placing them within less than and greater than symbols ( <F1> , <Cancel> , and <End> , for example).

There are also methods used to handle a callback by binding a Python function or method to an action that can be applied to a widget. These are shown in Table 4.23.

Table 4.23. Tkinter Event Callbacks

Method

Event

after()

Alarm callback called after given time in milliseconds

after_cancel()

Cancels an alarm callback

after_idle()

When the system is idle, registers a callback

bindtags ()

Returns the search order used by widget

bind()

Defines the callback that must be associated to a given event

bind_all()

Defines the callback that must be associated to a given event at the application level

bind_class()

Defines the callback that must be associated to a given event at the given widget class

<Configure>

Widget is resized or moved to a new location

unbind()

Removes bindings for the given event

unbind_all()

Removes bindings at the application level

unbind_class()

Removes bindings for the given event at the given widget class


Finally, Tkinter has protocols to handle events that communicate between the window manager and the GUI. This allows an application to intercept messages from the system and act accordingly . These protocols were original established for the X system, but Tk can handle events on multiple platforms. The syntax to bind a protocol to a handle event is as follows :

 widget.protocol(protocol, handler) 

In order for the widget to intercept a system message it needs to be on the Toplevel. The handler is almost always a function.

Tkinter Images

Tkinter uses the image class as a foundation to display graphic objects. Graphic objects Tkinter can display include both bitmap ( BitmapImage ) and GIF ( PhotoImage ) images. The functions image_names and image_types are used to handle all the images within the image class. The first returns a list containing the names of all available images, and the second returns a list that contains all the existing types that were created.

Images, once created, provide a handful of methods: image.width() , image.type() , and image.height() .

BitmapImage

BitmapImage is used to display bitmap images on widgets. In Tkinter, however, a bitmap not a .bmp format image. Bitmaps are actually two color images (well, two colors and a transparency mask to be precise) and have the options listed in Table 4.24.

Table 4.24. BitmapImage Options

Method

Purpose

cget()

Returns value of the given option

config()

Changes image options

configure()

Changes image options

height()

Returns height in pixels

width()

Returns width in pixels

type()

Returns the bitmap string


These options have methods available to them, listed in Table 4.25.

Table 4.25. BitmapImage Option Methods

Method

Used For

background

Background color

data

String to be used instead of a file

file

File to be read

foreground

Foreground color to be used

format

Specifies the file handler to be used

maskdata

String that defines the contents of the mask

maskfile

Specifies mask file

height

Gives image dimensions

width

Gives image dimensions


PhotoImage

PhotoImage is used for displaying full color images; it supports GIF and PPM files and has attributes as listed in Table 4.26.

Table 4.26. PhotoImage Attributes

Attribute

Holds

data

String to be used instead of a file

file

File to be read

height

Dimensions

width

Dimensions


The PyOpenGL Library

PyOpenGL is an OpenGL widget written by a large group of developers, including David Ascher, Mike Hartshorn, Jim Hugunin, and Tom Schwaller. PyOpenGL includes OpenGL bindings for Python created using the Simplified Wrapper and Interface Generator (SWIG) and distributed under open source licenses. It supports OpenGL v1.0, OpenGL v1.1, GLU, GLUT v3.7, GLE 3, WGL 4, and Togl (Tk OpenGL widget). PyOpenGL is also interoperable with Tkinter, wxPython, FxPy, PyGame, and Qt and a large number of other external GUI libraries for Python. It has a very active following and a regularly updated sourceforge project page at http://pyopengl.sourceforge.net/.

OpenGL has the reputation of being difficult to learn. Hey, there are reasons why they pay game developers the big bucks! Python's version of OpenGL is no different than any other version, and OpenGL looks pretty similar no matter what language you're playing with.

The reason OpenGL is considered difficult to pick up is because three-dimensional graphics programming can be a fairly difficult subject just on its own. Since OpenGL is fairly difficult to master, this section covers just a few examples. If you discover, as many programmers do, that OpenGL is your calling, then I recommend that you pick up OpenGL Game Programming by Kevin Hawkins and Dave Astle.

Using OpenGL in Python is quite an advantage over other languages, however, because Python and Pygame make several complex steps much easier. For instance, I use the python.game window in these examples to open up a window for displaying graphics. This could take dozens of lines of code in a nonhigh-level language, but it only takes two in these examples. You also do not have to worry about freeing and releasing memory for all of the complex graphics calls and routines. However, having no control over memory allocation and de-allocation can cause problems.

NOTE

OpenGL

OpenGL is a standard graphics library originally created by Silicon Graphics. Back then it was called GPL, and only ran on SGI hardware. SGI eventually turned their technology into an open standard and licensed it to different machines. OpenGL may be the premier development tool for developing portable 2d and 3d applications, and it has also been a standard since the early 1990s.

OpenGL is free for application and game designers. It is an owned technology, but the licensing applies to venders of hardware (i.e., the graphic card makers ) that wish to utilize the technology, not the software developers. SGI is currently working towards modifying the license into a true open source license. This makes OpenGL very popular among game developers, and many commercial games have used it, from Activision's Quake , to Blizzard's Diablo , to Bioware's NeverWinter Nights .

Installing PyOpenGL

PyOpenGL needs a handful of dependencies in order to access all of its functionality. Luckily, most of these will already be installed if you've been playing with the code in this chapter. PyOpenGL needs Python 2.2 or higher, Tcl/Tk, OpenGL, GLU (which should come pre-installed on most modern machines and with most modern graphics card), the OpenGL Utility Toolkit (or GLUT for short), and Numeric Python.

The OpenGL Context may also require a few dependencies, depending on the platform. Those dependencies that are freely distributable are on this book's CD, under \PYTHON\PYOPENGL\DEPENDENCIES, except for Numeric Python, which has its own folder (\PYTHON\NUMERIC PYTHON). The standard binary installers for PyOpenGL are located on the CD under \PYTHON\PUOPENGL. The source and project page for PyOpenGL can be found at Sourceforge, which is where you will want to look for the latest updates and news: http://pyopengl.sourceforge.net/documentation/installation.html

Using PyOpenGL

There are four libraries to PyOpenGL, each of which is normally imported separately:

  • GL. The basic, primitive library.

  • GLU. Short for GL utilities; includes more advanced commands than GL.

  • GLX. GL for X_Windows.

  • GLUT. GL Utilities Toolkit, which has even more sophisticated windowing features.

For these samples you will be using both GL and GLU:

 from OpenGL.GL import * from OpenGL.GLU import * 

To make things easier, you will also be using bits of the Pygame library:

 import pygame from pygame.locals import * 

First a small program creates a PyOpenGL Window with a graphic on a Win32 platform. This first program, labeled OpenGL_1.py in this chapter's code section on the CD, also sets the precedent for each PyOpenGL example that follows, so pay attention!

Presenting a Window in PyOpenGL

If you look at the sample code, the first thing you do after giving Python and Pygame access to the PyOpenGL libraries through import statements is to declare a couple of variables , like so:

 rquad = 0.0 xrot = yrot = zrot = 0.0 textures = [0,0] 

These are variables you'll use in later examples, not for this first simple one, so you can ignore them for now.

After the variables you define how to size the window or PyOpenGL scene. Do this by creating a windowsize function. This function will be called to set up the window or scene at least once when the program is first run, and when it is called, it will be given the height and width you want the window to be:

 def windowresize((width, height)):     glViewport(0, 0, width, height)     glMatrixMode(GL_PROJECTION)     glLoadIdentity()     gluPerspective(45, 1.0*width/height, 0.1, 100.0)     glMatrixMode(GL_MODELVIEW)     glLoadIdentity() 

The first command in windowresize is glViewport . This command resets the current view.

The glMatrixMode(GL_PROJECTION ) line then sets up the projection matrix, which is responsible for adding perspective. glMatrixMode is defined by the next two commands, in which the scene is set and the perspective is defined. The command that follows is glLoadIdentity() , which resets and restores the projection matrix to its original state.

Objects on the screen that are meant to be far away need to appear smaller in order to create realistic 3D, so the perspective is then defined with gluPerspective . In this example, the perspective is calculated by a 45-degree viewing angle based on 1 times (1.0*) windowsize 's height and width. 0.1 and 100.0 are the starting and ending points for how deep the screen can go, and how many layers the screen can have.

Finally, you use glLoadIdentity() a second time to turn attention to the projection matrix and reset it.

Initializing PyOpenGL

After defining a three-dimensional window, you can then create a function that initializes PyOpenGL. You need to establish what color the screen starts out as, the depth buffer, and whether to use smooth shading, as well as a number of other possible PyOpenGL features. Do this with an initialize command:

 def initialize():     glShadeModel(GL_SMOOTH)     glClearColor(0.0, 0.0, 0.0, 0.0)     glClearDepth(1.0)     glEnable(GL_DEPTH_TEST)     glDepthFunc(GL_LEQUAL)     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) 

In initialize , you use glShadeModel(GL_SMOOTH) first to ask PyOpenGL to use smooth shading (smooth shading is simply one way of blending colors and lighting when rendering a polygon). Next you use glClearColor , which sets the color of the window screen when it is clear.

PyOpenGL takes in four numbers when you declare a color. The first three represent the primary colors red, green, and blue, and the last is the alpha (transparency channel). Each number can range from 0.0 to 1.0; the lower the number, the darker the intensity, the higher the number, the brighter the intensity. The numbers must be in order of Red, Green, Blue, and Alpha. You can create different colors by mixing these primary colors. Black would be (0,0,0,0), white would be (1,1,1,0), and yellow would be (1,1,0,0) . Of course, the last number is the alpha or transparency.

After setting the screen color you set up the depth buffer. The depth buffer keeps track of how many layers deep the screen goes, and you need to have depth in order to have any sort of 3D. The depth buffer actually keeps track of which objects are in front and which are in back, so it knows how to draw the screen in the proper perspective. There are three commands associated with the depth buffer in our initialize function: glClearDepth , glEnable , and glDepthFunc .

glClearDepth specifies the depth value used when the depth buffer is cleared. The glEnable command is used to enable various PyOpenGL capabilities. In this case, it is enabling depth testing, which will allow initialize to do depth comparisons and update the depth buffer. glDepthFunc specifies the function used to compare each incoming pixel depth value with the depth value present in the depth buffer. LEQUAL is short for Less than or Equal to, and sets glDepthFunc to pass the incoming depth value if it is less than or equal to the present value.

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST ) is a long command, but it's basically only a way of telling PyOpenGL to please use the best corrective perspective and the highest-quality view when there is room for interpretation.

Drawing a Square

Our third function is the code that actually draws the display, so let's call it drawgraphics() . This function will actually display everything that goes onto the screen, so it will be doing most of the work in each example.

 def drawgraphics():     glClear(GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT)     glLoadIdentity()     glTranslatef(0.0, 0.0, -5.0)     glBegin(GL_QUADS)     glVertex3f(-1.0, 1.0, 0)     glVertex3f(1.0, 1.0, 0)     glVertex3f(1.0, -1.0, 0)     glVertex3f(-1.0, -1.0, 0)     glEnd() 

First you glClear to clear the screen to a color, clear the buffer, and then reset with glLoadIdentity . glLoadIdentity actually moves you to the center of the screen, which is 0,0 on the x- and y-axis. Left and down are negative numbers, and right and up would be positive numbers; see Figure 4.15.

Figure 4.15. Three-dimensional space labeled by X,Y, and Z

graphic/04fig15.gif


The glTranslatef() command produces a translation of the current matrix by multiplying it by the x, y, and z coordinates given to it. This sounds confusing, but all it really does is change the drawing point from the current view to someplace else. In this case, you do not change the glTranslatef() x or y coordinates (leaving them at 0,0) but you do give a -5.0 for the z-axis, which basically pushes the matrix back five screen depths. If you didn't push the matrix back, what you drew would be too close to the front of the 3D space for you to see it. Basically, glTranslatef() is the command that moves along the x-, y-, and z-axes. For instance, glTranslatef(1.5, 0.0, and 6.0) would mean to move left 1.5 units and into the screen depth by 6 units.

NOTE

TIP

When you use glTranslatef() , you are not mov ing coordinated relative to the center of the screen, you are actually moving glTranslatef() relative to wherever it currently is. If you left glTranslatef() at the top right corner of the screen with the last command, that is where it will still be when you use it later. This means you need always keep track of its current position.

glBegin tells PyOpenGL that you want to start drawing, and ( GL_QUADS ) tells PyOpenGL that you want to draw a square or four-sided shape of some sort. You use glVertex() to tell PyOpenGL where the four points of your square shape are located on the x-, y-, and z-axes, and glEnd() means you are done drawing and that there are no more points. The first glVertex() number is is the first point of the square (and the x-axis, if you are drawing a polygon). The second number is the y-axis, and the third number is the z.

You have three usable functions; now you just have to set them up in a main loop.

 def main():     # Define any variables     video_flags = OPENGLDOUBLEBUF     # Initialize Pygame     pygame.init()     pygame.display.set_mode((640,480), video_flags)     # Call our windowsize and Initialize functions     windowsize((640,480))     initialize()     #set frames to 0 before loop starts     frames = 0     # Have pygame keep track of time     ticks = pygame.time.get_ticks()     # while loop that draws and looks to quit     while 1:         event = pygame.event.poll()         if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):             break           # Draw our fun graphics          drawgraphics()          pygame.display.flip()          frames = frames+1 if __name__ == '__main__': main() 

There is actually quite a bit going on here. First, you define video_flags to be OpenGL and double-buffered; these are calls you need to make to Pygame in order to render OpenGL correctly. Then you initialize Pygame with its init() method and set the display to 640x480 with your video flags.

NOTE

Double Buffering

Drawing and redrawing screens and images can be time- and processor-consuming, and game programmers have developed many tricks for increasing the speed it takes to render drawings. One of these tricks is called double buffering , and is very common when animating. Double buffering is so common, in fact, that most modern game and animation libraries have built-in support for flags for using the technique. Can you believe that programmers used to have to create their own buffers by hand? Talk about Dark Ages!

Normally, when an image is redrawn, it is simply redrawn in place on the screen. In double buffering, the image is redrawn ahead of time in a buffer or a hidden area of the screen or memory, and then, when it is time to re-display, the buffer is simply copied to the screen. In reality, a complex animation or sequence may have dozens of unseen layers constantly loading with the graphics that will display seconds later.

Then you call the windowsize function with the same display size (640x480) and the initialize function that initializes PyOpenGL. You set up a baseline frame variable (equaling 0) and then you ask Pygame to use pygame.get.ticks to keep track of time in milliseconds.

The actual work happens in the while loop. First, use Pygame's event.poll() function to see, via keyboard input and an if statement, whether the user wants to quit. Then call the draw graphics function, which draws the square.

pygame.display.flip() updates the display each time it is called. pygame.dipsplay knows that you are using OpenGL and double buffering because of your earlier video flags, so it updates the entire display by swapping the current view with the new ones it has drawn and stored in memory (this is called a gl buffer swap ). Then you update your frames so that you know how many times the while loop has looped, and finally you initiate main with a standard Python if line.

Whew! If you run OpenGL_1.py you'll see a white square open in a 640x480-pixel Pygame window, similar to that in Figure 4.16.

Figure 4.16. OpenGL_1.py displays a square rendered in PyOpenGL and displayed within a Pygame window

graphic/04fig16.gif


Setting the Color of an Object

Now that you have a baseline, let's look at what else you can do with PyOpenGL. Let's try giving the square a color. You can use the glColor3f() command, which also takes in three commands, one each for red, green, and blue intensity values: glColor3f(r, g, b,) . PyOpenGL keeps these standards consistent across commands, so the colors have a range from 0.0 to 1.0 and work exactly the same as if you were setting up the screen background color with glClearColor3f() .

Turning on glColor3f is like switching to a different-colored pen. When you switch to red, everything you draw after that point is red. Then, if you switch to another color, everything you draw after that is drawn in the new color. To make your square a Python green, you simply need to add the glColor3f command in your drawgraphics() function before you begin drawing with glBegin(GL_QUADS) , like so:

 def drawgraphics():     glClear(GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT)     glLoadIdentity()     glTranslatef(0.0, 0.0, -5.0)     #Adding color to our square     glColor3f(0.1, 0.9, 0.5)     glBegin(GL_QUADS)     glVertex3f(-1.0, 1.0, 0)     glVertex3f(1.0, 1.0, 0)     glVertex3f(1.0, -1.0, 0)     glVertex3f(-1.0, -1.0, 0)     glEnd() 

Now when you run this program (labeled OpenGL_2.py on the CD), you will see a green square just like that in Figure 4.17. Notice that the polygon fills in the entire surface with the colors you've drawn. This is called smooth coloring .

Figure 4.17. Coloring in a surface with glColor3f()

graphic/04fig17.gif


Rotation and Movement

Now that you can color the square, let's try to rotate it. To do so, you need to add a bit to the drawgraphics function. First, make use of the rquad ( rquad is short for rotate quad ) variable by declaring it a global and then calling the glRotatef() function. Use a variable for rotation so that you have fine-grain control over the movement.

glRotatef(angle, x, y, z) produces a rotation of a given angle in degrees over a given vertices given in x, y, and z coordinates. The command takes four arguments: Angle , X vecto r, Y vector , and Z vector . Angle is a number that represents how much to spin the object. The x, y, and z vectors represent the vector around which the rotation will occur. For instance, (1,0,0), describes a vector that travels in the direction of 1 unit along the x-axis.

The current matrix (remember it's all about glMatrixMode ) is changed by this rotation. Set up the rotation by adding one line that calls glRotate() on your square using the rquad variable as the angle and rotating on the x-axis:

 def drawgraphics():     glClear(GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT)     glLoadIdentity()     glTranslatef(0.0, 0.0, -5.0)     # Set up rquad for rotation, only real difference     global rquad     glRotatef(rquad, 1.0, 0.0, 0.0)     glColor3f(0.1, 0.9, 0.5)     glBegin(GL_QUADS)     glVertex3f(-1.0, 1.0, 0)     glVertex3f(1.0, 1.0, 0)     glVertex3f(1.0, -1.0, 0)     glVertex3f(-1.0, -1.0, 0)     glEnd() 

And then at the end of drawgraphics you update rquad so that the drawing of the square continually rotates:

 # And update rquad for movement     rquad+= 0.1 

This creates a rotating flat square, as illustrated in Figure 4.18 (the source is on the CD as OpenGL_3.py) .

Figure 4.18. A flat plane rotates along its x-axis

graphic/04fig18.gif


By playing with the rquad variable, you can change how many degrees the plane rotates on the x-axis. You can make the plane spin faster or slower, backwards or forwards, by changing the values associated with it.

Moving from Flat to 3D

You have already done most of the work for displaying three dimensions. Let's say you wanted to change your flat plane to a cube. GL_QUAD is actually capable of displaying a cube object; you just need to tell it where the other vertices for the other five flat planes should go. This becomes a pixel-plotting problem; it is shown in Figure 4.19.

Figure 4.19. A cube and points for each side are mapped out in 3D space

graphic/04fig19.gif


Once you know where each pixel belongs, you can feed the location to GL_QUAD , which fills in each surface for you:

 # Front Face     glVertex3f( 1.0, 1.0,-1.0)     glVertex3f(-1.0, 1.0,-1.0)     glVertex3f(-1.0, 1.0, 1.0)     glVertex3f( 1.0, 1.0, 1.0)     # Back Face     glVertex3f( 1.0,-1.0, 1.0)     glVertex3f(-1.0,-1.0, 1.0)     glVertex3f(-1.0,-1.0,-1.0)      glVertex3f( 1.0,-1.0,-1.0)     # Top Face     glVertex3f( 1.0, 1.0, 1.0)     glVertex3f(-1.0, 1.0, 1.0)     glVertex3f(-1.0,-1.0, 1.0)     glVertex3f( 1.0,-1.0, 1.0)     # Bottom Face     glVertex3f( 1.0,-1.0,-1.0)     glVertex3f(-1.0,-1.0,-1.0)     glVertex3f(-1.0, 1.0,-1.0)     glVertex3f( 1.0, 1.0,-1.0)     # Right face     glVertex3f(-1.0, 1.0, 1.0)     glVertex3f(-1.0, 1.0,-1.0)     glVertex3f(-1.0,-1.0,-1.0)     glVertex3f(-1.0,-1.0, 1.0)     # Left Face     glVertex3f( 1.0, 1.0,-1.0)     glVertex3f( 1.0, 1.0, 1.0)     glVertex3f( 1.0,-1.0, 1.0)     glVertex3f( 1.0,-1.0,-1.0) 

Now the cube has six sides. PyOpenGL automatically draws them in a counter-clockwise orderthe first point is top-right, the second point is bottom-right, and so on until completely around the given plane. The rotation is already built-in, and the MatrixMode automatically knows to update each side as it rotates; check out OpenGL_4.py on the CD and Figure 4.20.

Figure 4.20. The flat plane becomes a full rotating cube

graphic/04fig20.gif


Let's say you wanted to speed up and twist your rotating cube around a bit more. It's easy to fiddle with MatrixMode, especially since you've thought ahead and included a number of variables with which to do it:

 # Now we use all of these # x,y, and z rots are the rotations on each axis xrot = yrot = zrot = 0.0 

These variables, xrot , yrot , and zrot , can be used to rotate the cube in a new way on the x-, y-, and x-axes. Do so by adding a few lines to the top of drawgraphics :

 global xrot, yrot, zrot     glClear(GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BIT)     glLoadIdentity()     glTranslatef(0.0, 0.0, -5.0)     global rquad # not used for now     glRotatef(xrot,1.0,0.0,0.0)     glRotatef(yrot,0.0,1.0,0.0)     glRotatef(zrot,0.0,0.0,1.0) 

And then add a few lines to the end of drawgraphics :

 # Use XYZ to rotate - speed it up a bit     xrot = xrot + 0.9     yrot = yrot + 0.9     zrot = zrot + 0.9 

This will cause your cube to rotate quicker and also spin on aother axis.

Adding Textures

In your final PyOpenGL tutorial you'll open and use a local texture image instead of having PyOpenGL simply color the cube; this is illustrated in Figure 4.21. The full code is listed in OpenGL_5.py in the Chapter 4 code section on the CD.

Figure 4.21. A textured cube spins around each axis

graphic/04fig21.gif


First you will make use of import os . A texture will then have to be loaded from outside of Python, and your program will need to understand how to navigate through different directories and pull files from its native operating system.

You will also finally be using the texture variables you initialized early on:

 # textures for loading the .bmp image textures = [0,0] 

You will be using textures[] for loading the .bmp you will be using for texture. The first thing you need is a new function that opens up the .bmp file:

 # New function to find, load, and use the texture def loadtextures():     # Need to find and load the texture     point_to_file = os.path.join('dtcfe.bmp')     texture_surface = pygame.image.load(point_to_file)     texture_buffer = pygame.image.tostring(texture_surface, "RGBX", 1) 

First, point_to_file uses the os module's os.path.join to point to the .bmp you want to usein this case it is the dtcfe.bmp file found on the CD with the code samples. The next two commands use Pygame methods to load the .bmp image to a new surface (texture_surface ) and then copy the image into a larger string buffer (texture_buffer ). Specifying RGBX tells Pygame that the texture should be 32-bit padded RGB data. This turns the .bmp image into an actual texture.

With Pygame, your textures must be at least 64x64 pixels, and shouldn't be more than 256x256 pixels. Textures need to be sized in height and width to the power of 2 (if the textures are 64x64, 128x128, or 256x256, they do not need to be resized, otherwise they do). These are of course the standard defaults for textures and are changeable , but not without more advanced programming.

Now that Pygame has the texture, you hand it over to OpenGL. First you need to specify that the texture is two-dimensional with GL_TEXTURE_2D , and then you need to bind it to a texture[] array that will hold any and all textures your program needs:

 glBindTexture(GL_TEXTURE_2D, textures[0]) 

glTextImage2D is a PyOpenGL command that specifies a two-dimensional texture. You feed it several values, including the texture surface, width, and height (using the get_width() and get_height() methods). Then you specify that the texture is two-dimensional with GL_TEXTURE_2D , explain how the color format is organized with GL_RGBA , define the data format used to store the texture data with GL_UNSIGNED_BYTE , and finally, you give glTextImage2D() the actual data of the texture itself, texture_buffer , which you defined with Pygame:

 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texture_surface.get_width(), texture_sur- face.get_height(), 0,                    GL_RGBA, GL_UNSIGNED_BYTE, texture_buffer ); 

Whewthat's our longest one-liner yet. The last step in loading a texture is to tell PyOpenGL what filtering to use when the image is stretched or altered on the screen. To do so, use PyOpenGL's built-in glTexParameterf() , which simply defines the options to use when texture mapping. The MIN and MAG filters specify texture magnification, and GL_NEAREST asks PyOpenGL to grab the nearest pixel when redrawing the GL_TEXTURE_2D image:

 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) 

Now that you can load the .bmp image and turn it into a texture, you need to make PyOpenGL use the texture on each side of the cube instead of filling in the sides with glColor3f() .

Drawing a textured cube is quite a bit different from drawing colored cubes. Most of the gl functions are the same but the glBindTexture command we used to load textures sets the texture we want to use, much like glColor3f() set the pen to a specific color:

 glBindTexture(GL_TEXTURE_2D, textures[0]) 

To map the texture correctly into a specific side of the texture, you need to make sure the top-right of the texture is mapped to the top-right of the side; same with the bottom-left. Each corner needs to be mapped using the glTexCoord2f() , command like so:

 glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0) 

The glTexCoord2f command is designed to map out textures in two dimensions. Once you get the hang of using the command it is as easy to use as glColor , there is just an added complexity to each of the cube's mapped points:

 glBegin(GL_QUADS)     # Front Face     glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)     glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)     glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0,  1.0)     glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0,  1.0)     # Back Face     glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)     glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)     glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)     glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, -1.0)     # Top Face     glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)     glTexCoord2f(0.0, 0.0); glVertex3f(-1.0,  1.0,  1.0)     glTexCoord2f(1.0, 0.0); glVertex3f( 1.0,  1.0,  1.0)     glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)     # Bottom Face     glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0)     glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, -1.0, -1.0)     glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)     glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)     # Right face     glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, -1.0)     glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)     glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0,  1.0)     glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)      # Left Face     glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)     glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)     glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0,  1.0)      glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)     glEnd(); 

The result of this code (OpenGL_5.py) is illustrated in Figure 4.21.

[ LiB ]


Game Programming with Pyton, Lua and Ruby
Game Programming with Pyton, Lua and Ruby
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 133

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net