This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
define('SVGNS', 'http://www.w3.org/2000/svg');
define('COLOR_EXTRACT_RENDER_SIZE', 128);
define('HARMATTAN_ICON_SIZE', 80);
define('HARMATTAN_FOCAL_SIZE', 56);
function svg2png($svg, $w, $h, $png)
{
$output = array();
$exitcode = -1;
$cmd = sprintf("~/bin/rsvg-convert --format=png --width=%d --height=%d --output=%s %s", $w, $h, escapeshellarg($png), escapeshellarg($svg));
exec($cmd, $output, $exitcode);
return $exitcode == 0 && file_exists($png);
}
function multiarray($d, $k, $n = 0)
{
return array_fill(0, $k, $d == 1 ? $n : multiarray($d - 1, $k, $n));
}
function warn($s)
{
global $warnings;
$warnings[] = $s;
}
function colfilter_none($c)
{
return false;
}
function colfilter_pure($c)
{
return ($c['red'] == 0 && $c['green'] == 0 && $c['blue'] == 0) ||
($c['red'] == 255 && $c['green'] == 255 && $c['blue'] == 255);
}
function colfilter_grey($c)
{
return ($c['red'] == $c['green']) && ($c['green'] == $c['blue']);
}
function colfilter_grey5($c)
{
$r = $c['red'] >> 3;
$g = $c['green'] >> 3;
$b = $c['blue'] >> 3;
return ($r == $g) && ($g == $b);
}
function domcolor_mean($img, $flt)
{
$w = imagesx($img);
$h = imagesy($img);
$t = $w * $h;
$r = 0; $g = 0; $b = 0;
for ($y = 0; $y < $h; $y++) {
for ($x = 0; $x < $w; $x++) {
$c = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if (call_user_func($flt, $c)) continue;
$r += $c['red'];
$g += $c['green'];
$b += $c['blue'];
}
}
$r /= $t; $g /= $t; $b /= $t;
return array($r, $g, $b);
}
function domcolor_mode($img, $flt)
{
$w = imagesx($img);
$h = imagesy($img);
$t = $w * $h;
$f = multiarray(3, 256, 0);
$max_c = false;
$max_f = 0;
for ($y = 0; $y < $h; $y++) {
for ($x = 0; $x < $w; $x++) {
$c = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if (call_user_func($flt, $c)) continue;
$this_f = $f[$c['red']][$c['green']][$c['blue']] += 1;
if ($this_f > $max_f) {
$max_c = $c;
$max_f = $this_f;
}
}
}
if ($max_c)
return array($max_c['red'], $max_c['green'], $max_c['blue']);
else
return false;
}
$domalgos = array('mean', 'mode');
$colfilters = array('none', 'pure', 'grey', 'grey5');
function extract_dominant_color($svg, $algo = 'average', $filter = 'grey')
{
global $domalgos, $colfilters;
$png = '/tmp/hicg-icon-' . uniqid() . '.png';
if (!in_array($algo, $domalgos)) {
warn('Invalid dominant color extraction algorithm');
return false;
}
if (!in_array($filter, $colfilters)) {
warn('Invalid colors to ignore');
return false;
}
if (!svg2png($svg, COLOR_EXTRACT_RENDER_SIZE, COLOR_EXTRACT_RENDER_SIZE, $png)) {
warn('Initial rasterization failed (not an .svg file?)');
return false;
}
$img = imagecreatefrompng($png);
if (!$img) {
warn('Initial rasterization generated incorrect PNG');
return false;
}
$color = call_user_func('domcolor_'. $algo, $img, 'colfilter_'.$filter);
imagedestroy($img);
unlink($png);
if (!$color) {
warn("I failed to get a dominant color. Your icon might be too greyish, so I'm making it grey.");
return array(128, 128, 128);
}
return $color;
}
function clip_color_value($c)
{
if ($c < 0) return 0;
else if ($c > 255) return 255;
return $c;
}
function clip_color($c)
{
return array_map('clip_color_value', $c);
}
function greyscale_color($c)
{
return .299*$c[0] + .587*$c[1] + .114*$c[2];
}
function color2hex($c)
{
return sprintf("%02x%02x%02x", $c[0], $c[1], $c[2]);
}
function get_disabled_color($c)
{
/* Clear and desature a bit. */
$nc = array_map(create_function('$x', 'return $x + 90;'), $c);
$grey = greyscale_color($nc);
$nc = array_map(create_function('$x', 'return $x * 0.8 + ('.$grey.') * 0.2;'), $nc);
return clip_color($nc);
}
function get_pressed_color($c)
{
/* Desaturate and darken. */
$grey = greyscale_color($c);
$nc = array_map(create_function('$x', 'return $x * 0.5 + ('.$grey.') * 0.5;'), $c);
$nc = array_map(create_function('$x', 'return $x - 40;'), $nc);
return clip_color($nc);
}
function get_icon_light_color($c)
{
$nc = array_map(create_function('$x', 'return $x + 60;'), $c);
return clip_color($nc);
}
function get_icon_dark_color($c)
{
$nc = array_map(create_function('$x', 'return $x - 50;'), $c);
return clip_color($nc);
}
function get_svg_attribute($node, $attrib)
{
$v = $node->getAttributeNS(SVGNS, 'svg:' . $attrib);
if (empty($v)) {
$v = $node->getAttribute('svg:' . $attrib);
if (empty($v)) {
$v = $node->getAttribute($attrib);
}
}
return $v;
}
function calc_scale_ratios($w, $h, &$s, &$tx, &$ty)
{
if ($w > $h) {
$s = HARMATTAN_FOCAL_SIZE / $w;
$sw = HARMATTAN_FOCAL_SIZE;
$sh = $s * $h;
} else {
$s = HARMATTAN_FOCAL_SIZE / $h;
$sw = $s * $w;
$sh = HARMATTAN_FOCAL_SIZE;
}
$tx = HARMATTAN_ICON_SIZE / 2 - $sw / 2;
$ty = HARMATTAN_ICON_SIZE / 2 - $sh / 2;
}
function make_icon($src, $template, $dst, $color, $shadow)
{
$srcDoc = new DOMDocument();
$srcDoc->load($src);
$doc = new DOMDocument();
$doc->load($template);
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('svg', SVGNS);
/* Get scaling ratios from source SVG. */
$srcNode = $srcDoc->documentElement;
$w = intval(get_svg_attribute($srcNode, 'width'));
$h = intval(get_svg_attribute($srcNode, 'height'));
calc_scale_ratios($w, $h, $s, $tx, $ty);
/* Set background color */
$bkg = $xpath->query('/svg:svg/svg:g/svg:path[@id="hicg_background"]');
$bkg->item(0)->setAttribute('fill', '#' . color2hex($color));
/* Create the scaling & centering transform. */
$g = $doc->createElement('g');
$g->setAttribute('transform', "translate($tx, $ty) scale($s)");
/* Add drop shadow filter if selected. */
if ($shadow) {
$g->setAttribute('filter', 'url(#hicg_drop_shadow)');
}
foreach ($srcNode->childNodes as $node) {
$n = $doc->importNode($node, TRUE);
$g->appendChild($n);
}
$doc->documentElement->appendChild($g);
$doc->save($dst);
return true;
}
function make_png($src, $dst)
{
if (!svg2png($src, HARMATTAN_ICON_SIZE, HARMATTAN_ICON_SIZE, $dst)) {
warn("I failed to generate a 80x80 PNG.");
return false;
}
return true;
}
$warnings = array();
$loadsample = true;
if (@isset($_POST['send'])) {
$prefix = './files/' . uniqid() . '_';
$srcfile = $prefix . 'src.svg';
if (!move_uploaded_file($_FILES['iconfile']['tmp_name'], $srcfile)) {
die('Upload failed.');
}
$domalgo = $_POST['domalgo'];
$colfilter = $_POST['colfilter'];
$dropshadow = $_POST['dropshadow'] == 'dropshadow';
$iconfile = $prefix . 'icon.svg';
$pngfile = $prefix . 'icon.png';
$domcolor = extract_dominant_color($srcfile, $domalgo, $colfilter);
if ($domcolor) {
$loadsample = false;
} else {
$discolor = get_disabled_color($domcolor);
$prscolor = get_pressed_color($domcolor);
}
if (!$loadsample) {
make_icon($srcfile, 'template.svg', $iconfile, $domcolor, $dropshadow);
make_png($iconfile, $pngfile);
} else {
/* Something went really wrong and we will show the sample, so delete uploaded SVG. */
unlink($srcfile);
}
$srcurl = $srcfile;
$iconurl = $iconfile;
$pngurl = $pngfile;
}
if (php_sapi_name() == 'cli' && $argc >= 2) {
$loadsample = false;
$srcfile = $argv[1];
$domalgo = 'mode';
$dropshadow = true;
$colfilter = 'grey';
$iconfile = 'cliicon.svg';
$pngfile = 'cliicon.png';
$domcolor = extract_dominant_color($srcfile, $domalgo, $colfilter);
make_icon($srcfile, 'template.svg', $iconfile, $domcolor, $dropshadow);
make_png($iconfile, $pngfile);
exit(0);
}
if ($loadsample) {
/* Load some sample data. */
$srcfile = 'samplesrc.svg';
$domalgo = 'mode';
$dropshadow = true;
$colfilter = 'grey';
$iconfile = 'sampleicon.svg';
$pngfile = 'sampleicon.png';
$domcolor = extract_dominant_color($srcfile, $domalgo, $colfilter);
$discolor = get_disabled_color($domcolor);
$prscolor = get_pressed_color($domcolor);
$srcurl = $srcfile;
$iconurl = $iconfile;
$pngurl = $pngfile;
}
echo '';
?>
Harmattan Icon Generator
Warnings
Warning:
Upload your icon
Results
Your icon's dominant color is #
A suggested disabled color is #
A suggested pressed color is #
Download the prerendered 80x80 PNG file. Or a SVG file, if you want to make more changes.
Suggested reading