Page decorations should be subtle and not distracting. I find that gentle gradients used as backgrounds satisfy this criteria. But making gradients is laborious. So I wrote a script to do it for me.


The script is simple but also quite powerful. Here is some example usage:

Blue to red moving left, produced with /cgi-bin/gradient.cgi?width=100&height=30&from=00f&to=f00&dir=left

Green to yellow moving up, produced with /cgi-bin/gradient.cgi?width=100&height=30&from=0f0&to=ff0&dir=up

It's also ideal for quickly testing gradient schemes in CSS styles, like this block of text.


There are five mandatory arguments for the CGI:

Argument Example Description
width150Width of image in pixels
height150Height of image in pixels
fromF12Starting color in 12-bit hex (See Notes below)
to0E8Ending color in 12-bit hex (See Notes below)
dirleftDirection, one of "left", "right", "up", or "down"


I chose to use a three character hexadecimal representation (12-bit) of the RGB color triplet for simplicities sake. I've found that in most situations, specifying six hex characters (24-bits) distracts and impedes a fast-and-loose creative process.


It's written in Perl, and requires GD capable of writing JPEG-encoded images.
# Gradient making program.
# Author: Michal Guerquin (
# Date: January, 2005

use CGI::Carp qw(fatalsToBrowser);
use CGI;
use GD;
use strict;

sub mkramp #($im, $steps, $r0, $g0, $b0, $r1, $g1, $b1)
  my ($im, $steps, $r0, $g0, $b0, $r1, $g1, $b1) = @_;
  my $dr = ($r1 - $r0) / $steps;
  my $dg = ($g1 - $g0) / $steps;
  my $db = ($b1 - $b0) / $steps;
  my @ramp = ();

  my $r = $r0;
  my $g = $g0;
  my $b = $b0;
  for (my $i=0; $i<$steps; $i++)
    @ramp = (@ramp, $im->colorAllocate($r,$g,$b));
    $r += $dr;
    $g += $dg;
    $b += $db;
  return @ramp;

my $theCGI = new CGI;

my $from = $theCGI->param("from");
my $to = $theCGI->param("to");
my $width = $theCGI->param("width");
my $height = $theCGI->param("height");
my $dir = $theCGI->param("dir");

if (not ($from  =~ /[0-9a-fA-F]{3}/)) { die "Missing or bad 'from'"; }
if (not ($to    =~ /[0-9a-fA-F]{3}/)) { die "Missing or bad 'to'"; }
if (not ($width =~ /[0-9]+/))         { die "Missing or bad 'width'"; }
if (not ($height =~ /[0-9]+/))        { die "Missing or bad 'height'"; }
if (not ($dir eq "left" or $dir eq "right" or $dir eq "up" or $dir eq "down")) { die "Missing or bad 'dir'"; }

$from =~ s/(.)(.)(.)/$1\|$2\|$3/;

$to =~ s/(.)(.)(.)/$1\|$2\|$3/;

my $from_r = 16*hex($from_r) + hex($from_r);
my $from_g = 16*hex($from_g) + hex($from_g);
my $from_b = 16*hex($from_b) + hex($from_b);
my $to_r = 16*hex($to_r) + hex($to_r);
my $to_g = 16*hex($to_g) + hex($to_g);
my $to_b = 16*hex($to_b) + hex($to_b);

my $steps = 1;
if ($dir eq "down") { $steps = $height; }
if ($dir eq "up") { $steps = $height; }
if ($dir eq "left") { $steps = $width; }
if ($dir eq "right") { $steps = $width; }

# create a new image
my $im = new GD::Image($width,$height);

my @ramp = &mkramp($im, $steps, $from_r,$from_g,$from_b, $to_r,$to_g,$to_b);

if ($dir eq "down")
  for (my $i=0; $i<$height; $i++)
elsif ($dir eq "up")
  for (my $i=0; $i<$height; $i++)
elsif ($dir eq "left")
  for (my $i=0; $i<$width; $i++)
elsif ($dir eq "right")
  for (my $i=0; $i<$width; $i++)

# make sure we are writing to a binary stream
binmode STDOUT;

print "Content-type: image/jpeg\n\n";

# Convert the image to JPG and print it on standard output
print $im->jpeg(100);


Ideas for future improvements:

This is, updated 2005-01-20 03:08 EST

Contact: michalg at domain where domain is (more)