summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJavier S. Pedro <maemo@javispedro.com>2011-06-30 22:48:35 +0200
committerJavier S. Pedro <maemo@javispedro.com>2011-06-30 22:48:35 +0200
commit6bd49ac82a9606ec394c52b1a8f583915c9bd8b5 (patch)
treefb5c2c009aca0c70dbf9d7d767c96842f0acaf46
downloadhicg-6bd49ac82a9606ec394c52b1a8f583915c9bd8b5.tar.gz
hicg-6bd49ac82a9606ec394c52b1a8f583915c9bd8b5.zip
initial import
-rw-r--r--index.php390
-rw-r--r--qsvg.cc59
-rw-r--r--style.css78
-rw-r--r--template.svg12
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>
diff --git a/qsvg.cc b/qsvg.cc
new file mode 100644
index 0000000..2eb737e
--- /dev/null
+++ b/qsvg.cc
@@ -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>