Xlib tutorial part 15 -- Gradients

by Alan at Tue 7th Jul 2009 1:00AM EST

Hello, and welcome to part 14 of this Xlib tutorial.

This week we're going to look at making the buttons prettier by using gradients, similar to what you see in MicroSoft OutLook. You can get the code

Again we have to do some mathematics. The idea is to make the button look like it bulges out from the surrounding objects. If we imagine a cylinder, lying horizontal, half of it visible, sticking out with light shining from above we immediately realize the top half of the cylinder will be a lighter colour than the bottom half. On top of that, the colour will change from brightest according to a straight forward mathematical progression to darker and darker until it is at its darkest at the bottom.

This sounds somewhat like a quadratic equation, but I played with using a quadratic equation to create the colours and discovered that it did not look quite right. In the end, I decided for my work, I prefered something sinusoidal: cosine actually.

Here's the code to fill in a one pixel wide pixmap with a gradient colour:

    for(y = 0; y < height; y += 1){
        cosine = cos(l2rads * y)/2.0; /* mute it */
        col2.red = bcolour.red + diffcol.red * cosine;
        col2.green = bcolour.green + diffcol.green * cosine;
        col2.blue = bcolour.blue + diffcol.blue * cosine;
        XAllocColor(dpy, cmap, &col2);
        XSetForeground(dpy, gc, col2.pixel);
        for (x = 0; x < width; x += 1){
            XDrawPoint(dpy, pmap, gc, x, y);

This code doesn't work in isolation, but I thought we'd start from the meat and work out. The cosine = line calculates a simple gradient brightness. Then, for each of red, green and blue we add a brightness difference and then allocate the colour and set each pixel in the row that colour.

Here's the start of the function that it's part of.

static Pixmap createGradPixmap(Display *dpy, int width, int height,
            char *basecolour){
    int x = 0, y;
    double cosine, l2rads;
    XColor bcolour, col2, diffcol;
    Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    Pixmap pmap;
    GC gc = DefGC(dpy);
    Window win = DefaultRootWindow(dpy);

    /* assume width == 1 for this iteration */
    width = 1;

    l2rads = M_PIl/(height);

    XParseColor(dpy, cmap, basecolour, &bcolour);
    diffcol.red = min(bcolour.red, 0xffff-bcolour.red);
    diffcol.green = min(bcolour.green, 0xffff-bcolour.green);
    diffcol.blue = min(bcolour.blue, 0xffff-bcolour.blue);
    fprintf(stderr, "height %d, width %d, %s
", height, width, basecolour);

    pmap = XCreatePixmap(dpy, win, width, height,
        DefaultDepth(dpy, DefaultScreen(dpy)));

We are working in our default colourmap, any reasonably modern system should be able to handle enough colours. We set l2rads (length to radians) to a value such that we go through 180 degrees (1 pi radians) over the height of the button.

We then figure out which colour we're working with, and figure out the maximum difference in each of red, green and blue that we can go without going off the range. (X colours are all in the 0-0xffff(65535) range.)

and finally we create the pixmap we're going to use. The part of the function after the loop is a simple return pmap;

In the same file (gradient.c), I have a couple of functions for managing the pixmaps (functions that will actually be called by other parts of the program). It will cache them and free the pixmaps on the display server when the last user frees it.

To use these functions, in the button object, I've added a field bgpix which initialises to 0 and changed the background field to be a string so that we can pass the string in to the getGradPixmap() function in gradient.c

In our buttonExpose method, we now, rather than just redrawing, we set the check if the bgpix is set to something other than 0, and call getGradPixmap() and set bgpix and the background pixmap of the window to it. Then, to be sure the window gets drawn we call XClearArea with an exposure of True so that it will call the expose again. That second time, we just redraw the text.

buttonConfigureNotify() is very similar, except we have to free the pixmap if the button changed size. Other changes to the button you find in the new source, I hope, will be easily understood.

The changes in mainloop.c are merely to add more insteresting things to the menus, and the changes in menu.c include a bug fix and letting the menubar also have a gradient.

Things to try

Comments are closed.