Graphics in GNOME inhabit the full gamut of libraries, from native X to the GNOME libraries, following a steady progression from raw and hard to use, to abstract and easy to use. Xlib offers the lowest level through its drawing primitives. GDK in turn wraps Xlib, taking away a lot of the hassle behind color and pixel management, and adding some powerful rendering functions with the GdkRGB module. GNOME adds libart to the mix, a sophisticated image manipulation suite that GNOME uses to manipulate its own graphics extensions, such as the gdk-pixbuf image-loading library and the GNOME Canvas drawing surface (see Chapter 11). These layers are for the most part cumulative, so even the highest, most abstracted layers, like the GNOME Canvas, can still use libart, GDK, and Xlib directly and indirectly. However, it is good practice to restrict your API calls to adjacent layers when possible.
Dealing with graphics at the level of the X Window System can be a lot of work. You have to do most of the color and buffer management yourself. The low-level nature of the Xlib API forces you to know intimate details of how video cards react to the various color depths. You should never have to touch Xlib directly in a GNOME or GTK+ application; in this section we'll concentrate on the basic concepts of color and buffer management, leaving the C code for later sections.
A frame buffer is the lowest level of the X11 graphics system. It sits closest to the video hardware, holding the raw data that the video card renders onto the monitor screen. The video card's current color resolution determines the exact format of the data in the frame buffer. Each visible pixel on the screen takes up a varying number of consecutive bits in the frame buffer.
For example, a monochrome display with no variance in brightness would require only one on-off bit for each pixel, resulting in 8 screen pixels per byte of memory in the frame buffer. Conversely, a high-color display showing 16 million colors would require 24 bits to express the full range of color, leading to 3 bytes for each pixel in the frame buffer. Thus given a screen size of 640 480, the monochrome display would require a frame buffer of 38,400 bytes ([640 480 1 bit] 8), while the 16-million-color display would require 921,600 bytes ([640 480 24 bits] 8), or close to a full megabyte.
Another difference between monochrome and color monitors is the number of electron guns the monitor fires at the CRT screen. A monochrome monitor has a single gun, which means that the video card has to give it only a single value: the intensity of the single color. A color monitor is a little more complex; it uses three electron guns simultaneously-one for each of the colors red, green, and blue. Every color that shows up on the monitor screen is a combination of these three basic components. Each pixel on a color monitor requires three discrete values, one for each electron gun.
To get the most out of each user's graphics hardware, the X Window System must be able to handle these different situations gracefully. As we will see in the sections that follow, X defines several different display modes to cover these variations. Some display modes, or visuals (see Section 10.1.3), pass their color values directly to the frame buffer (and thus the electron guns); other visuals use indirect color lookup tables to reduce the number of simultaneous colors the frame buffer must juggle. In the next section we'll see how these color tables work.
One way that X can account for limited hardware is to restrict the number of colors an application is allowed to display. Even though the video card might be physically able to display all 16 million colors, it might not have enough on-board video RAM to store all the pixels for a given screen size. An older video card with only 1MB of VRAM would not have a frame buffer big enough to display an 800 600 screen in 16 million colors ([800 600 24 bits] 8 = 1,440,000 bytes, or 1.4MB).
However, this does not mean that you can't run 800 600 resolution with that video card. With a little clever color juggling, the video card can narrow the total number of colors it displays simultaneously, without restricting the total range of colors from which it can choose. The video card can choose, for example, 256 important colors out of the 16 million possible, and store them in a lookup table, or color map. Rather than storing the entire 24-bit values of each pixel directly in the frame buffer, it can instead store the 24-bit color values in the lookup table, and put only the 8-bit lookup indexes into the frame buffer (see Figure 10.1). Thus in this case the video card has access to the full range of 16 million colors-only 256 of which it can display at any given time-but it suffers a frame buffer size of only 480,000 bytes ([800 600 8 bits] 8).
Notice that with a color lookup table, the video card can change colors indirectly, by altering the values stored in the table. Changing an entry in the lookup table changes the color for all pixels in the frame buffer that point to that entry. This trick can be exploited for some interesting color-cycling effects, but in most cases it leads to annoying color corruption and flashing.
While all of this may seem rather low-level and immaterial to a book on application programming, the effects of these hardware properties filter up through the various layers of abstractions to the application level. You, as an application developer, need to know about color maps and visuals if you want to implement certain features with graphics buffers. Even if you don't use color maps directly, you need to know a thing or two about color handling.
To simplify and categorize the various color-mapping situations, X11 divides them into six major groups, called visuals. Each visual represents a different style for handling color maps. A visual is a conceptual abstraction that allows us to describe complex color-mapping arrangements in clear, simple language. In theory, each top-level window can have its own visual, which implies that each window can have a different color depth. In practice, however, only a few higher-end X servers support per-window color depths; the other X servers limit all visuals on a display to the same color depth.
The six common visuals in X are StaticGray, GrayScale, StaticColor, PseudoColor, TrueColor, and DirectColor. These visuals have many interesting relationships with each other (see Figure 10.2). Their two primary characteristics are color depth and read/write access. Visuals can use a monochrome color map (StaticGray and GrayScale), or use an RGB color map as we discussed in the previous section (StaticColor and PseudoColor), or write colors directly to the frame buffer for full-color displays (TrueColor and DirectColor).
The other main distinction among visuals is whether or not their color map entries can be changed by the application. A static color map is read-only. Since the color map will never change, it can be shared by multiple applications without the risk of one application secretly changing the color palette used by another application. This sharing also cuts down on the amount of memory required to store color maps. Conversely, applications can change dynamic (read/write) color maps at any time. If an application uses a dynamic color map, it will typically allocate its own private color map so that other applications can't reappropriate its colors. Half of the six visuals-one of each color depth-have static color maps (StaticGray, StaticColor, and TrueColor), and the other half have dynamic color maps (GrayScale, PseudoColor, and DirectColor).
Now that we've established a method for handling colors, we need to figure out how to show these colors on the screen. As we learned in Section 1.3.5, the X server keeps track of various server-side resources in a remote cache so that the client won't have to keep sending the same data over the wire. Thus the client can send a graphical image to the server one time, and the server will maintain its copy until the client explicitly requests its destruction. In the meantime, whenever the window is moved or covered up and reexposed, the X server can use its local resource copy to refresh the screen display rather than requesting a new copy of the image from the client each time.
The X Window System supports two types of these drawables: windows and pixmaps. A drawable is simply a server-side resource you can draw on with the various Xlib drawing commands. A window drawable is visible and makes up most of the on-screen real estate used by applications. The applica- tion can show or hide windows at any time. When you draw to a window, the effects of that operation show up immediately on the screen.
The other type of drawable is the pixmap. The pixmap is very similar to the window. You can invoke the same drawing commands on a pixmap that you can on a window. Unlike windows, however, pixmaps are never visible. Your drawing commands change the contents of the server-side pixmap re- source, but those contents are not rendered to the screen. To see them, you must copy them to a window.
One of the most common uses for pixmaps is double buffering, in which the application uses a pixmap as an intermediary drawing buffer to smooth out the drawing process (see Figure 10.3). A complex graphic might require a long series of drawing commands to complete. Each time the window is reexposed, the client normally has to reexecute the same drawing commands. If the client is displaying over a slow connection, or if the rendering process takes too long, the graphic flickers as the display updates in real time. On the other hand, if the client renders to an off-screen pixmap, it doesn't matter how long the process takes. The client can update the pixmap at its leisure, whenever the graphic changes. When the X server needs to refresh the on-screen image, the client can tell it to copy the graphic from the pixmap drawable to the window drawable. Since both of these resources reside on the X server, this will be a fast operation.
A specialized form of the pixmap is the bitmap, a 1-bit monochrome pixmap that is commonly used as a stencil or mask. Bitmaps are usually used as a filter for displaying other images and aren't themselves displayed. For example, you might use a bitmap to render an odd-shaped image, copying only the pixels turned on in the bitmap.
Although the pixmap drawable is a good optimization, it still requires that all your drawing commands go across the wire. You may be able to reduce the on-screen flicker, but if you have a lot of drawing commands, you'll still risk bogging down the connection. X provides another mechanism, called an image, to allow you to do all your rendering at the client side.
The drawing commands for an image are not nearly as sophisticated as those for a drawable. You are pretty much limited to manipulating the raw color and pixel data yourself, without the benefit of all the sophisticated line- drawing and image-loading functions that a pixmap drawable enjoys. You also have to know quite a bit about the details of the current visual, and the format in which it expects the image data to be. A different visual may use a different data format, so you may end up writing different code for each color depth.
If you're writing an application in which you need to create and manipulate a local copy of a graphical image, you're probably better off using a higher abstraction (see Section 10.3), and leaving the low-level operations to GDK. You should use an X11 image only if you really know what you're doing.