Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 353
colour_manager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 14
6972
0.00% covered (danger)
0.00%
0 / 353
 __construct
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 11
 get_resource
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 11
 name_colour
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 3
 allocate_named
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 8
 allocate
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 34
 random_colour
0.00% covered (danger)
0.00%
0 / 1
420
0.00% covered (danger)
0.00%
0 / 77
 colour_scheme
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 17
 mono_range
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 31
 model_convert
0.00% covered (danger)
0.00%
0 / 1
132
0.00% covered (danger)
0.00%
0 / 43
 hsv2rgb
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 39
 rgb2hsv
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 27
 normalize_hue
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 7
 ah2h
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 21
 h2ah
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 24
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\captcha;
class colour_manager
{
    var $img;
    var $mode;
    var $colours;
    var $named_colours;
    /**
    * Create the colour manager, link it to the image resource
    */
    function __construct($img, $background = false, $mode = 'ahsv')
    {
        $this->img = $img;
        $this->mode = $mode;
        $this->colours = array();
        $this->named_colours = array();
        if ($background !== false)
        {
            $bg = $this->allocate_named('background', $background);
            imagefill($this->img, 0, 0, $bg);
        }
    }
    /**
    * Lookup a named colour resource
    */
    function get_resource($named_colour)
    {
        if (isset($this->named_colours[$named_colour]))
        {
            return $this->named_colours[$named_colour];
        }
        if (isset($this->named_rgb[$named_colour]))
        {
            return $this->allocate_named($named_colour, $this->named_rgb[$named_colour], 'rgb');
        }
        return false;
    }
    /**
    * Assign a name to a colour resource
    */
    function name_colour($name, $resource)
    {
        $this->named_colours[$name] = $resource;
    }
    /**
    * names and allocates a colour resource
    */
    function allocate_named($name, $colour, $mode = false)
    {
        $resource = $this->allocate($colour, $mode);
        if ($resource !== false)
        {
            $this->name_colour($name, $resource);
        }
        return $resource;
    }
    /**
    * allocates a specified colour into the image
    */
    function allocate($colour, $mode = false)
    {
        if ($mode === false)
        {
            $mode = $this->mode;
        }
        if (!is_array($colour))
        {
            if (isset($this->named_rgb[$colour]))
            {
                return $this->allocate_named($colour, $this->named_rgb[$colour], 'rgb');
            }
            if (!is_int($colour))
            {
                return false;
            }
            $mode = 'rgb';
            $colour = array(255 & ($colour >> 16), 255 & ($colour >>  8), 255 & $colour);
        }
        if (isset($colour['mode']))
        {
            $mode = $colour['mode'];
            unset($colour['mode']);
        }
        if (isset($colour['random']))
        {
            unset($colour['random']);
            // everything else is params
            return $this->random_colour($colour, $mode);
        }
        $rgb        = $this->model_convert($colour, $mode, 'rgb');
        $store        = ($this->mode == 'rgb') ? $rgb : $this->model_convert($colour, $mode, $this->mode);
        $resource    = imagecolorallocate($this->img, $rgb[0], $rgb[1], $rgb[2]);
        $this->colours[$resource] = $store;
        return $resource;
    }
    /**
    * randomly generates a colour, with optional params
    */
    function random_colour($params = array(), $mode = false)
    {
        if ($mode === false)
        {
            $mode = $this->mode;
        }
        switch ($mode)
        {
            case 'rgb':
                // @TODO random rgb generation. do we intend to do this, or is it just too tedious?
                break;
            case 'ahsv':
            case 'hsv':
            default:
                $default_params = array(
                    'hue_bias'            => false,    // degree / 'r'/'g'/'b'/'c'/'m'/'y'   /'o'
                    'hue_range'            => false,    // if hue bias, then difference range +/- from bias
                    'min_saturation'    => 30,        // 0 - 100
                    'max_saturation'    => 80,        // 0 - 100
                    'min_value'            => 30,        // 0 - 100
                    'max_value'            => 80,        // 0 - 100
                );
                $alt = ($mode == 'ahsv') ? true : false;
                $params = array_merge($default_params, $params);
                $min_hue        = 0;
                $max_hue        = 359;
                $min_saturation    = max(0, $params['min_saturation']);
                $max_saturation    = min(100, $params['max_saturation']);
                $min_value        = max(0, $params['min_value']);
                $max_value        = min(100, $params['max_value']);
                if ($params['hue_bias'] !== false)
                {
                    if (is_numeric($params['hue_bias']))
                    {
                        $h = intval($params['hue_bias']) % 360;
                    }
                    else
                    {
                        switch ($params['hue_bias'])
                        {
                            case 'o':
                                $h = $alt ?  60 :  30;
                                break;
                            case 'y':
                                $h = $alt ? 120 :  60;
                                break;
                            case 'g':
                                $h = $alt ? 180 : 120;
                                break;
                            case 'c':
                                $h = $alt ? 210 : 180;
                                break;
                            case 'b':
                                $h = 240;
                                break;
                            case 'm':
                                $h = 300;
                                break;
                            case 'r':
                            default:
                                $h = 0;
                                break;
                        }
                    }
                    $min_hue = $h + 360;
                    $max_hue = $h + 360;
                    if ($params['hue_range'])
                    {
                        $min_hue -= min(180, $params['hue_range']);
                        $max_hue += min(180, $params['hue_range']);
                    }
                }
                $h = mt_rand($min_hue, $max_hue);
                $s = mt_rand($min_saturation, $max_saturation);
                $v = mt_rand($min_value, $max_value);
                return $this->allocate(array($h, $s, $v), $mode);
                break;
        }
    }
    /**
    */
    function colour_scheme($resource, $include_original = true)
    {
        $mode = 'hsv';
        if (($pre = $this->get_resource($resource)) !== false)
        {
            $resource = $pre;
        }
        $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode);
        $results = ($include_original) ? array($resource) : array();
        $colour2 = $colour3 = $colour4 = $colour;
        $colour2[0] += 150;
        $colour3[0] += 180;
        $colour4[0] += 210;
        $results[] = $this->allocate($colour2, $mode);
        $results[] = $this->allocate($colour3, $mode);
        $results[] = $this->allocate($colour4, $mode);
        return $results;
    }
    /**
    */
    function mono_range($resource, $count = 5, $include_original = true)
    {
        if (is_array($resource))
        {
            $results = array();
            for ($i = 0, $size = count($resource); $i < $size; ++$i)
            {
                $results = array_merge($results, $this->mono_range($resource[$i], $count, $include_original));
            }
            return $results;
        }
        $mode = (in_array($this->mode, array('hsv', 'ahsv'), true) ? $this->mode : 'ahsv');
        if (($pre = $this->get_resource($resource)) !== false)
        {
            $resource = $pre;
        }
        $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode);
        $results = array();
        if ($include_original)
        {
            $results[] = $resource;
            $count--;
        }
        // This is a hard problem. I chicken out and try to maintain readability at the cost of less randomness.
        while ($count > 0)
        {
            $colour[1] = ($colour[1] + mt_rand(40,60)) % 99;
            $colour[2] = ($colour[2] + mt_rand(40,60));
            $results[] = $this->allocate($colour, $mode);
            $count--;
        }
        return $results;
    }
    /**
    * Convert from one colour model to another
    */
    function model_convert($colour, $from_model, $to_model)
    {
        if ($from_model == $to_model)
        {
            return $colour;
        }
        switch ($to_model)
        {
            case 'hsv':
                switch ($from_model)
                {
                    case 'ahsv':
                        return $this->ah2h($colour);
                        break;
                    case 'rgb':
                        return $this->rgb2hsv($colour);
                        break;
                }
                break;
            case 'ahsv':
                switch ($from_model)
                {
                    case 'hsv':
                        return $this->h2ah($colour);
                        break;
                    case 'rgb':
                        return $this->h2ah($this->rgb2hsv($colour));
                        break;
                }
                break;
            case 'rgb':
                switch ($from_model)
                {
                    case 'hsv':
                        return $this->hsv2rgb($colour);
                        break;
                    case 'ahsv':
                        return $this->hsv2rgb($this->ah2h($colour));
                        break;
                }
                break;
        }
        return false;
    }
    /**
    * Slightly altered from wikipedia's algorithm
    */
    function hsv2rgb($hsv)
    {
        $this->normalize_hue($hsv[0]);
        $h = $hsv[0];
        $s = min(1, max(0, $hsv[1] / 100));
        $v = min(1, max(0, $hsv[2] / 100));
        // calculate hue sector
        $hi = floor($hsv[0] / 60);
        // calculate opposite colour
        $p = $v * (1 - $s);
        // calculate distance between hex vertices
        $f = ($h / 60) - $hi;
        // coming in or going out?
        if (!($hi & 1))
        {
            $f = 1 - $f;
        }
        // calculate adjacent colour
        $q = $v * (1 - ($f * $s));
        switch ($hi)
        {
            case 0:
                $rgb = array($v, $q, $p);
                break;
            case 1:
                $rgb = array($q, $v, $p);
                break;
            case 2:
                $rgb = array($p, $v, $q);
                break;
            case 3:
                $rgb = array($p, $q, $v);
                break;
            case 4:
                $rgb = array($q, $p, $v);
                break;
            case 5:
                $rgb = array($v, $p, $q);
                break;
            default:
                return array(0, 0, 0);
                break;
        }
        return array(255 * $rgb[0], 255 * $rgb[1], 255 * $rgb[2]);
    }
    /**
    * (more than) Slightly altered from wikipedia's algorithm
    */
    function rgb2hsv($rgb)
    {
        $r = min(255, max(0, $rgb[0]));
        $g = min(255, max(0, $rgb[1]));
        $b = min(255, max(0, $rgb[2]));
        $max = max($r, $g, $b);
        $min = min($r, $g, $b);
        $v = $max / 255;
        $s = (!$max) ? 0 : 1 - ($min / $max);
        // if max - min is 0, we want hue to be 0 anyway.
        $h = $max - $min;
        if ($h)
        {
            switch ($max)
            {
                case $g:
                    $h = 120 + (60 * ($b - $r) / $h);
                    break;
                case $b:
                    $h = 240 + (60 * ($r - $g) / $h);
                    break;
                case $r:
                    $h = 360 + (60 * ($g - $b) / $h);
                    break;
            }
        }
        $this->normalize_hue($h);
        return array($h, $s * 100, $v * 100);
    }
    /**
    */
    function normalize_hue(&$hue)
    {
        $hue %= 360;
        if ($hue < 0)
        {
            $hue += 360;
        }
    }
    /**
    * Alternate hue to hue
    */
    function ah2h($ahue)
    {
        if (is_array($ahue))
        {
            $ahue[0] = $this->ah2h($ahue[0]);
            return $ahue;
        }
        $this->normalize_hue($ahue);
        // blue through red is already ok
        if ($ahue >= 240)
        {
            return $ahue;
        }
        // ahue green is at 180
        if ($ahue >= 180)
        {
            // return (240 - (2 * (240 - $ahue)));
            return (2 * $ahue) - 240; // equivalent
        }
        // ahue yellow is at 120   (RYB rather than RGB)
        if ($ahue >= 120)
        {
            return $ahue - 60;
        }
        return $ahue / 2;
    }
    /**
    * hue to Alternate hue
    */
    function h2ah($hue)
    {
        if (is_array($hue))
        {
            $hue[0] = $this->h2ah($hue[0]);
            return $hue;
        }
        $this->normalize_hue($hue);
        // blue through red is already ok
        if ($hue >= 240)
        {
            return $hue;
        }
        else if ($hue <= 60)
        {
            return $hue * 2;
        }
        else if ($hue <= 120)
        {
            return $hue + 60;
        }
        else
        {
            return ($hue + 240) / 2;
        }
    }
}