diff options
Diffstat (limited to 'index.php')
-rw-r--r-- | index.php | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/index.php b/index.php new file mode 100644 index 0000000..79a4428 --- /dev/null +++ b/index.php @@ -0,0 +1,390 @@ +<?php + +/* + hicg -- Harmattan Icon/Color Generator + Copyright (C) 2011 Javier S. Pedro <maemo@javispedro.com> + + 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 <http://www.gnu.org/licenses/>. +*/ + +define('SVGNS', 'http://www.w3.org/2000/svg'); + +define('COLOR_EXTRACT_RENDER_SIZE', 128); +define('HARMATTAN_ICON_SIZE', 80); +define('HARMATTAN_FOCAL_SIZE', 54); + +function svg2png($svg, $w, $h, $png) +{ + $output = array(); + $exitcode = -1; + exec(sprintf("~/bin/qsvg %s %d %d %s", escapeshellarg($svg), $w, $h, escapeshellarg($png)),$output,$exitcode); + return $exitcode == 0; +} + +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.png'; + if (!in_array($algo, $domalgos)) { + return false; + } + if (!in_array($filter, $colfilters)) { + return false; + } + if (!svg2png($svg, COLOR_EXTRACT_RENDER_SIZE, COLOR_EXTRACT_RENDER_SIZE, $png)) { + return false; + } + $img = imagecreatefrompng($png); + if (!$img) { + return false; + } + $color = call_user_func('domcolor_'. $algo, $img, 'colfilter_'.$filter); + imagedestroy($img); + 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) +{ + $srcDoc = new DOMDocument(); + $srcDoc->load($src); + $doc = new DOMDocument(); + $doc->load($template); + + /* 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); + + /* Fix gradient colors. */ + $grd = $doc->getElementsByTagName('stop'); + $grd->item(0)->setAttribute('style', 'stop-color:#' . color2hex(get_icon_light_color($color)) . ';stop-opacity:1'); + $grd->item(1)->setAttribute('style', 'stop-color:#' . color2hex(get_icon_dark_color($color)) . ';stop-opacity:1'); + + /* Create the scaling & centering transform. */ + $g = $doc->createElement('g'); + $g->setAttribute('transform', "translate($tx, $ty) scale($s)"); + + foreach ($srcNode->childNodes as $node) { + $n = $doc->importNode($node, TRUE); + $g->appendChild($n); + } + + $doc->documentElement->appendChild($g); + + $doc->save($dst); +} + +function make_png($src, $dst) +{ + if (!svg2png($src, HARMATTAN_ICON_SIZE, HARMATTAN_ICON_SIZE, $dst)) { + warn("I failed to generate a 80x80 PNG."); + } +} + +$warnings = array(); + +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']; + $iconfile = $prefix . 'icon.svg'; + $pngfile = $prefix . 'icon.png'; + + $domcolor = extract_dominant_color($srcfile, $domalgo, $colfilter); + if (!$domcolor) { + warn("I failed to get a dominant color. Your icon might be too greyish."); + $domcolor = array(0, 0, 0); + $discolor = array(0, 0, 0); + $prscolor = array(0, 0, 0); + } else { + $discolor = get_disabled_color($domcolor); + $prscolor = get_pressed_color($domcolor); + } + + make_icon($srcfile, 'template.svg', $iconfile, $domcolor); + make_png($iconfile, $pngfile); + + $srcurl = $srcfile; + $iconurl = $iconfile; + $pngurl = $pngfile; +} else { + /* Load some sample data. */ + $srcfile = 'samplesrc.svg'; + $domalgo = 'mode'; + $colfilter = 'grey5'; + $iconfile = 'sampleicon.svg'; + $pngfile = 'sampleicon.png'; + + $domcolor = extract_dominant_color($srcfile, $domalgo, $colfilter); + $discolor = get_disabled_color($domcolor); + $prscolor = get_pressed_color($domcolor); + + /* This should not get into production. ;) */ + make_icon($srcfile, 'template.svg', $iconfile, $domcolor); + make_png($iconfile, $pngfile); + + $srcurl = $srcfile; + $iconurl = $iconfile; + $pngurl = $pngfile; +} + +echo '<?xml version="1.0" encoding="utf-8" ?>'; +?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<title>Harmattan Icon Generator</title> +<link rel="stylesheet" type="text/css" href="style.css" /> +</head> +<body> +<?php if (!empty($warnings)): ?> +<h1>Warnings</h1> +<?php foreach ($warnings as $w): ?> + <p><strong>Warning:</strong> <?php echo $w; ?></p> +<?php endforeach; ?> +<?php endif; ?> +<h1>Upload your icon</h1> +<form enctype="multipart/form-data" action="." method="post"> + <fieldset> + <legend>Your application's current icon file</legend> + <label>SVG icon file: + <input type="file" name="iconfile" size="60" /> + </label> + </fieldset> + <fieldset> + <legend>Algorithm for dominant color extraction</legend> + <label><input type="radio" name="domalgo" value="mean" />Mean / Average</label> + <label><input type="radio" name="domalgo" value="mode" checked="true" />Mode</label> + </fieldset> + <fieldset> + <legend>Colors to ignore</legend> + <label><input type="radio" name="colfilter" value="none" />None</label> + <label><input type="radio" name="colfilter" value="pure" />Pure white/blacks</label> + <label><input type="radio" name="colfilter" value="grey" checked="true" />Pure greys</label> + <label><input type="radio" name="colfilter" value="grey5" />Greys at 5bpp per channel</label> + </fieldset> + <fieldset> + <legend>Drop shadow</legend> + <label><input type="radio" name="dropshadow" value="none" />None</label> + <label><input type="radio" name="dropshadow" value="std" />Standard</label> + </fieldset> + <div class="buttons"> + <input type="submit" name="send" value="Submit" /> + </div> +</form> +<h1>Results</h1> +<div id="uploaded"> + <p>You uploaded:</p> + <object type="image/svg+xml" data="<?php echo $srcurl; ?>" width="200" height="200"></object> +</div> +<div id="results"> + <div id="yourcolor"> + <p>Your icon's dominant color is <span class="color">#<?php echo color2hex($domcolor); ?></span></p> + <div class="color" style="background-color: #<?php echo color2hex($domcolor); ?>;"></div> + </div> + <div id="discolor"> + <p>A suggested disabled color is <span class="color">#<?php echo color2hex($discolor); ?></span></p> + <div class="color" style="background-color: #<?php echo color2hex($discolor); ?>;"></div> + </div> + <div id="prscolor"> + <p>A suggested pressed color is <span class="color">#<?php echo color2hex($prscolor); ?></span></p> + <div class="color" style="background-color: #<?php echo color2hex($prscolor); ?>;"></div> + </div> + <div id="youricon"> + <p>Harmattan style icon:</p> + <div> + <div class="icon"> + <object type="image/svg+xml" data="<?php echo $iconurl; ?>" width="80" height="80"></object> + <p>YourApp</p> + </div> + </div> + </div> + <p>Download <a href="<?php echo $iconurl; ?>">SVG file</a>. Or a 80x80 <a href="<?php echo $pngurl; ?>">PNG file</a>.</p> +</div> +<h1>Suggested reading</h1> +<ul> + <li><a href="http://www.developer.nokia.com/swipe/ux/pages/Colour.html">Nokia N9 UX Color Guidelines</a></li> + <li><a href="http://www.developer.nokia.com/swipe/ux/pages/Icons.html">Nokia N9 UX Icon Guidelines</a></li> +</ul> +</body> +</html> |