Create a rainbow with GD

URL: http://www.poirrier.be/~jean-etienne/info/clibs/gd-rainbow.php

Article written on February 28th, 2006.
Last revision on March 1st, 2006 (added the 2 last rainbows).

The goal of this small page is to draw a rainbow or, if you prefer, a HSV scale like this one (from a Gnuplot demo):

HSV scale

There is an archive containing the source code: gd-archive.tar.gz (2ko).

GD

From its website (http://www.boutell.com/gd/), GD is "an open source code library for the dynamic creation of images by programmers". It is well known in the PHP world because it quickly allows the creation of images with the PHP language (often used in webpages). For an example of use of PHP and GD, you can have a look at my virtual gel creator.

In the GD reference manual, they give a nice and well-explained example of how to use GD with C (see gdfirstexample.c in the archive). You must understand this example in order to continue.

Drawing a gradient

Drawing a gradient is very easy. One only has to allocate 256 increasing values to any of the three primary colors (or to all three colors). For example, if we need a black to white gradient, here is an excerpt of the code (see gradient.c in the archive).

int i;

gdImagePtr im;
FILE *pngout;

/* Declare color table (contains nearly all the colors) */

/* Each color (R/V/B) has a range from 0 to 255
	from 0 to 254 by steps of 2 (0, 2, 4, 6, ...) --> 127 possibilities
	127 (R) * 127 (G) * 127 (B) = 2 048 383 colors (a lot !) */

int mycolors[256];

/* Allocate colors ; since the first color in a new image is the
	background, we'll begin by nearly white (255, 255, 255) and we go to black */
for(i = 255; i >= 0; i--)
	mycolors[i] = gdImageColorAllocate(im, i, i, i);

/* Draw a vertical line for each color */
for(i = 0; i <= 255; i++)
	gdImageLine(im, i, 0, i, 99, mycolors[i]);

This code will give this PNG file as a result:

Gradient from black to white with GD

If you want a gradient from another primary color to black, you don't need to specify three "i" at the end of this line: mycolors[i] = gdImageColorAllocate(im, i, i, i);. If you specify gdImageColorAllocate(im, i, 0, 0), you'll have a gradient for red ; type gdImageColorAllocate(im, 0, i, 0) and you'll get a gradient for green ; use gdImageColorAllocate(im, 0, 0, i) and you'll obtain a gradient for blue. You can see results below.

Gradient for red with GD

Gradient for green with GD

Gradient for blue with GD

A strange rainbow

These gradients are beautiful but they are not the rainbow we want. How to draw this rainbow? Once more, we'll take a picture from another website (http://addictedtor.free.fr/graphiques/RGraphGallery.php?graph=108 in this case) to illustrate how we'll do it. As you can see in the figure below, the HSV scale is just the six lines from this cube in a certain order:

  1. red = 255 ; green = 0 -> 255 ; blue = 0
  2. red = 255 -> 0 ; green = 255 ; blue = 0
  3. red = 0 ; green = 255 ; blue = 0 -> 255
  4. red = 0 ; green = 255 -> 0 ; blue = 255
  5. red = 0 -> 255 ; green = 0 ; blue = 255
  6. red = 255 ; green = 0 ; blue = 255 -> 0

Rainbow in the RGB space

Now, we'll have to draw 6 times 256 values = 1536 values. So the color table will contain 1536 cells and our image will have a longer length (than the previous one). If we scrupulously apply the above list to our code, we will type (see rainbow.c in the archive):

for(i = 0; i < 256; i++) {
	// Line 1: red = 255 ; green = 0 -> 255 ; blue = 0
	mycolors[i] = gdImageColorAllocate(im, 255, i, 0);
	// Line 2: red = 255 -> 0 ; green = 255 ; blue = 0
	mycolors[i + 256] = gdImageColorAllocate(im, (255 - i), 255, 0);
	// Line 3: red = 0 ; green = 255 ; blue = 0 -> 255
	mycolors[i + 512] = gdImageColorAllocate(im, 0, 255, i);
	// Line 4: red = 0 ; green = 255 -> 0 ; blue = 255
	mycolors[i + 768] = gdImageColorAllocate(im, 0, (255 - i), 255);
	// Line 5: red = 0 -> 255 ; green = 0 ; blue = 255
	mycolors[i + 1024] = gdImageColorAllocate(im, i, 0, 255);
	// Line 6: red = 255 ; green = 0 ; blue = 255 -> 0
	mycolors[i + 1280] = gdImageColorAllocate(im, 255, 0, (255 - i));
}

And it doesn't work (see figure below)! Why? If we ask the values stored in the mycolors table, we'll see a lot of "-1" values (rainbow.c in the archive outputs these values for you). The problem comes from the gdImageColorAllocate function that returns "-1" when executed after the allocation of all gdMaxColors colors. gdMaxColors is the constant 256 ("the maximum number of colors in a palette-based PNG file according to the PNG standard").

Wrong rainbow generated with
GD in palette mode

So, what can we do? Either we will only take 256 colors (that is 256 / 6 = 42 colors per line) or we will do a TrueColor image. Let's try both options.

A palette-based rainbow

A palette-based rainbow is very easy to do (provided the code above was also easy to understand). Instead of taking 256 steps for each line, we'll only take 42 steps (because 256 possibilities / 6 lines = 42 possibilities per line, with integers). Since we'll only have 256 possibilities in total, I reduce the length of my picture (256 pixels instead of 1536).

Compared to the strange rainbow, this code didn't changed a lot (see rainbow-palette.c in the archive):

for(i = 0; i < 42; i++) {
	// Line 1: red = 255 ; green = 0 -> 255 ; blue = 0
	mycolors[i] = gdImageColorAllocate(im, 255, i * 6, 0);
	// Line 2: red = 255 -> 0 ; green = 255 ; blue = 0
	mycolors[i + 42] = gdImageColorAllocate(im, (255 - i * 6), 255, 0);
	// Line 3: red = 0 ; green = 255 ; blue = 0 -> 255
	mycolors[i + 84] = gdImageColorAllocate(im, 0, 255, i * 6);
	// Line 4: red = 0 ; green = 255 -> 0 ; blue = 255
	mycolors[i + 126] = gdImageColorAllocate(im, 0, (255 - i * 6), 255);
	// Line 5: red = 0 -> 255 ; green = 0 ; blue = 255
	mycolors[i + 168] = gdImageColorAllocate(im, i * 6, 0, 255);
	// Line 6: red = 255 ; green = 0 ; blue = 255 -> 0
	mycolors[i + 210] = gdImageColorAllocate(im, 255, 0, (255 - i * 6));
}

Finally, we get a decent rainbow :-) :

rainbow with 256 colors only

A TrueColor rainbow

In order to create a TrueColor image, we need to use the gdImageCreateTrueColor function in exactly the same way we used the gdImageCreate function for palette-based images. But Truecolor images are always filled with black (as "background") at creation time. Everything else is the same as for palette-based images. So, we can take the file rainbow.c and only change one line : im = gdImageCreateTrueColor(1536, 100); and that's all!. See rainbow-truecolor.c in the archive. The resulting image is:

rainbow with TrueColor image

That's all for the moment. If you have any question, request, etc., please contact me.