PHP 中的颜色排序:第 5 部分

自从我上次访问这个主题以来,我一直在考虑如何表示一组随机颜色,以便它看起来有序并且在此过程中不会丢失任何信息。我很快意识到我需要使用色彩空间的所有三个方面,这有助于生成 3D 对象。事实上,红、绿、蓝颜色空间是围绕一个立方体构建的,因此它通常可以表示为一个立方体。

色调、饱和度和值颜色空间是围绕圆柱的概念构建的,这意味着轴的 2 适合圆的周长(色调)和圆的直径(饱和度)。这是极坐标系的一个例子。因此,我们可以使用色调和饱和度绘制一个圆圈,该值可用于表示色彩空间的不同方面。

您可能已经看到用于在称为色轮的东西中绘制圆圈的色调和饱和度。这是允许您选择颜色的应用程序的最爱。以下是 Mac 上 Pages 应用程序的一个示例。

需要意识到的一件事是,该值实际上位于色轮下方,在这种情况下,我们所看到的与我正在努力克服的问题相同。即,删除一些数据以使轮子工作。我想要做的是将值合并到色轮中,以便我们可以同时表示所有三个。

首先,我需要创建一个 Color 类。这接受红色、绿色和蓝色,并根据这些值计算色调、饱和度和值。

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:
          // 如果最大值为 0。
          $hue = 0;
          $saturation = 0;
          $value = 0;
          break;
        case $min:
          // 如果最大值和最小值相同。
          $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;
    }
}

为了生成颜色,我创建了一个循环,根据它们的红色、绿色和蓝色值创建了 1000 种随机颜色。

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);
}

这会生成 100 种颜色,这足以开始看到排序工作,但没有这么多颜色,我们只是创建了一个色轮,无法看到存在的颜色之间的差异。

第一步是仅使用像素生成色轮。代码中有很多注释,但是沿着圆的圆周绘制像素是使用我在上一篇文章中绘制圆时使用的相同数学完成的。本质上,我们使用色调来计算出像素应该处于哪个角度,然后使用饱和度来计算出该像素应该离圆心多远。

function colorWheelPixels($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,y 坐标并将其偏移半径。
    $x = $centerX + $radius * cos($angle);
    $y = $centerY + $radius * sin($angle);
 
    // 将彩色像素放置在计算出的位置。
    imagesetpixel($im, $x, $y, $allocatedColor);
  }
 
  // 创建图像并销毁图像处理程序。
  imagepng($im, 'color-wheel-pixels-' . time() . '.png');
  imagedestroy($im);
}

这将生成以下图像。它很模糊,我们仍然缺少一些颜色信息,但颜色显然是排序的。

我认为将值整合到色轮中的第一种方法是使用线条。使用根据饱和度和色调计算出的坐标,我只是画了一条远离圆心的线。我们只是使用imageline()函数来创建这一行。线的长度与颜色的值成正比。这意味着较浅的颜色较长,较深的颜色较短。

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);
 
    // 从计算出的 x,y 坐标绘制一条远离中心的线。
    $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);
}

此代码产生以下输出。在这个例子中颜色更加明显,很明显它们已经被排序。

我的下一个想法是使用圆圈来显示价值。这有两种形式,圆圈和实心圆圈。

使用 imageellipse(). 该值乘以 10 以使其绘制的圆圈可见(值是 0 和 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);
 
    // 根据该值计算小圆圈的大小。
    $colorSize = $color->value * 10;
    imageellipse($im, $x, $y, $colorSize, $colorSize, $allocatedColor);
  }
 
  imagepng($im, 'color-wheel-circles-' . time() . '.png');
  imagedestroy($im);
}

这将产生以下图像。从这张图片中可以很明显地看出我们已经形成了一个色轮,圆圈是代表颜色值的一种很好的方式。

实心圆使用相同的代码,但在这种情况下,它们使用iamgefilledellipse()函数来生成圆。

function colorWheelFilledCircles($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);
 
    // 根据该值计算小圆圈的大小。
    $colorSize = $color->value * 10;
    imagefilledellipse($im, $x, $y, $colorSize, $colorSize, $allocatedColor);
  }
 
  imagepng($im, 'color-wheel-filled-circles-' . time() . '.png');
  imagedestroy($im);
}

这将产生以下图像。

那里的一切似乎都在工作,但我使用的随机颜色集合并不能很好地支持这些颜色已排序的想法。为了证明颜色是经过排序的,我决定取一小部分颜色,而不是来自整个光谱的颜色,并将它们显示在色轮中。

以下是一些仅使用色谱部分来生成色轮的示例。

顺便说一句,我确实尝试过生成所有可用的颜色作为 Color 对象。这基本上是 16,777,216 种不同的颜色(256 种红色 x 256 种绿色 x 256 种蓝色)。创建这么多对象并使用上述任何函数渲染它们在 PHP 中需要大约 4gig 的内存。然而,这确实会产生一个完美的色轮。

该图像是使用创建色轮的原始像素方法生成的,但它证明如果我们使可用的颜色空间饱和,我们会得到一个完美的圆圈。

就我个人而言,我认为生成实心圆色轮是表示给定数据集中可用颜色光谱的好方法。