summaryrefslogtreecommitdiff
path: root/util/spritegen.pl
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2015-05-11 12:49:13 +0200
committerYorhel <git@yorhel.nl>2015-05-11 13:17:36 +0200
commit887607bb3744c727ec617508c17b2b7df46c2287 (patch)
treea8d81234c75e90a112f50d5bb5de6db2536f24a1 /util/spritegen.pl
parent89d3405ac83145f0d7a27db37fcc835bdc2948d2 (diff)
Generate icons.png and associated CSS automatically
It became a bit of a hassle to keep updating that file manually in Gimp. This script performs surprisingly well for our set of icons.
Diffstat (limited to 'util/spritegen.pl')
-rwxr-xr-xutil/spritegen.pl143
1 files changed, 143 insertions, 0 deletions
diff --git a/util/spritegen.pl b/util/spritegen.pl
new file mode 100755
index 00000000..c6f830f7
--- /dev/null
+++ b/util/spritegen.pl
@@ -0,0 +1,143 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Image::Magick;
+use Cwd 'abs_path';
+
+our $ROOT;
+BEGIN { ($ROOT = abs_path $0) =~ s{/util/spritegen\.pl$}{}; }
+
+my $path = "$ROOT/data/icons";
+my $icons = "$ROOT/static/f/icons.png";
+my $css = "$ROOT/data/icons/icons.css";
+
+my @img = map {
+ my $i = Image::Magick->new();
+ $i->Read($_) and die $_;
+ {
+ f => /^\Q$path\E\/(.+)\.png/ && $1,
+ i => $i,
+ h => scalar $i->Get('height'),
+ w => scalar $i->Get('width')
+ }
+} glob("$path/*.png"), glob("$path/*/*.png");
+
+
+@img = sort { $b->{h} <=> $a->{h} || $b->{w} <=> $a->{w} } @img;
+
+my $minpixels = 0;
+$minpixels += $_->{w}*$_->{h} for @img;
+
+
+# Simple strip packing algortihm, First-Fit Decreasing Height.
+sub genstrip {
+ my $w = shift;
+ my @l;
+ my $h = 0;
+ for my $i (@img) {
+ my $found = 0;
+ # @img is assumed to be sorted by height, so image always fits
+ # (height-wise) in any of the previously created levels.
+ for my $l (@l) {
+ next if $l->{left} + $i->{w} > $w;
+ # Image fits, add to level
+ $i->{x} = $l->{left};
+ $i->{y} = $l->{top};
+ $l->{left} += $i->{w};
+ $found = 1;
+ last;
+ }
+ next if $found;
+
+ # No level found, create a new one
+ push @l, { top => $h, left => $i->{w} };
+ $i->{x} = 0;
+ $i->{y} = $h;
+ $h += $i->{h};
+ }
+ return $h;
+}
+
+
+# Tries to find the width of the strip for which the number of unused pixels is
+# the minimum. Simple and dumb linear search; it's fast enough.
+#
+# Note that minimum number of unused pixels does not imply minimum file size,
+# although there is some correlation. To further minimize the file size, it's
+# possible to attempt to group similar-looking images close together so that
+# the final png image might compress better. Finding a good (and fast)
+# algorithm for this is not a trivial task, however.
+sub minstrip {
+ my($minwidth, $maxwidth) = (0,0);
+ for(@img) {
+ $minwidth = $_->{w} if $_->{w} > $minwidth;
+ $maxwidth += $_->{w};
+ }
+ my $optw;
+ my $optsize = 1e10;
+ for my $w ($minwidth..$maxwidth) {
+ my $size = genstrip($w)*$w;
+ # To optimize for file size, uncommment below line. It's slow, but saves about 150 bytes (while using pngcrush).
+ #$size = img();
+ if($size < $optsize) {
+ $optw = $w;
+ $optsize = $size;
+ }
+ }
+ genstrip($optw);
+}
+
+
+sub calcdim {
+ my($w, $h) = (0,0);
+ for (@img) {
+ $w = $_->{x}+$_->{w} if $w < $_->{x}+$_->{w};
+ $h = $_->{y}+$_->{h} if $h < $_->{y}+$_->{h};
+ }
+ ($w, $h)
+}
+
+
+sub img {
+ my($w, $h) = calcdim;
+ my $img = Image::Magick->new;
+ print $img->Set(size => "${w}x$h");
+ print $img->ReadImage('canvas:rgba(0,0,0,0)');
+ my $pixels = $w*$h;
+ for my $i (@img) {
+ print $img->Composite(image => $i->{i}, x => $i->{x}, y => $i->{y});
+ }
+ print $img->Write($icons);
+ undef $img;
+
+ `pngcrush -q "$icons" "$icons~" 2>/dev/null && mv "$icons~" "$icons"`;
+
+ my $size = -s $icons;
+ #printf "Dim: %dx%d, size: %d, pixels wasted: %d\n", $w, $h, $size, $w*$h-$minpixels;
+ $size;
+}
+
+
+sub css {
+ # The gender icons need special treatment, they're 3 icons in one image.
+ my $gender;
+
+ open my $F, '>', $css or die $!;
+ for my $i (@img) {
+ if($i->{f} eq 'gender') {
+ $gender = $i;
+ next;
+ }
+ $i->{f} =~ /([^\/]+)$/;
+ printf $F ".icons.%s { background-position: %dpx %dpx }\n", $1, -$i->{x}, -$i->{y};
+ }
+ printf $F ".icons.gen.f, .icons.gen.b { background-position: %dpx %dpx }\n", -$gender->{x}, -$gender->{y};
+ printf $F ".icons.gen.m { background-position: %dpx %dpx }\n", -($gender->{x}+14), -$gender->{y};
+}
+
+
+#genstrip 80;
+minstrip;
+img;
+css;