[ LiB ] | ![]() ![]() |
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 controls 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.
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.
Value | Image format |
---|---|
rgb | SGI ImgLib Files |
gif | GIF 87a and 89a Files |
pbm | Portable bit-map Files |
pgm | Portable Graymap Files |
ppm | Portable Pixmap Files |
tiff | TIFF Files |
rast | Sun Raster Files |
xbm | X bit-map Files |
jpeg | JPEG data in JFIF Format |
bmp | BMP Files |
png | Portable Network Graphics |
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.
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.
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).
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.
Property | Function |
---|---|
activebackground | Sets the background color |
activeforeground | Sets the foreground color |
bitmap | Displays a given bit-map 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.
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.
Property | Function |
---|---|
arc | Creates an arc or an arc item |
bitmap | Creates a bit-map 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 |
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.
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) |
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.
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.
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.
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.
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.
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:
Methods | Function |
---|---|
add_command() | Adds a menu item |
add_radiobutton() | Creates a radio button menu item |
add_checkbutton() | |
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.
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 is very similar to the Label widget, and is used to create a multiple line non -editable object that displays text.
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.
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.
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.
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 8-n-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.
Text allows the editing and formatting of multiple lines of text and has a number of available methods, as listed in Table 4.12.
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.
The Toplevel widgets are directly managed by the window manager; its methods are listed in Table 4.13.
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.
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 bit-map for when the window is iconified |
iconify() | Turns the window into an icon |
iconmask() | Defines an icon bit-map 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 |
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.
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.
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 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.
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.
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)
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.
Option | Use Example |
---|---|
Column | Specifies the column number |
Columnspan | To make a widget span multiple (default is 8-n-1 column) |
Row | Specifies the row number |
Rowspan | To make a widget span multiple rows (default is one row) |
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.
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 |
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.
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> | |
<ButtonRelease -1> | Mouse button is released |
<Double button - 1> | A double click |
<Enter> | Mouse pointer enters widget |
<Leave> | Mouse pointer leaves widget |
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.
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 8-n-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.
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 uses the image class as a foundation to display graphic objects. Graphic objects Tkinter can display include both bit-map (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 is used to display bit-map images on widgets. In Tkinter, however, a bit-map 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.
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 bit-map string |
These options have methods available to them, listed in Table 4.25.
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 is used for displaying full color images; it supports GIF and PPM files and has attributes as listed in Table 4.26.
Attribute | Holds |
---|---|
data | String to be used instead of a file |
file | File to be read |
height | Dimensions |
width | Dimensions |
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.
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
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!
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 8-n-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.
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 9-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 9-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.
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_BIT|GL_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.
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 moving 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 9-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 = OPENGL|DOUBLEBUF # 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 9-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 9-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 graphicsfunction, 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.
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_BIT|GL_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.
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 vector, 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 8-n-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_BIT|GL_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).
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.
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.
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.
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_BIT|GL_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.
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.
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 ] | ![]() ![]() |