The last time I looked at sorting colours I had produced a nice band or sorted colours, but to do so I had essentially removed a third of the data from the colour information. This meant that there was no white or black colours in the band of sorted colours.
After a bit of thinking on how to solve this I hit upon a way of using a two dimensional array of colours to filter the colours into blocks. This would allow the missing colour information to be rendered correctly, and would only mean a small amount of work to allow it to work with the rendering function used in the previous examples.
Generating The Data
I could easily just generate every colour available and use that as the data. What I wanted to generate was a random assortment of colours that would represent the sort of data being produced by a system or other input.
To generate the data I created an array of 360 blank arrays. The 360 represents the hue value of the colour so all that was needed is to create the saturation and values for the colour and then store them in the correct array.
The following code initialises the colour arrays and then fills them with some random data. The hsvToRgb() function can be found in part 2.
$colourRanges = array_fill(0, 360, []);
foreach ($colourRanges as $hue => $range) {
for ($i = 0; $i < 300; ++$i) {
// Generate random values for saturation and value.
$saturation = mt_rand(0, 100) / 100;
$value = mt_rand(0, 100) / 100;
// Convert the colour from hsv to rgb.
$rgb = hsvToRbg($hue, $saturation, $value);
// Create Colour object.
$colourRanges[$hue][] = new Colour($rgb['red'], $rgb['blue'], $rgb['green']);
}
}
The loop of 300 is just an arbitrary value to generate a decent amount of data in each hue array.
Rendering
The rendering function just needs a little bit of alteration so that it can render a multidimensional array of Colour objects instead of just a flat array.
function renderColours($colourRanges, $sortedBy) {
$pixelSize = 1;
// Work out width.
$width = count($colourRanges) * $pixelSize;
// Ensure the correct height is worked out.
$height = 0;
foreach ($colourRanges as $colourRange) {
$potentialHeight = count($colourRange) * $pixelSize;
if ($potentialHeight > $height) {
$height = $potentialHeight;
}
}
// Initialize image.
$im = imagecreatetruecolor($width, $height);
// Fill image with coloured pixels.
$x = 0;
foreach ($colourRanges as $colourRange) {
$y = 0;
foreach ($colourRange as $yColour => $colour) {
$pixelColour = imagecolorallocate($im, $colour->red, $colour->green, $colour->blue);
imagefilledrectangle($im, $x, $y, $x + $pixelSize, $y + $pixelSize, $pixelColour);
$y = $y + $pixelSize;
}
$x = $x + $pixelSize;
}
// Save image.
imagepng($im, 'colours-' . $sortedBy . '.png');
imagedestroy($im);
}
Without sorting this generates the following image.

This shows that the hue is already sorted into bands of colours, so all we need to do is go through each band and sort it by other values.
Sorting
With that in place we need to sort each of the arrays and see if we can get any decent results by sorting just the bands of colours. This differs from the sorting done in the other posts, with the difference that we loop through the array and sort each band of colours.
RGB
Sorting by RGB is just as it sounds, just add together the reg, green and blue values and compare them.
foreach ($colourRanges as $id => $colourRange) {
usort($colourRange, function ($a, $b) {
return ($a->red + $a->green + $a->blue) <=> ($b->red + $b->green + $b->blue);
});
$colourRanges[$id] = $colourRange;
}
renderColours($colourRanges, 'rgb' . time());
This generates the following image.

It's clear that some sorting has taken place here. The whiter colours are pushed towards the bottom and the darker colours are pushed towards the top. Grey is still a bit of a problem as it doesn't appear to have moved much.
Lightness
For lightness I am re-using the calculateLightness() function from part 1.
foreach ($colourRanges as $id => $colourRange) {
usort($colourRange, function ($a, $b) {
return calculateLightness($a) <=> calculateLightness($b);
});
$colourRanges[$id] = $colourRange;
}
renderColours($colourRanges, 'lightness' . time());
This produces the following output.

Similar to the RGB sorting this works for lighter and darker colours, but greys are largely ignored. I thought this would create better results than just adding together the RGB values but this doesn't appear to be the case.
Luminance
The luminance of the colour is calculated by working out the proportions of red, green and blue to create a perceived level of lightness. Re-using the calculateLuminance() function from part 1.
foreach ($colourRanges as $id => $colourRange) {
usort($colourRange, function ($a, $b) {
return calculateLuminance($a) <=> calculateLuminance($b);
});
$colourRanges[$id] = $colourRange;
}
renderColours($colourRanges, 'luminance' . time());
This produces the following image.

This creates a better result than lightness or RGB sorting, but grey is still causing problems.
HSV
Sorting by hue, saturation and value is not necessary as we have technically already sorted by hue. As a result we just need to sort by the sum of saturation and value.
foreach ($colourRanges as $id => $colourRange) {
usort($colourRange, function ($a, $b) {
$hsv1 = rgbTohsv($a);
$hsv2 = rgbTohsv($b);
return ($hsv1['saturation'] + $hsv1['value']) <=> ($hsv2['saturation'] + $hsv2['value']);
});
$colourRanges[$id] = $colourRange;
}
renderColours($colourRanges, 'hsv' . time());
This creates the following image.

This has pushed the high saturation colours to the bottom and the low saturation colours to the top. This results in the whites and grey colours being distributed across the spectrum and not sorted into the correct result.
HEX
Out of interest (and because HEX sorting is quite good when sorting with single bands of colours) I decided to try sorting by the HEX value of the colour.
foreach ($colourRanges as $id => $colourRange) {
usort($colourRange, function ($a, $b) {
$aValue['red'] = str_pad(dechex($a->red), 2, '0', STR_PAD_LEFT);
$aValue['green'] = str_pad(dechex($a->green), 2, '0', STR_PAD_LEFT);
$aValue['blue'] = str_pad(dechex($a->blue), 2, '0', STR_PAD_LEFT);
$bValue['red'] = str_pad(dechex($b->red), 2, '0', STR_PAD_LEFT);
$bValue['green'] = str_pad(dechex($b->green), 2, '0', STR_PAD_LEFT);
$bValue['blue'] = str_pad(dechex($b->blue), 2, '0', STR_PAD_LEFT);
$aValue = implode($aValue);
$bValue = implode($bValue);
return $aValue <=> $bValue;
});
$colourRanges[$id] = $colourRange;
}
renderColours($colourRanges, 'hex' . time());
This generates the following image.

Yup, pretty terrible. Weirdly, this reminds me of the colour absorption spectrum of chlorophyll. This pattern is created because of the way that the hex value is created. Because of the way in which the hex value is added together I have essentially created a process that gives more precedence to red colours so those colours are slightly better sorted in this example.
Not Enough?
This approach of sorting a multidimensional array is closer than sorting a single dimensional array, but there are still a few problems. I stated before that creating bands of hue creates separate blocks of colours. Although this is technically true we still can't sort because we essentially have to combine one or more values in order to generate a sorting algorithm. Although we can separate and sort each colour there are still a lot of grey values that don't fit nicely into the sorting algorithm because we are essentially trying to force a three-dimensional data item (i.e. the colour) into a two-dimensional array.
Add new comment