diff options
-rw-r--r-- | index.php | 390 | ||||
-rw-r--r-- | qsvg.cc | 59 | ||||
-rw-r--r-- | style.css | 78 | ||||
-rw-r--r-- | template.svg | 12 |
4 files changed, 539 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> @@ -0,0 +1,59 @@ +#include <iostream> +#include <QtCore/QCoreApplication> +#include <QImage> +#include <QSvgRenderer> +#include <QPainter> + +/* + 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/>. +*/ + +using std::cerr; +using std::endl; + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + QStringList args = a.arguments(); + if (args.size() != 5) { + cerr << "usage: qsvg <file.svg> <width> <height> <file.png>" << endl; + return 1; + } + + QSvgRenderer r(args[1]); + + if (!r.isValid()) { + cerr << "Could not open source file." << endl; + return 1; + } + + int w, h; + w = args[2].toInt(); + h = args[3].toInt(); + + QImage i(w, h, QImage::Format_ARGB32); + i.fill(QColor(Qt::transparent).rgba()); + + QPainter p(&i); + r.render(&p); + + if (!i.save(args[4], "png")) { + cerr << "Could not save to destination file." << endl; + } + + return 0; +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..fddeb46 --- /dev/null +++ b/style.css @@ -0,0 +1,78 @@ +body { + background-color: #fff; + width: 80%; + margin: 0 auto; + font-family: sans-serif; + font-size: 0.9em; +} + +h1 { + display: block; + border-bottom: 1px solid black; + font-size: 1.4em; +} + +fieldset { + width: 90%; + margin: 1em auto; + border: 1px solid #bbb; +} + +legend { + color: #222; +} + +#uploaded { + text-align: center; + float: left; + border: 1px solid #ccc; + width: 28%; +} + +#results { + margin-left: 32%; +} + +div.color { + height: 3em; + border: 1px solid white; + -moz-border-radius: 1em; +} + +span.color { + font-weight: bold; +} + +#youricon { + min-height: 200px; +} + +#youricon div { + text-align: center; +} + +.icon { + display: block; + width: 100px; + min-height: 120px; + background-color: black; + border: 1px solid #ccc; + -moz-border-radius: 1em; + text-align: center; + font-family: sans-serif; + padding: 10px; + margin: 0 auto; + color: white; +} + +.icon object { + width: 80px; + height: 80px; + overflow: hidden; +} + +.icon p { + margin: 10px auto; + font-size: 1.1em; +} + diff --git a/template.svg b/template.svg new file mode 100644 index 0000000..a9f3ebc --- /dev/null +++ b/template.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" height="80" width="80" version="1.1"> + <defs> + <linearGradient id="hicg_back_gradient" x1="40" y1="0" x2="40" y2="80" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 972.8)" > + <stop id="hicg_back_grad_stop1" stop-color="#00a700" offset="0"/> + <stop id="hicg_back_grad_stop2" stop-color="#004100" offset="1"/> + </linearGradient> + </defs> + <g transform="translate(0 -972.36)"> + <path fill="url(#hicg_back_gradient)" d="m39.875 973.3c39.875-0.45834 37.75 21.833 39 39.125-1.0833 19.375 0.83333 39.375-38.875 39-39.083 0.4167-38.042-18.667-38.875-38.5 1.2917-18.583-0.91667-40.167 38.75-39.625z"/> + </g> +</svg> |