identicon-Xplusplus.cn的默认头像

相信用过github的人都会对它分配的默认头像很好奇:简单的颜色搭配,加上积木式的方块拼接,得到了一张与众不同的头像。

github头像

这种头像被称作identicon, Don Park在2007年1月18日首次想出了这个创意。Identicon是Hash值的可视化表示,常见的生成方法是根据IP地址或email地址生成。

github有一个生成这种头像的链接:https://github.com/identicons/xxxxxx.png,把其中的xxxxx换成任意字符串,就可以显示由该字符串生成的头像。

当然,除了这种由小方块组成的identicon,微软论坛和stackoverflow还有雪片状的identicon。stackexchange上有一个叫Jeff的人还做了由彩色背景,彩虹、云彩和独角兽等元素组成的identicon【链接】,样子是这样的,大家感受一下。

草泥马

不知道Jeff是不是中国人,如果是的话,这应该不是独角兽,是“草泥马”,哈哈~

xplusplus.cn的默认头像

由于Xplusplus.cn是IT技术精英的知识分享社区,同时我又是很传统的“字正腔圆”的中国人,我选择了github类型的identicon作为xplusplus.cn的默认头像,效果还是很不错的。 大家可以使用这个链接:

http://xplusplus.cn/image/avatar/xxx.png

来生成任意字符串的identicon,只要把其中的xxx改成目标字符串即可。大家应该猜得到,相同字符串,使用github的链接和xplusplus.cn的链接生成的identicon是不一样的。

生成identicon的算法

我没有从头开始写xplusplus.cn的默认头像算法,而是参考了github上的一个叫benjamin的哥们做得开源项目【https://github.com/yzalis/Identicon】,这个项目节省了我很多时间。由于它的代码的组织方式我不是很喜欢,我重新精简了代码,把核心代码保留在了一个函数里,结果是这样的。

<?php
function generateIdenticon($string, $size = 64, $margin = 0, $backColorArr = null)
{
    if(! $string) return false;
    if(! is_int($size)) return false;
    $pixelRatio = $size / 5;

    $generatedImage = imagecreatetruecolor($pixelRatio * 5 + $margin * 2, $pixelRatio * 5 + $margin * 2);

    if (null === $backColorArr) {
        $background = imagecolorallocate($generatedImage, 0, 0, 0);
        imagecolortransparent($generatedImage, $background);
    } else {
        $background = imagecolorallocate($generatedImage, $backColorArr[0], $backColorArr[1], $backColorArr[2]);
        imagefill($generatedImage, 0, 0, $background);
    }

    $hash = md5($string);

    $arrayOfSquare = array();

    preg_match_all('/(\w)(\w)/', $hash, $chars);
    foreach ($chars[1] as $i => $char) {
        if($i < 15){
            if ($i % 3 == 0) {
                $arrayOfSquare[$i/3][0] = (bool) intval(round(hexdec($char)/10));
                $arrayOfSquare[$i/3][4] = (bool) intval(round(hexdec($char)/10));
            } elseif ($i % 3 == 1) {
                $arrayOfSquare[$i/3][1] = (bool) intval(round(hexdec($char)/10));
                $arrayOfSquare[$i/3][3] = (bool) intval(round(hexdec($char)/10));
            } else {
                $arrayOfSquare[$i/3][2] = (bool) intval(round(hexdec($char)/10));
            }
            ksort($arrayOfSquare[$i/3]);
        }
    }

    $rgbColor = array();

    $rgbColor[0] = hexdec(array_pop($chars[1]))*16;
    $rgbColor[1] = hexdec(array_pop($chars[1]))*16;
    $rgbColor[2] = hexdec(array_pop($chars[1]))*16;

    $gdColor = imagecolorallocate($generatedImage, $rgbColor[0], $rgbColor[1], $rgbColor[2]);

    foreach ($arrayOfSquare as $lineKey => $lineValue) {
        foreach ($lineValue as $colKey => $colValue) {
            if (true === $colValue) {
                imagefilledrectangle($generatedImage,
                $colKey * $pixelRatio + $margin,
                $lineKey * $pixelRatio + $margin,
                ($colKey + 1) * $pixelRatio + $margin,
                ($lineKey + 1) * $pixelRatio + $margin, $gdColor);
            }
        }
    }

    ob_start();
    imagepng($generatedImage);
    $imageData = ob_get_contents();
    ob_end_clean();

    return $imageData;
}

header("Content-Type: image/png");
echo generateIdenticon('Xplusplus.cn', 350, 35, array(240, 240, 240));

从原本的5个文件550多行代码变成一个函数70行代码,当然,我的没有注释,所以接下来我大致介绍一下思路。

首先参数有四个:用来生成identicon的字符串,生成的正方形图片的一个边的像素个数,边框的像素个数和背景色。

我们需要生成的图片是一个5 * 5的方块矩阵组成的,所以先得到每个方块的边长的像素$pixelRatio。

由于图像是左右对称的,所以在生成布尔值矩阵$arrayOfSquare的时候,需要让x轴下标为0和4,1和3上的值相等。

图片的前景色取hash字符串的第0,2,4位,由于md5字符串的每一位都是16进制的一位,所以乘以16刚好可以得到颜色的RGB值。

最后按照布尔值矩阵$arrayOfSquare填充图片即可。

算法中用到了GD的一些函数,不熟悉可以查询相关文档,这里不具体描述。

最后的两行代码使用函数generateIdenticon生成字符串“Xplusplus.cn”的identicon,结果是这样的:

Xplusplus.cn的identicon

嗯,效果不错!

结尾

benjamin的identicon开源项目不支持给identicon添加边框,在计算$arrayOfSquare时也有bug,上述代码解决了这些问题。当然,我已经把这些修复PR给了benjamin,现在直接使用benjamin的代码也没有这些问题了。

Xplusplus.cn使用每个用户的邮件地址来生成identicon,如果前端页面每次展示头像的时候都重新生成一遍identicon,显然开销会比较大,通过设置一些nginx的rewrite规则,可以在第一次访问头像时生成identicon写到文件,然后每次都从文件中取就可以了。具体实现方式,本文就不展开描述了。