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

Your application's current icon file
Algorithm for dominant color extraction
Colors to ignore
Drop shadow

Results

You uploaded:

Your icon's dominant color is #

A suggested disabled color is #

A suggested pressed color is #

Harmattan style icon:

YourApp

Download the prerendered 80x80 PNG file. Or a SVG file, if you want to make more changes.

Suggested reading