Since my last visit to this subject I have been thinking about how to represent a collection of random colors so that it looks sorted and that no information is lost during that process. I quickly realised that I needed to use all three aspects of the color space, which lends itself to generating 3D objects. Indeed, the red, green, blue color space is built around a cube so it can often be represented as a cube.
The hue, saturation, and value color space is built around the concept of a cylinder, which means that 2 of the axis lend themselves to a circumference of a circle (the hue) and the diameter of a circle (the saturation). This is an example of the polar coordinate system. We can therefore draw a circle using hue and saturation and the value can be used to represent a different aspect of the color space.
You might have already seen the hue and saturation used to draw a circle in something called a color wheel. This is a favourite of applications that allow you to pick a color. Here is an example from the Pages application on a Mac.
One thing to realise is that the value is actually underneath the color wheel, in which case what we are looking at is the same problem I am trying to overcome. Namely, removing some of the data to make the wheel work. What I want to do is incorporate the value into the color wheel so that we can represent all three at once.
To start with I need to create a Color class. This accepts red, green and blue and calculates the hue, saturation and value based on these values.
class Color {
public $red = 0;
public $green = 0;
public $blue = 0;
public $hue;
public $saturation;
public $value;
public function __construct($red, $green, $blue)
{
$this->red = $red;
$this->green = $green;
$this->blue = $blue;
$this->calculageHsv();
}
function calculageHsv() {
$red = $this->red / 255;
$green = $this->green / 255;
$blue = $this->blue / 255;
$min = min($red, $green, $blue);
$max = max($red, $green, $blue);
switch ($max) {
case 0:
// If the max value is 0.
$hue = 0;
$saturation = 0;
$value = 0;
break;
case $min:
// If the maximum and minimum values are the same.
$hue = 0;
$saturation = 0;
$value = round($max, 4);
break;
default:
$delta = $max - $min;
if ($red == $max) {
$hue = 0 + ($green - $blue) / $delta;
} elseif ($green == $max) {
$hue = 2 + ($blue - $red) / $delta;
} else {
$hue = 4 + ($red - $green) / $delta;
}
$hue *= 60;
if ($hue < 0) {
$hue += 360;
}
$saturation = $delta / $max;
$value = round($max, 4);
}
$this->hue = $hue;
$this->saturation = $saturation;
$this->value = $value;
}
}
To generate the colors I created a loop that created 1000 random colors based on their red, green and blue values.
for ($i = 0; $i <1000; $i++) {
$red = ceil(mt_rand(0, 255));
$green = ceil(mt_rand(0, 255));
$blue = ceil(mt_rand(0, 255));
$colors[] = new Color($red, $green, $blue);
}
This generates 100 colors, which is good enough to start seeing the sorting working, but without so many colors that we just create a color wheel and can't see the difference between the colors present.
The first step is to generate the color wheel using just pixels. There is plenty of comments in the code, but the drawing of the pixels along the circumference of the circle is done using the same maths that I employed when drawing circles in a previous post. Essentially, we use the hue to work out what angle the pixel should lie at and then use the saturation to work out how far that pixel should be from the centre of the circle.
function colorWheelPixels($colors) {
// Set the width and height of the image.
$width = $height = 300;
// Create the image handler.
$im = imagecreatetruecolor($width, $height);
// Set a background color of white and fill the image with it.
$bgColor = imagecolorallocate($im, 255, 255, 255);
imagefill($im, 0, 0, $bgColor);
// Calcualte the centre of the image, based on the width and height.
$centerX = $centerY = $width / 2;
foreach ($colors as $color) {
// Create a color.
$allocatedColor = imagecolorallocate($im, $color->red, $color->green, $color->blue);
// Calculate the radius of the color based on the saturation.
$radius = $color->saturation * $centerX;
// We need to convert the degrees to radians so we can position the pixel on the
// correct point on the circumference of the circle.
$angle = deg2rad($color->hue);
// Calculate the x,y coordinates of the hue and offset it by the radius.
$x = $centerX + $radius * cos($angle);
$y = $centerY + $radius * sin($angle);
// Place the colored pixel in the calculated position.
imagesetpixel($im, $x, $y, $allocatedColor);
}
// Create the image and destroy the image handler.
imagepng($im, 'color-wheel-pixels-' . time() . '.png');
imagedestroy($im);
}
This generates the following image. It's quite faint, and we are still missing some of the color information, but the colors are obviously sorted.
The first way I thought to show integrate the value into the color wheel was to use lines. Using the worked out coordinate from the saturation and hue I just drew a line that pointed away from the centre of the circle. We just use the imageline() function to create this line. The length of the line is proportional to the value of the color. This means that lighter colors are longer and darker colors are shorter.
function colorWheelLines($colors) {
$width = $height = 300;
$im = imagecreatetruecolor($width, $height);
$bgColor = imagecolorallocate($im, 255, 255, 255);
imagefill($im, 0, 0, $bgColor);
$centerX = $centerY = $width / 2;
foreach ($colors as $color) {
$allocatedColor = imagecolorallocate($im, $color->red, $color->green, $color->blue);
$radius = $color->saturation * $centerX;
$angle = deg2rad($color->hue);
$x = $centerX + $radius * cos($angle);
$y = $centerY + $radius * sin($angle);
// Draw a line from the calculated x,y coordinates away from the center.
$x2 = $x + ($color->value * 10) * cos($angle);
$y2 = $y + ($color->value * 10) * sin($angle);
imageline($im, $x, $y, $x2, $y2, $allocatedColor);
}
imagepng($im, 'color-wheel-lines-' . time() . '.png');
imagedestroy($im);
}
This code produces the following output. The colors are much more apparent in this example and it's clear that the they have been sorted.
The next idea I had was to use circles to show the value. This came in two forms, circles and filled circles.
The circles based on the value of the color were pretty easy to generate using the imageellipse(). The value was multiplied by 10 in order to make the circles it draws visible (value is a decimal value between 0 and 1).
function colorWheelCircles($colors) {
$width = $height = 300;
$im = imagecreatetruecolor($width, $height);
$bgColor = imagecolorallocate($im, 255, 255, 255);
imagefill($im, 0, 0, $bgColor);
$centerX = $centerY = $width / 2;
foreach ($colors as $color) {
$allocatedColor = imagecolorallocate($im, $color->red, $color->green, $color->blue);
$radius = $color->saturation * $centerX;
$angle = deg2rad($color->hue);
$x = $centerX + $radius * cos($angle);
$y = $centerY + $radius * sin($angle);
// Calculate the size of the little circles based on the value.
$colorSize = $color->value * 10;
imageellipse($im, $x, $y, $colorSize, $colorSize, $allocatedColor);
}
imagepng($im, 'color-wheel-circles-' . time() . '.png');
imagedestroy($im);
}
This produces the following image. It's very clear from this image that we have formed a color wheel, and the circles are a nice way of representing the value of the colors.
The filled circles use the same code, but in this case they use the iamgefilledellipse() function to generate the circle.
function colorWheelFilledCircles($colors) {
// Sort the colors by the value to prevent any overlapping of colors.
usort($colors, function ($a, $b) {
return $b->value <=> $a->value;
});
$width = $height = 300;
$im = imagecreatetruecolor($width, $height);
$bgColor = imagecolorallocate($im, 255, 255, 255);
imagefill($im, 0, 0, $bgColor);
$centerX = $centerY = $width / 2;
foreach ($colors as $color) {
$allocatedColor = imagecolorallocate($im, $color->red, $color->green, $color->blue);
$radius = $color->saturation * $centerX;
$angle = deg2rad($color->hue);
$x = $centerX + $radius * cos($angle);
$y = $centerY + $radius * sin($angle);
// Calculate the size of the little circles based on the value.
$colorSize = $color->value * 10;
imagefilledellipse($im, $x, $y, $colorSize, $colorSize, $allocatedColor);
}
imagepng($im, 'color-wheel-filled-circles-' . time() . '.png');
imagedestroy($im);
}
This produces the following image.
Everything there appears to be working, but the random collection of colors I am using isn't lending itself well to the idea that these colors are sorted. To demonstrate that the colors are sorted I decided to take a subsection of colors, rather than colors from all over the spectrum, and show them in the color wheel.
Here are some examples of using just sections of the color spectrum to generate color wheels.
Incidentally, I did have a go at generating all of the colors available as Color objects. This was essentially 16,777,216 different colors (256 red x 256 green x 256 blue). Creating this many objects and rendering them using any of the above functions takes about 4gig of memory in PHP. This does, however, generate a perfect color wheel.
That image was generated using the original pixels approach to creating the color wheel, but it proves that if we saturate the colors space available we get a perfect circle.
Personally, I think generating a filled circle color wheel is a good way of representing the spectrum of colors available in a given data set.
Add new comment