LjunggrenThe Lair

Archive

Reference

PHP snippet: RGB ↔ HSV colour conversion

A small PHP class for converting colours between RGB and HSV, handy for twisting pictures and generating captchas.

Originally
2007
Reviewed
2026-11
Revision
2

HSV (hue, saturation, value) is a much friendlier space than RGB when you want to shift a colour: rotate the hue, wash out the saturation, darken the value, then convert back. I used this class for picture twisting and for generating captchas with GD.

Usage

$c = new ColorMangler();
[$c->r, $c->g, $c->b] = [133, 64, 127];   // heather, #85407F
$c->toHSV();
$c->h = fmod($c->h + 0.5, 1);               // rotate the hue half a turn
$c->authorative = 'hsv';                    // h/s/v now hold the truth
echo $c->toHEX();                           // the complementary green
$colour = $c->getTrueColor($im);            // or allocate it in a GD image

The class

// HSV values: 0-1. RGB values: 0-255.
class ColorMangler
{
    public string $authorative = 'rgb'; // set to 'hsv' after changing h/s/v, so the outputs convert first
    public float $r = 0, $g = 0, $b = 0, $h = 0, $s = 0, $v = 0;

    public function toHSV(): void
    {
        $var_R = $this->r / 255;
        $var_G = $this->g / 255;
        $var_B = $this->b / 255;

        $var_Min = min($var_R, $var_G, $var_B);
        $var_Max = max($var_R, $var_G, $var_B);
        $del_Max = $var_Max - $var_Min;

        $V = $var_Max;

        if ($del_Max == 0) {
            $H = 0;
            $S = 0;
        } else {
            $S = $del_Max / $var_Max;

            $del_R = ((($var_Max - $var_R) / 6) + ($del_Max / 2)) / $del_Max;
            $del_G = ((($var_Max - $var_G) / 6) + ($del_Max / 2)) / $del_Max;
            $del_B = ((($var_Max - $var_B) / 6) + ($del_Max / 2)) / $del_Max;

            if ($var_R == $var_Max) {
                $H = $del_B - $del_G;
            } elseif ($var_G == $var_Max) {
                $H = (1 / 3) + $del_R - $del_B;
            } else {
                $H = (2 / 3) + $del_G - $del_R;
            }

            if ($H < 0) {
                $H++;
            }
            if ($H > 1) {
                $H--;
            }
        }

        $this->h = $H;
        $this->s = $S;
        $this->v = $V;
    }

    public function toRGB(): void
    {
        $H = $this->h;
        $S = $this->s;
        $V = $this->v;

        if ($S == 0) {
            $R = $G = $B = $V * 255;
        } else {
            $var_H = $H == 1 ? 0 : $H * 6; // H = 1 is the same hue as 0
            $var_i = floor($var_H);
            $var_1 = $V * (1 - $S);
            $var_2 = $V * (1 - $S * ($var_H - $var_i));
            $var_3 = $V * (1 - $S * (1 - ($var_H - $var_i)));

            if ($var_i == 0) {
                [$var_R, $var_G, $var_B] = [$V, $var_3, $var_1];
            } elseif ($var_i == 1) {
                [$var_R, $var_G, $var_B] = [$var_2, $V, $var_1];
            } elseif ($var_i == 2) {
                [$var_R, $var_G, $var_B] = [$var_1, $V, $var_3];
            } elseif ($var_i == 3) {
                [$var_R, $var_G, $var_B] = [$var_1, $var_2, $V];
            } elseif ($var_i == 4) {
                [$var_R, $var_G, $var_B] = [$var_3, $var_1, $V];
            } else {
                [$var_R, $var_G, $var_B] = [$V, $var_1, $var_2];
            }

            $R = $var_R * 255;
            $G = $var_G * 255;
            $B = $var_B * 255;
        }

        // Whole numbers: GD and %02X take ints, and PHP 8.1+ warns on lossy float-to-int conversion.
        $this->r = round($R);
        $this->g = round($G);
        $this->b = round($B);
    }

    public function toHEX(): string
    {
        if ($this->authorative == 'hsv') {
            $this->toRGB();
        }
        return sprintf('#%02X%02X%02X', $this->r, $this->g, $this->b);
    }

    public function toCSSRGB(): string
    {
        if ($this->authorative == 'hsv') {
            $this->toRGB();
        }
        return 'rgb(' . $this->r . ',' . $this->g . ',' . $this->b . ')';
    }

    public function getTrueColor(GdImage $im): int|false
    {
        if ($this->authorative == 'hsv') {
            $this->toRGB();
        }
        return imagecolorallocate($im, (int)$this->r, (int)$this->g, (int)$this->b);
    }
}

Where this came from. Written for the old ljunggren.net code-snippets page. The conversion follows the widely used EasyRGB RGB/HSV formulas.