Color Sorting In PHP: Part 4

Following on from by previous post about sorting colors I decided to take another step towards sorting colors by segmenting the data to create a further dimension to the multi-dimensional array.

The array is already split into segments based on the hue of the color, but we can further split this by separating out saturation and value into separate arrays within hue. To do this we set the saturation or value to be a constant and push them into separate arrays.

The new code looks like this. Please excuse the duplication of code here. This is just a simple example to show how the array is put together.

$colorRanges = array_fill(0, 360, []);

foreach ($colorRanges as $hue => $range) {
  // Setup saturation and value arrays.
  $colorRanges[$hue]['saturation'] = [];
  $colorRanges[$hue]['value'] = [];

  for ($i = 0; $i < 150; ++$i) {
    // Generate random values for saturation.
    $saturation = mt_rand(0, 100) / 100;
    $value = 1;

    // Convert the color from hsv to rgb.
    $rgb = hsvToRbg($hue, $saturation, $value);

    // Create Color object.
    $colorRanges[$hue]['saturation'][] = new Color($rgb['red'], $rgb['blue'], $rgb['green']);
  }

  for ($i = 0; $i < 150; ++$i) {
    // Generate random values for value.
    $saturation = 1;
    $value = mt_rand(0, 100) / 100;

    // Convert the color from hsv to rgb.
    $rgb = hsvToRbg($hue, $saturation, $value);

    // Create Color object.
    $colorRanges[$hue]['value'][] = new Color($rgb['red'], $rgb['blue'], $rgb['green']);
  }
}

Setting the value of saturation or value of a color to be 1 (or 100%) is important in order to actually show the colors. With a value of 0 the colors will be either all black (for saturation) or a combination of black or white (for value). With a value in between 0 and 1 showing pale or washed out colors.

Rendering

As I have added another dimension to the array I need to alter the render function slightly to render out all of the data in the array.

function renderColors($colorRanges, $sortedBy) {
  $pixelSize = 1;

  // Work out width.
  $width = count($colorRanges) * $pixelSize;

  // Ensure the correct height is worked out.
  $height = 0;
  foreach ($colorRanges as $colorRange) {
    $potentialHeight = max(count($colorRange['saturation']), count($colorRange['value'])) * $pixelSize;
    if ($potentialHeight > $height) {
      $height = $potentialHeight;
    }
  }

  // Height will be double.
  $height = $height * 2;

  // Initialize image.
  $im = imagecreatetruecolor($width, $height);

  // Fill image with colored pixels.
  $x = 0;
  foreach ($colorRanges as $colorRange) {
    $y = 0;
    foreach ($colorRange['saturation'] as $color) {
      $pixelColour = imagecolorallocate($im, $color->red, $color->green, $color->blue);
      imagefilledrectangle($im, $x, $y, $x + $pixelSize, $y + $pixelSize, $pixelColour);
      $y = $y + $pixelSize;
    }
    foreach ($colorRange['value'] as $color) {
      $pixelColour = imagecolorallocate($im, $color->red, $color->green, $color->blue);
      imagefilledrectangle($im, $x, $y, $x + $pixelSize, $y + $pixelSize, $pixelColour);
      $y = $y + $pixelSize;
    }
    $x = $x + $pixelSize;
  }

  // Save image.
  imagepng($im, 'colors-' . $sortedBy . '.png');
  imagedestroy($im);
}

What we are doing here is rendering out the image in two parts. The saturation part of the image is on top and the value part of the image is at the bottom. This generates an image that looks like this with the above random data.

Colors, segmented by hue, saturation and value, but not sorted.

We can now go about sorting this data structure.

Sorting

I quickly discovered that sorting this data becomes quite easy a few different comparisons (RGB, HSV, lightness etc) produced very similar (if not identical) results.

As an example, here is RGB sorting of the colors array.

foreach ($colorRanges as $id => $colorRange) {
  foreach ($colorRange as $coloramountid => $coloramount) {
    usort($coloramount, function ($a, $b) {
      return ($b->red + $b->green + $b->blue) <=> ($a->red + $a->green + $a->blue);
    });
    $colorRanges[$id][$coloramountid] = $coloramount;
  }
}

renderColors($colorRanges, 'rgb' . time());

This generates the following image.

Colors, segmented by hue, saturation and value, sorted by RGB values.

The HVS sorting code is a little different.

foreach ($colorRanges as $id => $colorRange) {
    usort($colorRange['saturation'], function ($a, $b) {
      $hsv1 = rgbTohsv($a);
      $hsv2 = rgbTohsv($b);

      return $hsv1['saturation'] <=> $hsv2['saturation'];
    });
    $colorRanges[$id]['saturation'] = $colorRange['saturation'];

    usort($colorRange['value'], function ($a, $b) {
      $hsv1 = rgbTohsv($a);
      $hsv2 = rgbTohsv($b);

      return $hsv2['value'] <=> $hsv1['value'];
    });

    $colorRanges[$id]['value'] = $colorRange['value'];
}

This results in the following image.

Colors, segmented by hue, saturation and value and then sorted by saturation and value.

This is very close to the original intension of the research. This was to take an array of colors and sort them into a coherent looking spectrum.

One thing of interest was the HEX sorting algorithm. This worked for the most part, but produced some odd artefacts in the sorting or the value part of the image. This is the image that was produced.

Colors, segmented by hue, saturation and value, sorted by HEX.

Still Not Enough?

Those with keen eyes will probably spot that although we have created a nice looking spectrum, we have actually lost information here. Setting the saturation and value amounts to 1 is essentially removing that value from the equation. This means we are still losing data to get this working correctly. Additionally, we have artificially tied together hue with either a varying saturation or value. In the real world all three values would not be linked to each other so using this algorithm to sort colors from real world data would not work.

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
10 + 0 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.