Skip to content

Using colors to paint the rainbow

Up until now, all our painting operation were done using black and white. We will (finally) see now how to draw using colors.

  1. Color maps

    In the beginning, there were not enough colors. Screen controllers could only support a limited number of colors simultaneously (initially 2, then 4, 16 and 256). Because of this, an application could not just ask to draw in a "light purple-red" color, and expect that color to be available. Each application allocated the colors it needed, and when all the color entries (4, 16, 256 colors) were in use, the next color allocation would fail.

    Thus, the notion of "a color map" was introduced. A color map is a table whose size is the same as the number of simultaneous colors a given screen controller. Each entry contained the RGB (Red, Green and Blue) values of a different color (all colors can be drawn using some combination of red, green and blue). When an application wants to draw on the screen, it does not specify which color to use. Rather, it specifies which color entry of some color map to be used during this drawing. Change the value in this color map entry and the drawing will use a different color.

    In order to be able to draw using colors that got something to do with what the programmer intended, color map allocation functions are supplied. You could ask to allocate entry for a color with a set of RGB values. If one already existed, you would get its index in the table. If none existed, and the table was not full, a new cell would be allocated to contain the given RGB values, and its index returned. If the table was full, the procedure would fail. You could then ask to get a color map entry with a color that is closest to the one you were asking for. This would mean that the actual drawing on the screen would be done using colors similar to what you wanted, but not the same.

    On today's more modern screens where one runs an X server with support for 16 million colors, this limitation looks a little silly, but remember that there are still older computers with older graphics cards out there. Using color map, support for these screen becomes transparent to you. On a display supporting 16 million colors, any color entry allocation request would succeed. On a display supporting a limited number of colors, some color allocation requests would return similar colors. It won't look as good, but your application would still work.

  2. Allocating and freeing Color Maps

    When you draw using XCB, you can choose to use the standard color map of the screen your window is displayed on, or you can allocate a new color map and apply it to a window. In the latter case, each time the mouse moves onto your window, the screen color map will be replaced by your window's color map, and you'll see all the other windows on screen change their colors into something quite bizzare. In fact, this is the effect you get with X applications that use the "-install" command line option.

    In XCB, a color map is (as often in X) an Id:

    typedef uint32_t xcb_colormap_t;
    typedef uint32_t xcb_colormap_t;

    In order to access the screen's default color map, you just have to retrieve the default_colormap field of the xcb_screen_t structure (see Section Checking basic information about a connection):

    #include <stdio.h>
    
    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t *c;
      xcb_screen_t     *screen;
      xcb_colormap_t    colormap;
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      colormap = screen->default_colormap;
    
      return 0;
    }
    #include <stdio.h>
    
    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t *c;
      xcb_screen_t     *screen;
      xcb_colormap_t    colormap;
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      colormap = screen->default_colormap;
    
      return 0;
    }

    This will return the color map used by default on the first screen (again, remember that an X server may support several different screens, each of which might have its own resources).

    The other option, that of allocating a new colormap, works as follows. We first ask the X server to give an Id to our color map, with this function:

    xcb_colormap_t xcb_generate_id (xcb_connection_t *c);
    xcb_colormap_t xcb_generate_id (xcb_connection_t *c);

    Then, we create the color map with

    xcb_void_cookie_t xcb_create_colormap (xcb_connection_t *c,       /* Pointer to the xcb_connection_t structure */
                                           uint8_t           alloc,   /* Colormap entries to be allocated (AllocNone or AllocAll) */
                                           xcb_colormap_t    mid,     /* Id of the color map */
                                           xcb_window_t      window,  /* Window on whose screen the colormap will be created */
                                           xcb_visualid_t    visual); /* Id of the visual supported by the screen */
    xcb_void_cookie_t xcb_create_colormap (xcb_connection_t *c,       /* Pointer to the xcb_connection_t structure */
                                           uint8_t           alloc,   /* Colormap entries to be allocated (AllocNone or AllocAll) */
                                           xcb_colormap_t    mid,     /* Id of the color map */
                                           xcb_window_t      window,  /* Window on whose screen the colormap will be created */
                                           xcb_visualid_t    visual); /* Id of the visual supported by the screen */

    Here is an example of creation of a new color map:

    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t *c;
      xcb_screen_t     *screen;
      xcb_window_t      win;
      xcb_colormap_t    cmap
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      /* We create the window win here*/
    
      cmap = xcb_generate_id (c);
      xcb_create_colormap (c, XCB_COLORMAP_ALLOC_NONE, cmap, win, screen->root_visual);
    
      return 0;
    }
    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t *c;
      xcb_screen_t     *screen;
      xcb_window_t      win;
      xcb_colormap_t    cmap
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      /* We create the window win here*/
    
      cmap = xcb_generate_id (c);
      xcb_create_colormap (c, XCB_COLORMAP_ALLOC_NONE, cmap, win, screen->root_visual);
    
      return 0;
    }

    Note that the window parameter is only used to allow the X server to create the color map for the given screen. We can then use this color map for any window drawn on the same screen.

    To free a color map, it suffices to use this function:

    xcb_void_cookie_t xcb_free_colormap (xcb_connection_t *c,   /* The connection */
                                         xcb_colormap_t cmap);  /* The color map */
    xcb_void_cookie_t xcb_free_colormap (xcb_connection_t *c,   /* The connection */
                                         xcb_colormap_t cmap);  /* The color map */
    Comparison Xlib/XCB
    • XCreateColormap ()
    • xcb_generate_id ()
    • xcb_create_colormap ()
    • XFreeColormap ()
    • xcb_free_colormap ()

  3. Allocating and freeing a color entry

    Once we got access to some color map, we can start allocating colors. The informations related to a color are stored in the following structure:

    typedef struct {
        uint8_t  response_type;
        uint8_t  pad0;
        uint16_t sequence;
        uint32_t length;
        uint16_t red;          /* The red component   */
        uint16_t green;        /* The green component */
        uint16_t blue;         /* The blue component  */
        uint8_t  pad1[2];
        uint32_t pixel;        /* The entry in the color map, supplied by the X server */
    } xcb_alloc_color_reply_t;
    typedef struct {
        uint8_t  response_type;
        uint8_t  pad0;
        uint16_t sequence;
        uint32_t length;
        uint16_t red;          /* The red component   */
        uint16_t green;        /* The green component */
        uint16_t blue;         /* The blue component  */
        uint8_t  pad1[2];
        uint32_t pixel;        /* The entry in the color map, supplied by the X server */
    } xcb_alloc_color_reply_t;

    XCB supplies these two functions to fill it:

    xcb_alloc_color_cookie_t xcb_alloc_color       (xcb_connection_t        *c,
                                                    xcb_colormap_t           cmap,
                                                    uint16_t                 red,
                                                    uint16_t                 green,
                                                    uint16_t                 blue);
    xcb_alloc_color_reply_t *xcb_alloc_color_reply (xcb_connection_t        *c,
                                                    xcb_alloc_color_cookie_t cookie,
                                                    xcb_generic_error_t    **e);
    xcb_alloc_color_cookie_t xcb_alloc_color       (xcb_connection_t        *c,
                                                    xcb_colormap_t           cmap,
                                                    uint16_t                 red,
                                                    uint16_t                 green,
                                                    uint16_t                 blue);
    xcb_alloc_color_reply_t *xcb_alloc_color_reply (xcb_connection_t        *c,
                                                    xcb_alloc_color_cookie_t cookie,
                                                    xcb_generic_error_t    **e);

    The fuction xcb_alloc_color() takes the 3 RGB components as parameters (red, green and blue). Here is an example of using these functions:

    #include <malloc.h>
    
    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t        *c;
      xcb_screen_t            *screen;
      xcb_window_t             win;
      xcb_colormap_t           cmap;
      xcb_alloc_color_reply_t *rep;
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      /* We create the window win here*/
    
      cmap = xcb_generate_id (c);
      xcb_create_colormap (c, XCB_COLORMAP_ALLOC_NONE, cmap, win, screen->root_visual);
    
      rep = xcb_alloc_color_reply (c, xcb_alloc_color (c, cmap, 65535, 0, 0), NULL);
    
      if (!rep)
        return 0;
    
      /* Do something with r->pixel or the components */
    
      free (rep);
    
      return 0;
    }
    #include <malloc.h>
    
    #include <xcb/xcb.h>
    
    int
    main ()
    {
      xcb_connection_t        *c;
      xcb_screen_t            *screen;
      xcb_window_t             win;
      xcb_colormap_t           cmap;
      xcb_alloc_color_reply_t *rep;
    
      /* Open the connection to the X server and get the first screen */
      c = xcb_connect (NULL, NULL);
      screen = xcb_setup_roots_iterator (xcb_get_setup (c)).data;
    
      /* We create the window win here*/
    
      cmap = xcb_generate_id (c);
      xcb_create_colormap (c, XCB_COLORMAP_ALLOC_NONE, cmap, win, screen->root_visual);
    
      rep = xcb_alloc_color_reply (c, xcb_alloc_color (c, cmap, 65535, 0, 0), NULL);
    
      if (!rep)
        return 0;
    
      /* Do something with r->pixel or the components */
    
      free (rep);
    
      return 0;
    }

    As xcb_alloc_color_reply() allocates memory, you have to free rep.

    TODO: Talk about freeing colors.