1<?php
2
3namespace splitbrain\RingIcon;
4
5/**
6 * Class RingIcon
7 *
8 * Generates a identicon/visiglyph like image based on concentric rings
9 *
10 * @todo add a mono color version
11 * @author Andreas Gohr <andi@splitbrain.org>
12 * @license MIT
13 * @package splitbrain\RingIcon
14 */
15class RingIcon
16{
17    protected $size;
18    protected $fullsize;
19    protected $rings = 4;
20    protected $center;
21    protected $ringwidth;
22    protected $seed;
23    protected $ismono = false;
24    protected $monocolor;
25
26    /**
27     * RingIcon constructor.
28     * @param int $size width and height of the resulting image
29     * @param int $rings number of rings
30     */
31    public function __construct($size, $rings = 3)
32    {
33        $this->size = $size;
34        $this->fullsize = $this->size * 5;
35
36        $this->center = floor($this->fullsize / 2);
37        $this->ringwidth = floor($this->fullsize / $rings);
38
39        $this->seed = random_int(0, mt_getrandmax()) . time();
40    }
41
42    /**
43     * Generates an ring image
44     *
45     * If a seed is given, the image will be based on that seed
46     *
47     * @param string $seed initialize the genrator with this string
48     * @param string $file if given, the image is saved at that path, otherwise is printed to browser
49     */
50    public function createImage($seed = '', $file = '')
51    {
52        if (!$seed) {
53            $seed = random_int(0, mt_getrandmax()) . time();
54        }
55        $this->seed = $seed;
56
57        // monochrome wanted?
58        if ($this->ismono) {
59            $this->monocolor = [$this->rand(20, 255), $this->rand(20, 255), $this->rand(20, 255)];
60        } else {
61            $this->monocolor = null;
62        }
63
64        // create
65        $image = $this->createTransparentImage($this->fullsize, $this->fullsize);
66        $arcwidth = $this->fullsize;
67        for ($i = $this->rings; $i > 0; $i--) {
68            $this->drawRing($image, $arcwidth);
69            $arcwidth -= $this->ringwidth;
70        }
71
72        // resample for antialiasing
73        $out = $this->createTransparentImage($this->size, $this->size);
74        imagecopyresampled($out, $image, 0, 0, 0, 0, $this->size, $this->size, $this->fullsize, $this->fullsize);
75        if ($file) {
76            imagepng($out, $file);
77        } else {
78            header("Content-type: image/png");
79            imagepng($out);
80        }
81        imagedestroy($out);
82        imagedestroy($image);
83    }
84
85    /**
86     * When set to true a monochrome version is returned
87     *
88     * @param bool $ismono
89     */
90    public function setMono($ismono)
91    {
92        $this->ismono = $ismono;
93    }
94
95    /**
96     * Generate number from seed
97     *
98     * Each call runs MD5 on the seed again
99     *
100     * @param int $min
101     * @param int $max
102     * @return int
103     */
104    protected function rand($min, $max)
105    {
106        $this->seed = md5($this->seed);
107        $rand = hexdec(substr($this->seed, 0, 8));
108        return ($rand % ($max - $min + 1)) + $min;
109    }
110
111    /**
112     * Drawas a single ring
113     *
114     * @param resource $image
115     * @param int $arcwidth outer width of the ring
116     */
117    protected function drawRing($image, $arcwidth)
118    {
119        $color = $this->randomColor($image);
120        $transparency = $this->transparentColor($image);
121
122        $start = $this->rand(20, 360);
123        $stop = $this->rand(20, 360);
124        if ($stop < $start) [$start, $stop] = [$stop, $start];
125
126        imagefilledarc($image, $this->center, $this->center, $arcwidth, $arcwidth, $stop, $start, $color, IMG_ARC_PIE);
127        imagefilledellipse(
128            $image,
129            $this->center,
130            $this->center,
131            $arcwidth - $this->ringwidth,
132            $arcwidth - $this->ringwidth,
133            $transparency
134        );
135
136        imagecolordeallocate($image, $color);
137        imagecolordeallocate($image, $transparency);
138    }
139
140    /**
141     * Allocate a transparent color
142     *
143     * @param resource $image
144     * @return int
145     */
146    protected function transparentColor($image)
147    {
148        return imagecolorallocatealpha($image, 0, 0, 0, 127);
149    }
150
151    /**
152     * Allocate a random color
153     *
154     * @param $image
155     * @return int
156     */
157    protected function randomColor($image)
158    {
159        if ($this->ismono) {
160            return imagecolorallocatealpha(
161                $image,
162                $this->monocolor[0],
163                $this->monocolor[1],
164                $this->monocolor[2],
165                $this->rand(0, 96)
166            );
167        }
168        return imagecolorallocate(
169            $image,
170            $this->rand(0, 255),
171            $this->rand(0, 255),
172            $this->rand(0, 255)
173        );
174    }
175
176    /**
177     * Create a transparent image
178     *
179     * @param int $width
180     * @param int $height
181     * @return resource
182     * @throws \Exception
183     */
184    protected function createTransparentImage($width, $height)
185    {
186        $image = @imagecreatetruecolor($width, $height);
187        if (!$image) {
188            throw new \Exception('Missing libgd support');
189        }
190        imagealphablending($image, false);
191        $transparency = $this->transparentColor($image);
192        imagefill($image, 0, 0, $transparency);
193        imagecolordeallocate($image, $transparency);
194        imagesavealpha($image, true);
195        return $image;
196    }
197}
198