initial commit

This commit is contained in:
Levi 2014-10-12 23:33:28 -04:00
parent 9e022af117
commit ed65942d22
198 changed files with 292 additions and 2 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*/.idea/
*-assets/

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Levi Lansing
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.

View File

@ -1,2 +1,33 @@
pixel-avatar-generator #Pixel-Style Avatar Generator
====================== ![Avatar Example](graphics/examples/avatar-0.png?raw=true "Avatar Example")
I needed a kid-friendly (G rated) avatar generator, preferably PHP based. Inspired by the randomly generated github avatars, but unable to find a satisfactory pre-made solution, I did what I always do and took on the challenge from scratch. Here's the results of my simple PG-rated pixel-style avatar generator, with billions of possible combinations!
![Avatar Example 1](graphics/examples/avatar-1.png?raw=true "Avatar Example 1")
![Avatar Example 2](graphics/examples/avatar-2.png?raw=true "Avatar Example 2")
![Avatar Example 3](graphics/examples/avatar-3.png?raw=true "Avatar Example 3")
![Avatar Example 4](graphics/examples/avatar-4.png?raw=true "Avatar Example 4")
##Usage
It should be pretty straightforward to use-
```
// Avatar::render($size = 400, $gender = null, $id = null)
Avatar::render(200, 'male', 'some-reusable-identifier');
```
By using an identifier, you can re-generate the identical avatar by sending the same identifier (and gender) later. For my own purposes size is limited to 512 max width/height for performance reasons, but if you plan to generate and cache the avatars, you can go as big as you like!
If you want to use the output rather than render directly to the output stream, use `Avatar::generate` which returns the image resource.
##Photoshop file included
Under `graphics/` you'll find `avatars.psd`, a complete photoshop file that produced the layers using the image assets generator. You can add and remove layers, regenerate the layers and then replace the `images/` folder to use your own set. Just make sure the file names begin with the layer name. include `_m`, `_f`, or `_mf` at the end of the file name to designate the layer for Males, Females, or both respectively.
The generator is set to 20x20px layer sizes. If you create a brand new set with a different resolution you'll want to change the `AVATAR_SIZE` constant in avatar.php.
##More Examples
![Avatar Examples](graphics/examples/avatars.gif?raw=true "Avatar Examples")
Enjoy!
Licensed under the MIT licence. See LICENSE for details.

233
generator/avatars.php Normal file
View File

@ -0,0 +1,233 @@
<?php
/**
* Class Avatar
* a static class for generating avatars from image layers
*/
class Avatar {
const MAX_SIZE = 512;
const AVATAR_SIZE = 20;
private static $layers = ['background', 'skin', 'mouth', 'eyes', 'brow', 'face', 'facewear', 'shirt', 'hair'];
private static $hairColors = [[240,212,83], [62,33,21], [100,23,15], [143,140,137], [112,83,39], [135,71,0], [129,81,57], [self::AVATAR_SIZE, self::AVATAR_SIZE, self::AVATAR_SIZE]];
private static $eyeColors = [[1, 101, 59],[66,79,1],[66,66,66],[39,39,39],[0,82,156],[76,44,4],[0,59,108],[0,93,128],[93,105,29]];
private static $browColor = [99, 54, 32];
/**
* render an avatar as a png straight to the browser
* @param int $size
* @param string $gender
* @param string $id
*/
public static function render($size = 400, $gender = null, $id = null) {
header("Content-type: image/png");
if ($gender != 'male' && $gender != 'female') {
$gender = mt_rand(0,1) ? 'male' : 'female';
}
$avatar = self::generate($size, $gender, $id);
imagepng($avatar);
imagedestroy($avatar);
}
/**
* generate an avatar from the layers.
* @param int $size
* @param string $gender
* @param null $id
* @return resource image
*/
public static function generate($size = 400, $gender = 'male', $id = null) {
$size = max(1,min($size, self::MAX_SIZE));
if (is_null($id)) {
$id = rand(0,getrandmax());
}
if ($id)
srand(base_convert(substr(md5($id), 0, 8), 16, 10));
$layerList = self::getLayerList();
$imgPath = __DIR__ . DIRECTORY_SEPARATOR . 'layers' . DIRECTORY_SEPARATOR;
// create a transparent base avatar image
$avatar = imagecreatetruecolor(self::AVATAR_SIZE,self::AVATAR_SIZE);
imageSaveAlpha($avatar, true);
imagefill($avatar, 0, 0, 0x7f << 24);
// add each layer
$browColor = self::$browColor;
$hairColor = self::$hairColors[rand(0, count(self::$hairColors)-1)];
$eyeColor = self::$eyeColors[rand(0, count(self::$eyeColors)-1)];
$background = null;
foreach (self::$layers as $layer) {
$file = $layerList[$layer][$gender][rand(0, count($layerList[$layer][$gender])-1)];
$img = imagecreatefrompng($imgPath . $file);
if ($layer == 'hair' || ($gender=='male' && $layer=='face')) {
self::recolor($img, $hairColor);
}
if ($layer == 'eyes') {
self::recolor($img, $eyeColor, true);
}
if ($layer == 'brow') {
self::recolor($img, [$browColor[0]+$hairColor[0]/2, $browColor[1]+$hairColor[1]/2, $browColor[2]+$hairColor[2]/2], true);
}
if ($layer == 'background') {
$background = $img;
continue;
}
if (rand(0,1) == 0)
imageflip($img, IMG_FLIP_HORIZONTAL);
imagecopy($avatar, $img, 0, 0, 0, 0, self::AVATAR_SIZE, self::AVATAR_SIZE);
imagedestroy($img);
}
// create the output avatar at full size
$avatarLarge = imagecreatetruecolor($size, $size);
if (!is_null($background))
imagecopyresized($avatarLarge, $background, 0, 0, 0, 0, $size, $size, self::AVATAR_SIZE, self::AVATAR_SIZE);
// add the shadow
$shadow = self::makeShadowLayer($avatar, 110, $size, $size);
$offsetX = ceil(.4/self::AVATAR_SIZE*$size);
$offsetY = ceil(1.3/self::AVATAR_SIZE*$size);
imagecopy($avatarLarge, $shadow, $offsetX, $offsetY, 0, 0, $size, $size);
imagedestroy($shadow);
// add an outline
$outline = self::makeOutlineLayer($avatar, 0x00222222, .1/self::AVATAR_SIZE*$size+.75, $size, $size);
imagecopy($avatarLarge, $outline, 0, 0, 0, 0, $size, $size);
imagedestroy($outline);
imagecopyresized($avatarLarge, $avatar, 0, 0, 0, 0, $size, $size, self::AVATAR_SIZE, self::AVATAR_SIZE);
imagedestroy($avatar);
return $avatarLarge;
}
/**
* recolor a layer using the alpha channel
* @param $img
* @param $newColor
* @param bool $ignoreWhite
*/
private static function recolor($img, $newColor, $ignoreWhite=false) {
for ($x=0; $x<self::AVATAR_SIZE; $x++) {
for ($y=0; $y<self::AVATAR_SIZE; $y++) {
$c = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if ($c['alpha'] < 127 && (!$ignoreWhite || $c['red'] != 255 || $c['green'] != 255 || $c['blue'] != 255)) {
imagesetpixel($img, $x, $y, imagecolorallocatealpha($img, $newColor[0], $newColor[1], $newColor[2], $c['alpha']));
}
}
}
}
/**
* create a semi-transparent black layer to be used as a drop shadow using the alpha chanel
* @param $img
* @param $alpha
* @param $width
* @param $height
* @return resource
*/
private static function makeShadowLayer($img, $alpha, $width, $height) {
$newImg = imagecreatetruecolor(self::AVATAR_SIZE, self::AVATAR_SIZE);
imageSaveAlpha($newImg, true);
imagefill($newImg, 0, 0, 0x7f << 24);
for ($x=0; $x<self::AVATAR_SIZE; $x++) {
for ($y=0; $y<self::AVATAR_SIZE; $y++) {
$c = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if ($c['alpha'] < 100) {
imagesetpixel($newImg, $x, $y, ($alpha & 0x7f) << 24);
}
}
}
$fullSize = imagecreatetruecolor($width, $height);
imageSaveAlpha($fullSize, true);
imagefill($fullSize, 0, 0, 0x7f << 24);
imagecopyresized($fullSize, $newImg, 0, 0, 0, 0, $width, $height, self::AVATAR_SIZE, self::AVATAR_SIZE);
imagedestroy($newImg);
return $fullSize;
}
/**
* draw an outline around the non-transparent pixels
* @param $img
* @param $color
* @param $thickness
* @param $width
* @param $height
* @return resource
*/
private static function makeOutlineLayer($img, $color, $thickness, $width, $height) {
$thickness = (int)$thickness;
$newImg = imagecreatetruecolor(self::AVATAR_SIZE, self::AVATAR_SIZE);
imageSaveAlpha($newImg, true);
imagefill($newImg, 0, 0, 0x7f << 24);
for ($x=0; $x<self::AVATAR_SIZE; $x++) {
for ($y=0; $y<self::AVATAR_SIZE; $y++) {
$c = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if ($c['alpha'] < 100) {
imagesetpixel($newImg, $x, $y, $color);
}
}
}
$fullSize = imagecreatetruecolor($width, $height);
imageSaveAlpha($fullSize, true);
imagefill($fullSize, 0, 0, 0x7f << 24);
imagecopyresized($fullSize, $newImg, $thickness, $thickness, 0, 0, $width, $height, self::AVATAR_SIZE, self::AVATAR_SIZE);
imagecopy($fullSize, $fullSize, 0, -$thickness*2, 0, 0, $width, $height);
imagecopy($fullSize, $fullSize, -$thickness*2, 0, 0, 0, $width, $height);
imagedestroy($newImg);
return $fullSize;
}
/**
* generate the list of layer files by layer name and gender
* @return array
*/
private static function getLayerList() {
$list = array_fill_keys(self::$layers, null);
$list = array_map(function() { return ['male' => [], 'female'=>[]]; }, $list);
foreach (scandir(__DIR__ . DIRECTORY_SEPARATOR . 'layers') as $file) {
$layer = self::findLayer($file);
if ($layer) {
if (self::isForMale($file))
$list[$layer]['male'][] = $file;
if (self::isForFemale($file))
$list[$layer]['female'][] = $file;
}
}
return $list;
}
/**
* determine the layer name from a file name
* @param $fileName
* @return null|string
*/
private static function findLayer($fileName) {
$found = '';
foreach (self::$layers as $layer) {
if (substr_compare($layer, $fileName, 0, strlen($layer), true) == 0) {
if (strlen($layer) > strlen($found))
$found = $layer;
}
}
return $found == '' ? null : $found;
}
/**
* determine if the file is meant for male avatars
* @param $fileName
* @return bool
*/
private static function isForMale($fileName) {
return (preg_match('/_f?mf?\.png/', $fileName) > 0 || preg_match('/_[mf]+\.png/', $fileName) == 0);
}
/**
* determine if the file is meant for female avatars
* @param $fileName
* @return bool
*/
private static function isForFemale($fileName) {
return (preg_match('/_m?fm?\.png/', $fileName) > 0 || preg_match('/_[mf]+\.png/', $fileName) == 0);
}
}

4
generator/index.php Normal file
View File

@ -0,0 +1,4 @@
<?php
require 'avatars.php';
Avatar::render();
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

BIN
generator/layers/eyes1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
generator/layers/eyes2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

BIN
generator/layers/eyes3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

BIN
generator/layers/eyes4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

BIN
generator/layers/eyes4a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

BIN
generator/layers/eyes4b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

BIN
generator/layers/eyes4c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

BIN
generator/layers/eyes5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

BIN
generator/layers/eyes5a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

BIN
generator/layers/eyes6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
generator/layers/eyes7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Some files were not shown because too many files have changed in this diff Show More