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