Beginner Questions About XCB - xcb_alloc_color is too slow

Chris Sorenson csoren at isd.net
Tue Feb 23 05:30:27 UTC 2021


Around 25 years ago, before the X Render Extension, Eric Foster-Johnson 
wrote a great article on the topic of using color in X, for the "Cross 
Thoughts" column in Unix Review Magazine. I've kept a copy ever since:

When the X Window system became popular, particularly on UNIX
workstations, you were lucky if you had a display that supported 256
colors. At the time, most X programmers used the simplest color
functions because most workstations didn't provide more than the
simplest color displays.

Cross Thoughts

In recent years that's all changed and we now expect even low-end
hardware to display thousands of colors. Even so, most X programs
still stick to old habits and don't take advantage of the greater
color capabilities that X provides.

This month we'll go over color programming, starting from setting up
colors in a colormap to using more-complicated X visuals.

X builds its color model on RGB components, the red, green, and blue
values used to define color on a typical monitor. Using the Xcms
routines, short for X color management system, you can define colors
in terms of CIE (a color model defined in 1931 that represents all
possible colors in a three-dimensional color space)1 or HSV (hue
saturation value) definitions, but the Xcms routines translate either
definition to the RGB values the X server requires.

In X, the RGB values go from 0 (all off) to 65535 (all on). Many
color definitions that you'll find, though, go from 0 to 255. It's
not that hard to convert with a macro such as the following:

#define Conv256To64K(v) \
((65535 * (long) v)/256)

Whatever method you use, if you start with RGB values, you need to
follow the X scale of 0 to 65535.

You can allocate color cells from a colormap using RGB values. Or,
you can look up colors by name from a color database. X provides a
database of more than 700 color names (most of them shades of gray,
though). Each color in the database is defined with RGB values
(interestingly, using a range of 0 to 255). You can look up common
colors by name with the XLookupColor function:


Status XLookupColor(
Display* display,
Colormap colormap,
char* colorName,
XColor* rgbColor, /* RETURN */
XColor* hardwareColor) /* RETURN */

On success, XLookupColor returns a nonzero value and fills in the two
XColor structures. XLookupColor fills up the rgbColor structure with
the red, green, and blue components as read in from the color
database. XLookupColor also fills the hardwareColor structure with
the closest match to the color definition that is supported on your
hardware. Because of this, I tend to use the hardwareColor structure
rather than the rgbColor structure.

In both cases, the XColor structure holds the following values:

typedef struct {
unsigned long pixel;
unsigned short red, green, blue;
char flags;
char pad;
} XColor;

XLookupColor fills in the red, green, and blue fields, along with the
flags, which indicate which of the fields were used by bitmasks:
DoRed, DoGreen, and DoBlue, respectively.

You can then allocate cells in a colormap from the color definitions
held in the XColor structures filled in by XLookupColor. X calls
these color cells pixels, which aren't dots on the screen, but values
that represent a position in a colormap that holds a given color. In
simple colormaps, the pixel is merely an index into an array. This is
not always the case though, as we'll discuss later.

To allocate a color cell, call XAllocColor:

Status XAllocColor(
Display* display,
Colormap colormap,
XColor* hardwareColor) /* in/out */

The XColor pointer is both an input and output parameter. On input,
XAllocColor reads the RGB values and flags fields in the structure.
On completion, XAllocColor fills in the pixel field with the pixel
value for that color in the passed-in colormap.

Before allocating a new color cell, XAllocColor checks if a color
cell already holds the given RGB values in the colormap you pass in.
This lets applications share common color cells, say, for red or
gray. If you pass in the default colormap, chances are most common
colors are already allocated.

On success, you can use the pixel field for drawing. With the X
library, you can call XSetForeground to store the color into a
graphic context as the drawing color:

int XSetForeground(
Display * display,
GC gc,
unsigned long pixel)

With a Motif application, you can use the pixel value to set the
foreground or background resources for a widget. For example:

XtVaSetValues(widget,
XmNforeground,
hardwareColor.pixel,
NULL);

You can also combine the tasks of looking up a color from a name and
allocating a cell with XAllocNamedColor.

Both XAllocColor and XAllocNamedColor allocate read-only color cells.
That is, once set, you cannot change the definition of the color in
that cell. Because of this, multiple applications, using the same
colormap, can share the same colors.

It's slightly more difficult to allocate cells that you can change
later, called read-write color cells. To do this, call
XAllocColorCells or XAllocColorPlanes. Allocating color planes lets
you do fun things like use color planes for double-buffering. A later
column will take that on. For now, XAllocColorCells takes the
following parameters:

Status XAllocColorCells(
Display* display,
Colormap colormap,
Bool contiguous,
unsigned long* planeMasks,
unsigned int numberPlanes,
unsigned long* pixels,
unsigned int numberPixels)

With XAllocColorCells, you can allocate both color planes and
individual cells. If you set the contiguous flag to True, you can
insist that the color planes allocated must be contiguous. This is
not always possible if a number of colors have already been allocated
from the colormap.

If you're just interested in read-write color cells, pass False for
the contiguous value, pass NULL for the planeMasks and zero for the
numberPlanes. Pass the number of pixels-color cells-you want to
allocate in the numberPixels parameter, and an array of unsigned long
values long enough to hold the returned pixel IDs in the pixels
parameter. For example:

unsigned long pixels[12];

status = XAllocColorCells( display, colormap, False, (unsigned long*)
                            NULL, 0, pixels, 12);

if (status != 0) { /* We're OK to use the colors in the pixels
                    array...*/ }

This call defines a 12-element array and asks for 12 color cells.

Allocating read-write color cells is just the first step. X gives you
these color cells without defining any colors in the cells. The next
step is to do this by calling XStoreColor:

XStoreColor(
Display* display,
Colormap colormap,
XColor* colorDefinition)

With XStoreColor, you must not only fill in the RGB values in the
XColor structure, but the pixel field as well. Set the pixel field to
one of the pixels you got back from the call to XAllocColorCells.
Also remember to set the flags field, typically to the
DoRed | DoGreen | DoBlue, the OR of these bitmasks.

Once you store in colors, you can then use these color cells, the
pixel values, with normal X drawing or widget color values. You can
call XStoreColor, or its brethren XStoreColors or XStoreNamedColor,
to then change the definition of a color cell on the fly. This is
useful for animated effects or letting a user modify the interface.

Colormaps

So far, all of these calls require a colormap. You allocate colors in
a colormap. You can create multiple colormaps for different tasks and
your system hardware may be able to display one or more colormaps at
the same time.

If you use more colormaps than your hardware can support
simultaneously, you'll see color flashing as you select windows.
Parts of your display may also go black as other color maps are
swapped in.

To avoid color flashing, and to conserve what used to be precious
color cells, most X applications use the default colormap. Using the
default colormap makes your code simpler (you often don't have to do
special coding to use this colormap) and helps enforce a unity of
colors on your display. You can get the default colormap by calling
the DefaultColormap macro:

Colormap DefaultColormap(
Display* display,
int screenNumber)

You can create your own colormaps, but here things start to become
more complex because you have to deal with visuals.

Visuals and Colormaps

X abstracts the differences between color hardware through the
concept of a visual. X provides six visual abstractions:

     PseudoColor visuals provide read-write color arrays. The pixel
     value provides an index into the array and the contents of the
     array hold the actual color value (the RGB data). You can change
     cells in a colormap created under a PseudoColor visual.

     GrayScale visuals are a lot like PseudoColor visuals, except you
     can only have shades of gray. With such a visual, you must set
     color cells to hold equal red, green, and blue values.

     StaticColor visuals are a lot like PseudoColor visuals, but the
     RGB values in the colormaps are predefined. These colormaps are
     read-only.

     StaticGray visuals are like StaticColor visuals, but allow for
     only predefined shades of gray. A black and white display has a
     very simple StaticGray visual.

     DirectColor visuals decompose the pixel values into separate
     fields for red, green, and blue. Each separate field then
     provides an index into a separate red, green, or blue colormap.

     TrueColor visuals also decompose the pixel values like
     DirectColor visuals. TrueColor visuals, though, have read-only
     RGB values. Typically, these RGB values provide a near linear
     ramp of colors.

Most of the differences of the visual abstractions relate to the type
of colormaps you can create for a given class of visual, such as
PseudoColor or DirectColor.

To create a colormap, call XCreateColormap:

Colormap XCreateColormap(
Display* display,
Window window,
Visual* visual,
int allocateFlag)

When creating a colormap, you can pre-allocate all the cells in the
colormap by passing the flag AllocAll as the allocateFlag parameter.
Pass AllocNone to request that no cells be allocated for your
application up front. Note that for visual types that only provide
read-only colormaps, you must pass AllocNone. These visuals are the
StaticColor, StaticGray, and TrueColor visual types. It's up to you
to remember which visuals lead to which types of colormaps.

Working with Multiple Visuals

Like the default colormap, there's also a default visual. Typically,
this is a PseudoColor visual, since most X applications are coded to
assume a PseudoColor visual. To get the default visual, call the
DefaultVisual macro:

Visual* DefaultVisual(
Display* display,
int screenNumber)

To find the visuals available on your display, you can call
XMatchVisualInfo to find a visual that matches a specific type of
visual, such as DirectColor, and a preferred depth (number of color
planes) that you pass in. If you are not sure which type of visual
you want to use or its depth (perhaps you want to find the visual
with the greatest depth, for example), then you can call
XGetVisualInfo to get a list of visuals to choose from. Both of these
functions provide XVisualInfo structures, from which you can get the
Visual pointer you need for the X routines.

The tricky part is that when you create a window, it must be
associated with a visual. You can then associate any number of
colormaps with a window, but each of these colormaps must have been
created for the visual-the same visual the window is associated with.
If you don't follow these rules, the X server will generate BadMatch
errors.

Because of this, it's generally a good idea to use the same visual
across all the windows your application creates. Now, this becomes
more complicated when you work with X toolkits, such as Motif. Motif
tries to hide the creation of actual windows. In most cases, each
Motif widget creates a window on the display (windows in X are
relatively cheap resources). Motif (actually the underlying X Toolkit
Intrinsics library) creates these windows when you call
XtRealizeWidget, which is often the very last call made before
jumping into the event loop.

Furthermore, all Motif widgets have foreground, background, and
borderColor resources, all of which refer to color cells. These color
cells must be valid cells for the colormap associated with the
window, which in turn must be created under the visual associated
with the window.

In practical terms, you should set the following resources for every
top-level shell widget (widgets beneath the top-level shell can
inherit the proper values):

     visual, which holds the visual you want to use for the window

     colormap, which must be a colormap created under the visual

     depth, which should match the depth (number of color planes) of
     the colormap, and be a valid depth supported by the visual

     background or backgroundPixmap, which must be a valid color cell
     in the colormap associated with the window or a valid pixmap that
     has a depth that matches the depth of the colormap

     borderColor or borderPixmap, which follow the same rules as
     listed previously

     foreground, which must be a valid color cell as described
     previously

You need to set these resources on each top-level shell widget,
before calling XtRealizeWidget. Remember that menus and dialogs are
also top-level shell widgets. I've found these guidelines helpful:

     Try to use the same visual throughout your application. This
     stopss a whole series of BadMatch errors where the visuals,
     colormaps, and color cells don't match up.

     Where possible, use the same colormap throughout the application.
     High-end imaging applications won't be able to do this, of
     course. If you cannot use the same colormap, then try to use
     different colormaps only for special image-processing or other
     high-color-usage windows. For the rest of the application, use
     the same colormap. This often works well with a Motif drawing
     area widget for displaying an image or other color-intensive
     rendering.

     Always make sure all color cells used in a window come from a
     colormap associated with that window.

With the rules associated with visuals, colormaps, and color cells,
making use of X color capabilities can seem a daunting task, but if
you start small and gradually build up your application, it will turn
out fine.

References

1. From TechWeb's TechEncyclopedia
    (http://www.techweb.com/encyclopedia/),
    a service of CMPnet and The Computer Language Co. Inc.

Eric Foster-Johnson is the co-author of Power Programming Motif (with
Kevin Reichard), and author of UNIX Programming Tools
(MIS: Press/M&T Books).

https://twitter.com/ericfosterjohns



More information about the xorg mailing list