9.6. Pixmaps

Pixmaps are data structures that contain pictures. These pictures can be used in various places, but most commonly as icons on the X desktop, or as cursors.

A pixmap which only has 2 colors is called a bitmap, and there are a few additional routines for handling this common special case.

To understand pixmaps, it would help to understand how X window system works. Under X, applications do not need to be running on the same computer that is interacting with the user. Instead, the various applications, called "clients", all communicate with a program which displays the graphics and handles the keyboard and mouse. This program which interacts directly with the user is called a "display server" or "X server." Since the communication might take place over a network, it's important to keep some information with the X server. Pixmaps, for example, are stored in the memory of the X server. This means that once pixmap values are set, they don't need to keep getting transmitted over the network; instead a command is sent to "display pixmap number XYZ here." Even if you aren't using X with GTK currently, using constructs such as Pixmaps will make your programs work acceptably under X.

To use pixmaps in PyGTK, we must first build a GdkPixmap using GDK functions in PyGTK. Pixmaps can either be created from in-memory data, or from data read from a file. We'll go through each of the calls to create a pixmap.
 
bitmap = create_bitmap_from_data(gdkwindow, data, width, height)

This routine is used to create a single-plane pixmap (2 colors) from data in memory. Each bit of the data represents whether that pixel is off or on. Width and height are in pixels. The gdkwindow argument must refer to a realized GdkWindow, since a pixmap's resources are meaningful only in the context of the screen where it is to be displayed.
 
pixmap, mask = create_pixmap_from_xpm(gdkwindow, transparent_color, filename)
pixmap, mask = create_pixmap_from_xpm(gtkwidget, transparent_color, filename)
pixmap, mask = create_pixmap_from_xpm(colormap, transparent_color, filename)

XPM format is a readable pixmap representation for the X Window System. It is widely used and many different utilities are available for creating image files in this format. The create_pixmap_from_xpm() function can be called in PyGTK with three sets of argument types as shown above. In the first case the first argument is a GdkWindow type. (Most GTK widgets have an underlying GdkWindow which can be retrieved by using the widget's get_window() method.) In the second case, the first argument is a GtkWidget which is used by PyGTK to determine the colormap to be used to create the pixmap.  In the third case the first argument is a GdkColormap type. (Most GTK widgets have an underlying GdkWindow which will also have a GdkColormap that can be retrieved using the widget's get_colormap() method.) The file specified by filename must contain an image in that format and it is loaded into the pixmap structure. The mask specifies which bits of the pixmap are opaque; it is created by the function. All other bits are colored using the color specified by transparent_color. An example using this follows below.
 
pixmap, mask = create_pixmap_from_xpm_d(gdkwindow, transparent_color, data)
pixmap, mask = create_pixmap_from_xpm_d(gtkwidget, transparent_color, data)
pixmap, mask = create_pixmap_from_xpm_d(colormap, transparent_color, data)

Small images can be incorporated into a program as data in the XPM format. A pixmap is created using this data, instead of reading it from a file. Like the create_pixmap_from_xpm()function there are three ways of creating a pixmap from XPM data. An example of such data is:
 
xpm_data = [
"16 16 3 1",
"       c None",
".      c #000000000000",
"X      c #FFFFFFFFFFFF",
"                ",
"   ......       ",
"   .XXX.X.      ",
"   .XXX.XX.     ",
"   .XXX.XXX.    ",
"   .XXX.....    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .XXXXXXX.    ",
"   .........    ",
"                ",
"                "
]

The final way to create a blank pixmap suitable for drawing operations is:
 
pixmap = create_pixmap(gtkwidget, width, height, depth)

gtkwidget is either a GtkWidget that can have an underlying GdkWindow. or None. If gtkwidget is a GtkWidget then depth can be -1 to indicate that the depth should be determined from the gtkwindow. If gtkwidget is None then the depth must be specified.

Once we've created a pixmap, we can display it as a GTK widget. We must create a GTK pixmap widget to contain the GDK pixmap. This is done using:
 
gtkpixmap = GtkPixmap(gdkpixmap, mask )

The other pixmap widget methods are
 
pixmap.get_type()

pixmap.set(val, mask)

pixamp, mask = pixmap.get()

The GtkPixmap set() method is used to change the pixmap that the widget is currently managing. val is a pixmap created by one of the above pixmap creation functions.

To make life a little easier a GtkPixmap can be created directly from XPM using GtkPixmap() with the same arguments as used for create_pixmap_from_xpm():
 
gtkpixmap = GtkPixmap(gdkwindow, filename, transparent_color)
gtkpixmap = GtkPixmap(gtkwidget, filename, transparent_color)
gtkpixmap = GtkPixmap(colormap, filename, transparent_color)

Note: the filename and transparent_color arguments are reversed from the create_pixmap_from_xpm() function. With these convenience calls, GtkPixmap() arranges to create the pixmap and mask and then create the GtkPixmap all in one call.

The pixmap.py program is an example of using a pixmap in a button. Figure 9.5 shows the result:

Figure 9.5 Pixmap in a Button Example

The source code is:
 
    1   #!/usr/bin/env python
    2   
    3   # example pixmap.py
    4   
    5   import gtk
    6   
    7   # XPM data of Open-File icon
    8   xpm_data = [
    9   "16 16 3 1",
   10   "       c None",
   11   ".      c #000000000000",
   12   "X      c #FFFFFFFFFFFF",
   13   "                ",
   14   "   ......       ",
   15   "   .XXX.X.      ",
   16   "   .XXX.XX.     ",
   17   "   .XXX.XXX.    ",
   18   "   .XXX.....    ",
   19   "   .XXXXXXX.    ",
   20   "   .XXXXXXX.    ",
   21   "   .XXXXXXX.    ",
   22   "   .XXXXXXX.    ",
   23   "   .XXXXXXX.    ",
   24   "   .XXXXXXX.    ",
   25   "   .XXXXXXX.    ",
   26   "   .........    ",
   27   "                ",
   28   "                "
   29   ]
   30   
   31   class PixmapExample:
   32       # when invoked (via signal delete_event), terminates the application.
   33       def close_application(self, widget, event, data=None):
   34           gtk.mainquit()
   35           return gtk.FALSE
   36   
   37       # is invoked when the button is clicked.  It just prints a message.
   38       def button_clicked(self, widget, data=None):
   39           print "button clicked"
   40   
   41       def __init__(self):
   42           # create the main window, and attach delete_event signal to terminating
   43           # the application
   44           window = gtk.GtkWindow(gtk.WINDOW_TOPLEVEL)
   45           window.connect("delete_event", self.close_application)
   46           window.set_border_width(10)
   47           window.show()
   48   
   49           # now for the pixmap from gdk
   50           style = window.get_style()
   51           pixmap, mask = gtk.create_pixmap_from_xpm_d(window.get_window(),   52                                                       style.bg[gtk.STATE_NORMAL],
   53                                                       xpm_data )
   54   
   55           # a pixmap widget to contain the pixmap
   56           pixmapwid = gtk.GtkPixmap(pixmap, mask)
   57           pixmapwid.show()
   58   
   59           # a button to contain the pixmap widget
   60           button = gtk.GtkButton()
   61           button.add(pixmapwid)
   62           window.add(button)
   63           button.show()
   64   
   65           button.connect("clicked", self.button_clicked)
   66   
   67   def main():
   68       gtk.mainloop()
   69       return 0
   70   
   71   if __name__ == "__main__":
   72       PixmapExample()
   73       main()

To load a file from an XPM data file called icon0.xpm in the current directory, we would have created the pixmap thus
 
    # load a pixmap from a file
    pixmap, mask = create_pixmap_from_xpm(window.get_window(),
                                          style.bg[STATE_NORMAL],
                                         "./icon0.xpm")
    pixmapwid = GtkPixmap(pixmap, mask)
    pixmapwid.show()
    window.add(pixmapwid)

A disadvantage of using pixmaps is that the displayed object is always rectangular, regardless of the image. We would like to create desktops and applications with icons that have more natural shapes. For example, for a game interface, we would like to have round buttons to push. The way to do this is using shaped windows.

A shaped window is simply a pixmap where the background pixels are transparent. This way, when the background image is multi-colored, we don't overwrite it with a rectangular, non-matching border around our icon. The wheelbarrow.py example program displays a full wheelbarrow image on the desktop. Figure 9.6 shows the wheelbarrow over a terminal window:

Figure 9.6 Wheelbarrow Example Shaped Window

The source code for wheelbarrow.py is:
 
    1   #!/usr/bin/env python
    2   
    3   # example-start wheelbarrow wheelbarrow.c
    4   
    5   import gtk
    6   import GDK
    7   
    8   # XPM
    9   WheelbarrowFull_xpm = [
   10   "48 48 64 1",
   11   "       c None",
   12   ".      c #DF7DCF3CC71B",
   13   "X      c #965875D669A6",
   14   "o      c #71C671C671C6",
   15   "O      c #A699A289A699",
   16   "+      c #965892489658",
   17   "@      c #8E38410330C2",
   18   "#      c #D75C7DF769A6",
   19   "$      c #F7DECF3CC71B",
   20   "%      c #96588A288E38",
   21   "&      c #A69992489E79",
   22   "*      c #8E3886178E38",
   23   "=      c #104008200820",
   24   "-      c #596510401040",
   25   ";      c #C71B30C230C2",
   26   ":      c #C71B9A699658",
   27   ">      c #618561856185",
   28   ",      c #20811C712081",
   29   "<      c #104000000000",
   30   "1      c #861720812081",
   31   "2      c #DF7D4D344103",
   32   "3      c #79E769A671C6",
   33   "4      c #861782078617",
   34   "5      c #41033CF34103",
   35   "6      c #000000000000",
   36   "7      c #49241C711040",
   37   "8      c #492445144924",
   38   "9      c #082008200820",
   39   "0      c #69A618611861",
   40   "q      c #B6DA71C65144",
   41   "w      c #410330C238E3",
   42   "e      c #CF3CBAEAB6DA",
   43   "r      c #71C6451430C2",
   44   "t      c #EFBEDB6CD75C",
   45   "y      c #28A208200820",
   46   "u      c #186110401040",
   47   "i      c #596528A21861",
   48   "p      c #71C661855965",
   49   "a      c #A69996589658",
   50   "s      c #30C228A230C2",
   51   "d      c #BEFBA289AEBA",
   52   "f      c #596545145144",
   53   "g      c #30C230C230C2",
   54   "h      c #8E3882078617",
   55   "j      c #208118612081",
   56   "k      c #38E30C300820",
   57   "l      c #30C2208128A2",
   58   "z      c #38E328A238E3",
   59   "x      c #514438E34924",
   60   "c      c #618555555965",
   61   "v      c #30C2208130C2",
   62   "b      c #38E328A230C2",
   63   "n      c #28A228A228A2",
   64   "m      c #41032CB228A2",
   65   "M      c #104010401040",
   66   "N      c #492438E34103",
   67   "B      c #28A2208128A2",
   68   "V      c #A699596538E3",
   69   "C      c #30C21C711040",
   70   "Z      c #30C218611040",
   71   "A      c #965865955965",
   72   "S      c #618534D32081",
   73   "D      c #38E31C711040",
   74   "F      c #082000000820",
   75   "                                                ",
   76   "          .XoO                                  ",
   77   "         +@#$%o&                                ",
   78   "         *=-;#::o+                              ",
   79   "           >,<12#:34                            ",
   80   "             45671#:X3                          ",
   81   "               +89<02qwo                        ",
   82   "e*                >,67;ro                       ",
   83   "ty>                 459@>+&&                    ",
   84   "$2u+                  ><ipas8*                  ",
   85   "%$;=*                *3:.Xa.dfg>                ",
   86   "Oh$;ya             *3d.a8j,Xe.d3g8+             ",
   87   " Oh$;ka          *3d$a8lz,,xxc:.e3g54           ",
   88   "  Oh$;kO       *pd$%svbzz,sxxxxfX..&wn>         ",
   89   "   Oh$@mO    *3dthwlsslszjzxxxxxxx3:td8M4       ",
   90   "    Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B*     ",
   91   "     Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5&   ",
   92   "      Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM*  ",
   93   "       OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
   94   "        2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
   95   "        :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
   96   "         +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
   97   "          *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en",
   98   "           p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
   99   "           OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
  100   "            3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ",
  101   "             @26MvzxNzvlbwfpdettttttttttt.c,n&  ",
  102   "             *;16=lsNwwNwgsvslbwwvccc3pcfu<o    ",
  103   "              p;<69BvwwsszslllbBlllllllu<5+     ",
  104   "              OS0y6FBlvvvzvzss,u=Blllj=54       ",
  105   "               c1-699Blvlllllu7k96MMMg4         ",
  106   "               *10y8n6FjvllllB<166668           ",
  107   "                S-kg+>666<M<996-y6n<8*          ",
  108   "                p71=4 m69996kD8Z-66698&&        ",
  109   "                &i0ycm6n4 ogk17,0<6666g         ",
  110   "                 N-k-<>     >=01-kuu666>        ",
  111   "                 ,6ky&      &46-10ul,66,        ",
  112   "                 Ou0<>       o66y<ulw<66&       ",
  113   "                  *kk5       >66By7=xu664       ",
  114   "                   <<M4      466lj<Mxu66o       ",
  115   "                   *>>       +66uv,zN666*       ",
  116   "                              566,xxj669        ",
  117   "                              4666FF666>        ",
  118   "                               >966666M         ",
  119   "                                oM6668+         ",
  120   "                                  *4            ",
  121   "                                                ",
  122   "                                                "
  123   ]
  124   
  125   class WheelbarrowExample:
  126       # When invoked (via signal delete_event), terminates the application  127       def close_application(self, widget, event, data=None):
  128           gtk.mainquit()
  129           return gtk.FALSE
  130   
  131       def __init__(self):
  132           # Create the main window, and attach delete_event signal to terminate
  133           # the application.  Note that the main window will not have a titlebar
  134           # since we're making it a popup.
  135           window = gtk.GtkWindow(gtk.WINDOW_POPUP)
  136           window.connect("delete_event", self.close_application)
  137           window.set_events(window.get_events() | GDK.BUTTON_PRESS_MASK)
  138           window.connect("button_press_event", self.close_application)
  139           window.show()
  140   
  141           # Now for the pixmap and the pixmap widget
  142           style = window.get_style()
  143           gc = style.black_gc
  144           gdk_pixmap, mask = gtk.create_pixmap_from_xpm_d(
  145               window.get_window(), style.bg[gtk.STATE_NORMAL],
  146               WheelbarrowFull_xpm)
  147           pixmap = gtk.GtkPixmap(gdk_pixmap, mask)
  148           pixmap.show()
  149   
  150           # To display the pixmap, we use a fixed widget to place the pixmap
  151           fixed = gtk.GtkFixed()
  152           fixed.set_usize(200, 200)
  153           fixed.put(pixmap, 0, 0)
  154           window.add(fixed)
  155           fixed.show()
  156   
  157           # This masks out everything except for the image itself
  158           window.shape_combine_mask(mask, 0, 0)
  159       
  160           # show the window
  161           window.set_uposition(100, 200)
  162           window.show()
  163   
  164   def main():
  165       gtk.mainloop()
  166       return 0
  167   
  168   if __name__ == "__main__":
  169       WheelbarrowExample()
  170       main()

To make the wheelbarrow image sensitive, we attached the button_press_event signal to make the program exit. Lines 137-138 make the picture sensitive to a mouse button being pressed and connect the close_application() method.