-
-
Save joachimdoerr/9f67b58b0629940af416e56d55f1fa3c to your computer and use it in GitHub Desktop.
PHP implementation of Don Park Identicon algorithm with additional shapes and variations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Identicon++ | |
* | |
* @description: PHP implementation of Don Park Identicon algorithm with additional shapes and variations. | |
* | |
* @author: Damien "Mistic" Sorel - http://strangeplanet.fr | |
* @license: MIT | |
* @version: 2.0.0 | |
* @date: 21/05/2015 | |
* | |
* @usage: Init Identicon\IdenticonPlus with an hash and an optional array of options | |
* - size : image size (between 8 and 512, default 128) | |
* - colors : number of colors (1 or 2, default 2) | |
* - background : 6 hex background color used for antialiasing (default FFFFFF) | |
* | |
* @example: direct render | |
* > (new IdenticonPlus($_GET['hash'], $_GET))->display(); | |
* | |
* @example: get image ressource | |
* > $img = (new IdenticonPlus($hash))->render(); | |
* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2013-2015 Damien "Mistic" Sorel | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files (the | |
* 'Software'), to deal in the Software without restriction, including | |
* without limitation the rights to use, copy, modify, merge, publish, | |
* distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to | |
* the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
* | |
*/ | |
namespace Identicon; | |
class IdenticonPlus | |
{ | |
/** | |
* Shapes for corners and sides | |
*/ | |
static $SHAPES1; | |
/** | |
* Shapes for center | |
*/ | |
static $SHAPES2; | |
/** | |
* User hash | |
* @var string | |
*/ | |
public $hash = null; | |
/** | |
* Image size (between 8 and 512) | |
* @var int | |
*/ | |
public $size = 128; | |
/** | |
* Number of colors (1 or 2) | |
* @var int | |
*/ | |
public $colors = 2; | |
/** | |
* Background color (6-hex without #) | |
* @var string | |
*/ | |
public $background = 'FFFFFF'; | |
/** | |
* Storage of the image | |
* @var resource | |
*/ | |
private $_img = null; | |
/** | |
* class constructor | |
* @param string $hash | |
* @param array $options | |
*/ | |
public function __construct($hash=null, $options=array()) | |
{ | |
$this->hash = $hash; | |
if (!$this->hash) | |
{ | |
$this->hash = md5(uniqid(time(), true)); | |
} | |
// we need 21 chars (84 bits) | |
$this->hash = str_pad($this->hash, 21, $this->hash); | |
// size is between 8 and 512 | |
if (array_key_exists('size', $options)) | |
{ | |
$this->size = max(8, min(intval($options['size']), 512)); | |
} | |
// colors is 1 or 2 | |
if (array_key_exists('colors', $options)) | |
{ | |
$this->colors = max(1, min(intval($options['colors']), 2)); | |
} | |
// background | |
if (array_key_exists('background', $options)) | |
{ | |
$this->background = $options['background']; | |
} | |
} | |
/** | |
* Render the squareicon and returns the image resource | |
* @return resource | |
*/ | |
public function render() | |
{ | |
if ($this->_img != null) | |
{ | |
return $this->_img; | |
} | |
if (!is_array($this->background)) | |
{ | |
$this->background = sscanf($this->background, '%2x%2x%2x'); | |
} | |
## PARSE HASH | |
// sprite shapes (2 chars for each, 256 possibilities, modulated to X possibilities) | |
$shapes = array( | |
'corner' => hexdec(substr($this->hash, 0, 2)) % count(self::$SHAPES1), | |
'side' => hexdec(substr($this->hash, 2, 2)) % count(self::$SHAPES1), | |
'center' => hexdec(substr($this->hash, 4, 2)) % count(self::$SHAPES2), | |
); | |
// sprites rotations (1 char for each, 16 possibilities, truncated to 4 possibilities) | |
$rots = array( | |
'corner' => hexdec(substr($this->hash, 6, 1)) & 3, | |
'side' => hexdec(substr($this->hash, 7, 1)) & 3, | |
); | |
// pattern variation (one bit for each sprite) + center sprite background (one bit) | |
$vars = array(); | |
$tmp = decbin(hexdec(substr($this->hash, 8, 1))); | |
$tmp = str_pad($tmp, 4, '0', STR_PAD_LEFT); | |
list($vars['corner'], $vars['side'], $vars['center'], $center_bg) = str_split($tmp); | |
$colors = array(); | |
// corner sprite color (2 chars for each, 24 bits) | |
$colors['corner'] = array( | |
hexdec(substr($this->hash, 9, 2)), | |
hexdec(substr($this->hash, 11, 2)), | |
hexdec(substr($this->hash, 13, 2)), | |
); | |
// shift color if same has background | |
if ($colors['corner'][0]==$this->background[0] && $colors['corner'][1]==$this->background[1] && $colors['corner'][2]==$this->background[2]) | |
{ | |
$colors['corner'][0]+= $colors['corner'][0]==255 ? -1 : +1; | |
$colors['corner'][1]+= $colors['corner'][1]==255 ? -1 : +1; | |
$colors['corner'][2]+= $colors['corner'][2]==255 ? -1 : +1; | |
} | |
// side sprite color (2 chars for each, 24 bits) | |
if ($this->colors == 2) | |
{ | |
$colors['side'] = array( | |
hexdec(substr($this->hash, 15, 2)), | |
hexdec(substr($this->hash, 17, 2)), | |
hexdec(substr($this->hash, 19, 2)), | |
); | |
// shift color if same has background | |
if ($colors['side'][0]==$this->background[0] && $colors['side'][1]==$this->background[1] && $colors['side'][2]==$this->background[2]) | |
{ | |
$colors['side'][0]+= $colors['side'][0]==255 ? -1 : +1; | |
$colors['side'][1]+= $colors['side'][1]==255 ? -1 : +1; | |
$colors['side'][2]+= $colors['side'][2]==255 ? -1 : +1; | |
} | |
} | |
else | |
{ | |
$colors['side'] = $colors['corner']; | |
} | |
## RENDER IDENTICON | |
// start with blank 3x3 image | |
$this->_img = imagecreatetruecolor($this->size*3, $this->size*3); | |
imageantialias($this->_img, true); | |
// assign white as background | |
$bg = imagecolorallocate($this->_img, $this->background[0], $this->background[1], $this->background[2]); | |
imagefilledrectangle($this->_img, 0, 0, $this->size, $this->size, $bg); | |
// generate corner sprites | |
$corner = $this->getsprite($shapes['corner'], $colors['corner'], $rots['corner'], $vars['corner']); | |
imagecopy($this->_img, $corner, 0, 0, 0, 0, $this->size, $this->size); | |
$corner = imagerotate($corner, 90, 0); | |
imagecopy($this->_img, $corner, 0, $this->size*2, 0, 0, $this->size, $this->size); | |
$corner = imagerotate($corner, 90, 0); | |
imagecopy($this->_img, $corner, $this->size*2, $this->size*2, 0, 0, $this->size, $this->size); | |
$corner = imagerotate($corner, 90, 0); | |
imagecopy($this->_img, $corner, $this->size*2, 0, 0, 0, $this->size, $this->size); | |
// generate side sprites | |
$side = $this->getsprite($shapes['side'], $colors['side'], $rots['side'], $vars['side']); | |
imagecopy($this->_img, $side, $this->size, 0, 0, 0, $this->size, $this->size); | |
$side = imagerotate($side, 90, 0); | |
imagecopy($this->_img, $side, 0, $this->size, 0, 0, $this->size, $this->size); | |
$side = imagerotate($side, 90, 0); | |
imagecopy($this->_img, $side, $this->size, $this->size*2, 0, 0, $this->size, $this->size); | |
$side = imagerotate($side, 90, 0); | |
imagecopy($this->_img, $side, $this->size*2, $this->size, 0, 0, $this->size, $this->size); | |
// generate center sprite | |
$center = $this->getcenter($shapes['center'], $colors['corner'], $colors['side'], $center_bg, $vars['center']); | |
imagecopy($this->_img, $center, $this->size, $this->size, 0, 0, $this->size, $this->size); | |
## GENERATE FINAL IMAGE | |
// create blank image according to specified dimensions | |
$resized = imagecreatetruecolor($this->size, $this->size); | |
imageantialias($resized, true); | |
// assign white as background | |
$bg = imagecolorallocate($resized, $this->background[0], $this->background[1], $this->background[2]); | |
// resize identicon according to specification | |
imagecopyresampled($resized, $this->_img, 0, 0, 0, 0, $this->size, $this->size, $this->size*3, $this->size*3); | |
// make white transparent | |
imagecolortransparent($resized, $bg); | |
imagedestroy($this->_img); | |
$this->_img = $resized; | |
return $this->_img; | |
} | |
/** | |
* Display all possible shapes | |
*/ | |
public static function reference() | |
{ | |
// init | |
$total1 = count(self::$SHAPES1); | |
$total2 = count(self::$SHAPES2); | |
$cols = 8; | |
$lines = ceil($total1/$cols) + ceil($total2/$cols) + 2; | |
$inst = new self(null, array( | |
'size' => 32 | |
)); | |
$inst->background = array(255,255,255); | |
$inst->color = array(80,80,80); | |
// create image | |
$img = imagecreatetruecolor($inst->size*$cols, $inst->size*$lines); | |
$bg = imagecolorallocate($img, $inst->background[0], $inst->background[1], $inst->background[2]); | |
$fg = imagecolorallocate($img, 0, 0, 0); | |
imagefilledrectangle($img, 0, 0, $inst->size*$cols, $inst->size*$lines, $bg); | |
// draw standard shapes | |
imagestring($img, 3, 10, 10, 'Sides and corners shapes', $fg); | |
$x = 0; $y = 1; | |
for ($i=0; $i<$total1; $i++, $x++) { | |
if ($x == 8) | |
{ | |
$x = 0; | |
$y++; | |
} | |
$sprite = $inst->getsprite($i, $inst->color, 0, 0); | |
imagecopy($img, $sprite, $x*$inst->size, $y*$inst->size, 0, 0, $inst->size, $inst->size); | |
} | |
// draw center shapes | |
imagestring($img, 3, 10, ($y+1)*$inst->size+10, 'Center shapes', $fg); | |
$x = 0; $y+=2; | |
for ($i=0; $i<$total2; $i++, $x++) { | |
if ($x == 8) | |
{ | |
$x = 0; | |
$y++; | |
} | |
$sprite = $inst->getcenter($i, $inst->color, $inst->color, 0, 0); | |
imagecopy($img, $sprite, $x*$inst->size, $y*$inst->size, 0, 0, $inst->size, $inst->size); | |
} | |
// draw grid | |
for ($i=1; $i<$cols; $i++) | |
{ | |
imageline($img, $i*$inst->size, $inst->size, $i*$inst->size, (ceil($total1/$cols)+1)*$inst->size, $fg); | |
imageline($img, $i*$inst->size, (ceil($total1/$cols)+2)*$inst->size, $i*$inst->size, $lines*$inst->size, $fg); | |
} | |
for ($i=1; $i<$lines; $i++) | |
{ | |
imageline($img, 0, $i*$inst->size, $cols*$inst->size, $i*$inst->size, $fg); | |
} | |
// render | |
header('Content-Type: image/png'); | |
imagepng($img); | |
} | |
/** | |
* Display the squareicon to the browser | |
*/ | |
public function display() | |
{ | |
header('Content-Type: image/png'); | |
imagepng($this->render()); | |
} | |
/** | |
* Destroy the internal resource storage | |
*/ | |
public function destroy() | |
{ | |
if ($this->_img != null) | |
{ | |
imagedestroy($this->_img); | |
$this->_img = null; | |
} | |
} | |
/** | |
* Callback for map resize | |
*/ | |
private function resize($s) | |
{ | |
return $s*$this->size; | |
} | |
/** | |
* Generate sprite for corners and sides | |
* @param int $shape | |
* @param int[] $color | |
* @param int $rotation | |
* @param int $var | |
*/ | |
private function getsprite($shape, $color, $rotation, $var) | |
{ | |
// create sprite image | |
$sprite = imagecreatetruecolor($this->size, $this->size); | |
imageantialias($sprite, true); | |
// assign colors | |
$c[0] = imagecolorallocate($sprite, $this->background[0], $this->background[1], $this->background[2]); | |
$c[1] = imagecolorallocate($sprite, $color[0], $color[1], $color[2]); | |
// draw background | |
imagefilledrectangle($sprite, 0, 0, $this->size, $this->size, $c[!!$var]); | |
// draw shape | |
$this->drawshape($sprite, self::$SHAPES1[$shape], $c[!$var]); | |
// rotate sprite | |
if ($rotation!=0) | |
{ | |
$sprite = imagerotate($sprite, 90*$rotation, 0); | |
} | |
return $sprite; | |
} | |
/** | |
* Generate sprite for center block | |
* @param int $shape | |
* @param int[] $color1 | |
* @param int[] $color2 | |
* @param int $usebg | |
* @param int $var | |
*/ | |
private function getcenter($shape, $color1, $color2, $usebg, $var) | |
{ | |
// create sprite image | |
$sprite = imagecreatetruecolor($this->size, $this->size); | |
imageantialias($sprite, true); | |
// assign colors | |
if ($usebg>0 && (abs($color1[0]-$color2[0])>127 || abs($color1[1]-$color2[1])>127 || abs($color1[2]-$color2[2])>127)) | |
{ // make sure there's enough contrast before we use background color of side sprite | |
$c[0] = imagecolorallocate($sprite, $color2[0], $color2[1], $color2[2]); | |
} | |
else | |
{ | |
$c[0] = imagecolorallocate($sprite, $this->background[0], $this->background[1], $this->background[2]); | |
} | |
$c[1] = imagecolorallocate($sprite, $color1[0], $color1[1], $color1[2]); | |
// draw background | |
imagefilledrectangle($sprite, 0, 0, $this->size, $this->size, $c[!!$var]); | |
// draw shape | |
$this->drawshape($sprite, self::$SHAPES2[$shape], $c[!$var]); | |
return $sprite; | |
} | |
/** | |
* Draw components of a shape into a sprite | |
* @param resource $sprite | |
* @param array $shape | |
* @param int $color | |
*/ | |
private function drawshape($sprite, $shape, $color) | |
{ | |
if (!is_array($shape[0])) | |
{ | |
$shape = array($shape); | |
} | |
foreach ($shape as $subshape) | |
{ | |
if ($subshape[0] === 'c') | |
{ | |
imagefilledellipse($sprite, $this->size*$subshape[2], $this->size*$subshape[3], $this->size*$subshape[1], $this->size*$subshape[1], $color); | |
} | |
else | |
{ | |
$subshape = array_map(array($this, 'resize'), $subshape); | |
imagefilledpolygon($sprite, $subshape, count($subshape)/2, $color); | |
} | |
} | |
} | |
} | |
IdenticonPlus::$SHAPES1 = array( | |
// triangle | |
array(0.5,1,1,0,1,1), | |
// small triangle | |
array(0,1,0.5,0.5,0.5,1), | |
// parallelogram | |
array(0.5,0,1,0,0.5,1,0,1), | |
// chevron | |
array(0,0,0.5,0,1,0.5,0.5,1,0,1,0.5,0.5), | |
// ribbon | |
array(0,0.5,0.5,0,1,0.5,0.5,1,0.5,0.5), | |
// horseshoe | |
array(0.5,0,1,0.5,0.5,1,0,0.5,0.3,0.5,0.5,0.71,0.71,0.5,0.5,0.3), | |
// mouse ears | |
array(0.5,0,1,0,1,1,0.5,1,1,0.5), | |
// trough | |
array(0,0.5,0.5,1,1,0.5,0.5,0,1,0,1,1,0,1), | |
// sails | |
array(0,0.5,1,0,1,1,0,1,1,0.5), | |
// fins | |
array(1,0,1,1,0.5,1,1,0.5,0.5,0.5), | |
// sail & fin | |
array(1,0,1,1,0,1,1,0.5,0.5,0.5), | |
// fish | |
array(0.5,0,0.5,1,1,1,1,0.5,0,0.5), | |
// tiles couples | |
array(0,0,0,0.5,0.5,0,0.5,0.5,0,0.5,0.5,1,0.5,0.5,1,0.5,0.5,1,1,1,1,0.5,0.5,0), | |
// kite | |
array(0,0,1,0,0.5,0.5,1,0.5,0.5,1,0.5,0.5,0,1), | |
// rays | |
array(0.5,0,1,0,1,1,0.5,1,1,0.75,0.5,0.5,1,0.25), | |
// double rhombus | |
array(0.5,0,0.5,1,1,0.5,1,0,0,1,0,0.5), | |
// radioactive | |
array(0.5,0,1,0,0,1,0,0.5,1,0.5,0.5,1), | |
// tiles | |
array(0,0,1,0,0,1,0,0.5,1,0.5,0.5,1,0.5,0,0,0.5), | |
// hourglass | |
array(0,0,1,1,0,1,1,0), | |
// large corner | |
array(0,0,1,0,0,1), | |
// arrow | |
array(0.5,0,1,0.5,0,1), | |
// beak | |
array(0,1,0.5,0,1,0,1,0.5), | |
// diamond | |
array(0.5,0,0.75,0.5,0.5,1,0.25,0.5), | |
// pyramid | |
array(0,0.5,1,0.5,0.5,1), | |
// pyramid 2 | |
array(0,0,1,0,0.5,0.5), | |
// double pyramid | |
array(array(0,0.5,1,0.5,0.5,1),array(0,0,1,0,0.5,0.5)), | |
// not a pyramid | |
array(0.5,0,1,1,0,1), | |
// not an empty pyramid | |
array(0,0,1,0,0.5,1,0,0,0.30,0.20,0.5,0.6,0.7,0.20,0.30,0.20), | |
// big arrow | |
array(0,0,0.5,0.5,1,0,0.5,1), | |
// triforce | |
array(0,0,1,0,1,1,0,1,1,0.5,0.5,0.25,0.5,0.75,0,0.5,0.5,0.25), | |
// spikes 1 | |
array(0,0,1,0,0.75,0.5,0.5,0,0.25,0.5), | |
// spikes 2 | |
array(array(0,0,0.5,0,0.25,0.5),array(1,1,0.5,1,0.75,0.5)), | |
// spikes 3 | |
array(array(0,0,0.5,0,0.25,0.5),array(1,0,1,0.5,0.5,0.25)), | |
// spikes 4 | |
array(array(0,0,0.5,0,0.25,0.5),array(1,0,1,0.5,0.5,0.25),array(0,0.5,0.5,0.75,0,1)), | |
// big spikes | |
array(0,0,1,0,0.75,1,0.5,0,0.25,1), | |
// big spikes 2 | |
array(array(0,0,0.5,0,0.25,1),array(0.5,1,0.75,0,1,1)), | |
// two diamonds | |
array(0.5,0,0.75,0.25,0.25,0.75,0.5,1,0.75,0.75,0.25,0.25), | |
// tartan | |
array(0,0,1,0,0,0.5,1,1,0,1,1,0.5), | |
// ruby | |
array(0.50,0,1,0,1,0.5,0.5,1,0,1,0,0.5), | |
// geneva space monument | |
array(1,0,0.5,1,0,1), | |
// xbox | |
array(array(0,0,1,0.5,1,1),array(1,0,0,1,0,0.5)), | |
// doors | |
array(array(0,0,1,0,0.5,0.25),array(0,1,0.5,0.75,1,1)), | |
// big checker | |
array(0,0,0.5,0,0.5,1,1,1,1,0.5,0,0.5), | |
// one quarter | |
array(0,0,0.5,0,0.5,0.5,0,0.5), | |
// half fill | |
array(0,0,1,0,1,0.5,0,0.5), | |
// chicane | |
array(0.5,0,1,0,1,0.5,0.5,0.75,0.5,1,0,1,0,0.5,0.5,0.25), | |
// chicane 2 | |
array(0,0,1,1,1,0.5,0.75,0.25,0.25,0.75,0,0.5), | |
); | |
IdenticonPlus::$SHAPES2 = array( | |
// circle | |
array('c', 0.9, 0.5, 0.5), | |
// small circle | |
array('c', 0.5, 0.5, 0.5), | |
// 4 circles | |
array(array('c', 0.4, 0.25, 0.25), array('c', 0.4, 0.25, 0.75), array('c', 0.4, 0.75, 0.25), array('c', 0.4, 0.75, 0.75)), | |
// square | |
array(0,0,1,0,1,1,0,1), | |
// medium square | |
array(0.25,0.25,0.75,0.25,0.75,0.75,0.25,0.75), | |
// small square | |
array(0.33,0.33,0.67,0.33,0.67,0.67,0.33,0.67), | |
// checkerboard | |
array(0,0,0.33,0,0.33,1,0,1,0,0.67,1,0.67,1,1,0.67,1,0.67,0,1,0,1,0.33,0,0.33), | |
// switzerland | |
array(0.33,0,0.67,0,0.67,0.33,1,0.33,1,0.67,0.67,0.67,0.67,1,0.33,1,0.33,0.67,0,0.67,0,0.33,0.33,0.33), | |
// diamond | |
array(0.5,0,1,0.5,0.5,1,0,0.5), | |
// medium diamond | |
array(0.5,0.15,0.855,0.5,0.5,0.855,0.15,0.5), | |
// small diamond | |
array(0.5,0.25,0.75,0.5,0.5,0.75,0.25,0.5), | |
// empty diamond | |
array(0.5,0,1,0.5,0.5,1,0,0.5,0.5,0,0.5,0.25,0.25,0.5,0.5,0.75,0.75,0.5,0.5,0.25), | |
// empty diamond 2 | |
array(0.5,0,1,0.5,0.5,1,0,0.5,0.5,0,0.5,0.33,0.33,0.33,0.33,0.67,0.67,0.67,0.67,0.33,0.5,0.33), | |
// cross | |
array(array(0,0.25,1,0.75,1,0.25,0,0.75),array(0.75,0,0.25,1,0.75,1,0.25,0)), | |
// morning star | |
array(0,0,0.5,0.25,1,0,0.75,0.5,1,1,0.5,0.75,0,1,0.25,0.5), | |
// night star | |
array(0.5,0,0.67,0.33,1,0.5,0.67,0.67,0.5,1,0.33,0.67,0,0.5,0.33,0.33), | |
// empty star | |
array(0,0,0.5,0.25,1,0,0.75,0.5,1,1,0.5,0.75,0,1,0.25,0.5,0.5,0.75,0.75,0.5,0.5,0.25,0.25,0.5), | |
// spikes | |
array(array(0,0,0.5,0,0.25,0.5),array(1,0,1,0.5,0.5,0.25),array(0,0.5,0.5,0.75,0,1),array(0.5,1,0.75,0.5,1,1)), | |
// revert spikes | |
array(array(0.5,0,1,0.25,0.5,0.5),array(1,0.5,0.75,1,0.5,0.5),array(0.5,1,0,0.75,0.5,0.5),array(0,0.5,0.25,0,0.5,0.5)), | |
// cross 2 | |
array(0,0,0.21,0,0.5,0.3,0.8,0,1,0,1,0.2,0.7,0.5,1,0.81,1,1,0.81,1,0.5,0.7,0.2,1,0,1,0,0.8,0.3,0.5,0,0.21), | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment