Skip to content

Instantly share code, notes, and snippets.

@joachimdoerr
Forked from mistic100/IdenticonPlus.php
Created February 18, 2019 07:33
Show Gist options
  • Save joachimdoerr/9f67b58b0629940af416e56d55f1fa3c to your computer and use it in GitHub Desktop.
Save joachimdoerr/9f67b58b0629940af416e56d55f1fa3c to your computer and use it in GitHub Desktop.
PHP implementation of Don Park Identicon algorithm with additional shapes and variations
<?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