From: simon Date: Tue, 26 Dec 2006 22:00:11 +0000 (+0000) Subject: Code to construct Windows icon files for the puzzles, by munging the X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/commitdiff_plain/fbd2dc512d5a3b913aa36c559e213debca115537 Code to construct Windows icon files for the puzzles, by munging the screenshots into appropriate sizes and colour depths. This is all done with a nasty Perl script, because ImageMagick does not output correct .ICO format. Not sure why; it isn't _that_ hard. I intend at some point to link the resulting icons into the actual Windows puzzle binaries, but before then I have to make them prettier: most of them would benefit from being derived from a smaller crop of the puzzle screenshot instead of trying to fit the whole thing in. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@7017 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/icons/Makefile b/icons/Makefile index 114de63..a5a1c93 100644 --- a/icons/Makefile +++ b/icons/Makefile @@ -4,26 +4,74 @@ PUZZLES = blackbox bridges cube dominosa fifteen flip guess inertia lightup \ loopy map mines net netslide pattern pegs rect samegame sixteen \ slant solo tents twiddle untangle -BASEPNGS = $(patsubst %,%-base.png,$(PUZZLES)) -WEBPNGS = $(patsubst %,%-web.png,$(PUZZLES)) +BASE = $(patsubst %,%-base.png,$(PUZZLES)) +WEB = $(patsubst %,%-web.png,$(PUZZLES)) + +BASE4 = $(patsubst %,%-base4.png,$(PUZZLES)) + +P48D24 = $(patsubst %,%-48d24.png,$(PUZZLES)) +P48D8 = $(patsubst %,%-48d8.png,$(PUZZLES)) +P48D4 = $(patsubst %,%-48d4.png,$(PUZZLES)) +P32D24 = $(patsubst %,%-32d24.png,$(PUZZLES)) +P32D8 = $(patsubst %,%-32d8.png,$(PUZZLES)) +P32D4 = $(patsubst %,%-32d4.png,$(PUZZLES)) +P16D24 = $(patsubst %,%-16d24.png,$(PUZZLES)) +P16D8 = $(patsubst %,%-16d8.png,$(PUZZLES)) +P16D4 = $(patsubst %,%-16d4.png,$(PUZZLES)) +ICONS = $(patsubst %,%.ico,$(PUZZLES)) BIN = ../ PIC = ./ -base: $(BASEPNGS) -webpics: $(WEBPNGS) +base: $(BASE) +web: $(WEB) +pngicons: $(I48D24) $(I32D24) $(I16D24) +icons: $(ICONS) -fifteen.png : override REDO=0.3 -flip.png : override REDO=0.3 -netslide.png : override REDO=0.3 -sixteen.png : override REDO=0.3 -twiddle.png : override REDO=0.3 +fifteen-base.png : override REDO=0.3 +flip-base.png : override REDO=0.3 +netslide-base.png : override REDO=0.3 +sixteen-base.png : override REDO=0.3 +twiddle-base.png : override REDO=0.3 -$(BASEPNGS): %-base.png: $(BIN)% $(PIC)%.sav $(PIC)screenshot.sh +$(BASE): %-base.png: $(BIN)% $(PIC)%.sav $(PIC)screenshot.sh $(BIN)$* $(PIC)$*.sav $@ $(REDO) -$(WEBPNGS): %-web.png: %-base.png +$(BASE4): %-base4.png: %-base.png + convert -colors 16 +dither -map win16pal.xpm $^ $@ + +$(WEB): %-web.png: %-base.png $(PIC)square.pl 150 5 $^ $@ +$(P48D24): %-48d24.png: %-base.png + $(PIC)square.pl 48 4 $^ $@ +$(P32D24): %-32d24.png: %-base.png + $(PIC)square.pl 32 2 $^ $@ +$(P16D24): %-16d24.png: %-base.png + $(PIC)square.pl 16 1 $^ $@ + +$(P48D8) $(P32D8) $(P16D8): %d8.png: %d24.png + convert -colors 256 $^ $@ + +# The depth-4 images work better if we normalise the colours +# _before_ shrinking, and then normalise again afterwards. +$(P48D4): %-48d4.png: %-base4.png + $(PIC)square.pl 48 1 $^ tmp2.png + convert -colors 16 -map win16pal.xpm tmp2.png $@ + rm -f tmp.png tmp2.png +$(P32D4): %-32d4.png: %-base.png + $(PIC)square.pl 32 1 $^ tmp2.png + convert -colors 16 -map win16pal.xpm tmp2.png $@ + rm -f tmp.png tmp2.png +$(P16D4): %-16d4.png: %-base.png + $(PIC)square.pl 16 1 $^ tmp2.png + convert -colors 16 -map win16pal.xpm tmp2.png $@ + rm -f tmp.png tmp2.png + +$(ICONS): %.ico: %-48d24.png %-48d8.png %-48d4.png \ + %-32d24.png %-32d8.png %-32d4.png \ + %-16d24.png %-16d8.png %-16d4.png + $(PIC)icon.pl $? > $@ + clean: - rm -f *.png + rm -f *.png *.ico diff --git a/icons/icon.pl b/icons/icon.pl new file mode 100755 index 0000000..f0d2931 --- /dev/null +++ b/icons/icon.pl @@ -0,0 +1,211 @@ +#!/usr/bin/perl + +# Take nine input image files and convert them into a +# multi-resolution Windows .ICO icon file. The nine files should +# be, in order: +# +# - 48x48 icons at 24-bit, 8-bit and 4-bit colour depth respectively +# - 32x32 icons at 24-bit, 8-bit and 4-bit colour depth respectively +# - 16x16 icons at 24-bit, 8-bit and 4-bit colour depth respectively +# +# ICO files support a 1-bit alpha channel on all these image types. +# +# TODO: it would be nice if we could extend this icon builder to +# support monochrome icons and a user-specified subset of the +# available formats. None of that should be too hard: the +# monochrome raster data has the same format as the alpha channel, +# monochrome images have a 2-colour palette containing 000000 and +# FFFFFF respectively, and really the biggest problem is designing +# a sensible command-line syntax! + +%win16pal = ( + "\x00\x00\x00\x00" => 0, + "\x00\x00\x80\x00" => 1, + "\x00\x80\x00\x00" => 2, + "\x00\x80\x80\x00" => 3, + "\x80\x00\x00\x00" => 4, + "\x80\x00\x80\x00" => 5, + "\x80\x80\x00\x00" => 6, + "\xC0\xC0\xC0\x00" => 7, + "\x80\x80\x80\x00" => 8, + "\x00\x00\xFF\x00" => 9, + "\x00\xFF\x00\x00" => 10, + "\x00\xFF\xFF\x00" => 11, + "\xFF\x00\x00\x00" => 12, + "\xFF\x00\xFF\x00" => 13, + "\xFF\xFF\x00\x00" => 14, + "\xFF\xFF\xFF\x00" => 15, +); +@win16pal = sort { $win16pal{$a} <=> $win16pal{$b} } keys %win16pal; + +@hdr = (); +@dat = (); + +&readicon($ARGV[0], 48, 48, 24); +&readicon($ARGV[1], 48, 48, 8); +&readicon($ARGV[2], 48, 48, 4); +&readicon($ARGV[3], 32, 32, 24); +&readicon($ARGV[4], 32, 32, 8); +&readicon($ARGV[5], 32, 32, 4); +&readicon($ARGV[6], 16, 16, 24); +&readicon($ARGV[7], 16, 16, 8); +&readicon($ARGV[8], 16, 16, 4); + +# Now write out the output icon file. +print pack "vvv", 0, 1, scalar @hdr; # file-level header +$filepos = 6 + 16 * scalar @hdr; +for ($i = 0; $i < scalar @hdr; $i++) { + print $hdr[$i]; + print pack "V", $filepos; + $filepos += length($dat[$i]); +} +for ($i = 0; $i < scalar @hdr; $i++) { + print $dat[$i]; +} + +sub readicon { + my $filename = shift @_; + my $w = shift @_; + my $h = shift @_; + my $depth = shift @_; + my $pix; + my $i; + my %pal; + + # Read the file in as RGBA data. We flip vertically at this + # point, to avoid having to do it ourselves (.BMP and hence + # .ICO are bottom-up). + my $data = []; + open IDATA, "convert -flip -depth 8 $filename rgba:- |"; + push @$data, $rgb while (read IDATA,$rgb,4,0) == 4; + close IDATA; + # Check we have the right amount of data. + $xl = $w * $h; + $al = scalar @$data; + die "wrong amount of image data ($al, expected $xl) from $filename\n" + unless $al == $xl; + + # Build the alpha channel now, so we can exclude transparent + # pixels from the palette analysis. We replace transparent + # pixels with undef in the data array. + # + # We quantise the alpha channel half way up, so that alpha of + # 0x80 or more is taken to be fully opaque and 0x7F or less is + # fully transparent. Nasty, but the best we can do without + # dithering (and don't even suggest we do that!). + my $x; + my $y; + my $alpha = ""; + + for ($y = 0; $y < $h; $y++) { + my $currbyte = 0, $currbits = 0; + for ($x = 0; $x < (($w+31)|31)-31; $x++) { + $pix = ($x < $w ? $data->[$y*$w+$x] : "\x00\x00\x00\xFF"); + my @rgba = unpack "CCCC", $pix; + $currbyte <<= 1; + $currbits++; + if ($rgba[3] < 0x80) { + if ($x < $w) { + $data->[$y*$w+$x] = undef; + } + $currbyte |= 1; # MS has the alpha channel inverted :-) + } else { + # Might as well flip RGBA into BGR0 while we're here. + if ($x < $w) { + $data->[$y*$w+$x] = pack "CCCC", + $rgba[2], $rgba[1], $rgba[0], 0; + } + } + if ($currbits >= 8) { + $alpha .= pack "C", $currbyte; + $currbits -= 8; + } + } + } + + # For an 8-bit image, check we have at most 256 distinct + # colours, and build the palette. + %pal = (); + if ($depth == 8) { + my $palindex = 0; + foreach $pix (@$data) { + next unless defined $pix; + $pal{$pix} = $palindex++ unless defined $pal{$pix}; + } + die "too many colours in 8-bit image $filename\n" unless $palindex <= 256; + } elsif ($depth == 4) { + %pal = %win16pal; + } + + my $raster = ""; + if ($depth < 24) { + # For a non-24-bit image, flatten the image into one palette + # index per pixel. + my $currbyte = 0, $currbits = 0; + for ($i = 0; $i < scalar @$data; $i++) { + $pix = $data->[$i]; + $currbyte <<= $depth; + $currbits += $depth; + if (defined $pix) { + if (!defined $pal{$pix}) { + die "illegal colour value $pix at pixel $i in $filename\n"; + } + $currbyte |= $pal{$pix}; + } else { + $currbyte |= 0; + } + if ($currbits >= 8) { + $raster .= pack "C", $currbyte; + $currbits -= 8; + } + } + } else { + # For a 24-bit image, reverse the order of the R,G,B values + # and stick a padding zero on the end. + for ($i = 0; $i < scalar @$data; $i++) { + if (defined $data->[$i]) { + $raster .= $data->[$i]; + } else { + $raster .= "\x00\x00\x00\x00"; + } + } + $depth = 32; # and adjust this + } + + # Prepare the icon data. First the header... + my $data = pack "VVVvvVVVVVV", + 40, # size of bitmap info header + $w, # icon width + $h*2, # icon height (x2 to indicate the subsequent alpha channel) + 1, # 1 plane (common to all MS image formats) + $depth, # bits per pixel + 0, # no compression + length $raster, # image size + 0, 0, 0, 0; # resolution, colours used, colours important (ignored) + # ... then the palette ... + if ($depth <= 8) { + my $ncols = (1 << $depth); + my $palette = "\x00\x00\x00\x00" x $ncols; + foreach $i (keys %pal) { + substr($palette, $pal{$i}*4, 4) = $i; + } + $data .= $palette; + } + # ... the raster data we already had ready ... + $data .= $raster; + # ... and the alpha channel we already had as well. + $data .= $alpha; + + # Prepare the header which will represent this image in the + # icon file. + my $header = pack "CCCCvvV", + $w, $h, # width and height (this time the real height) + 1 << $depth, # number of colours, if less than 256 + 0, # reserved + 1, # planes + $depth, # bits per pixel + length $data; # size of real icon data + + push @hdr, $header; + push @dat, $data; +} diff --git a/icons/square.pl b/icons/square.pl index 295b2f1..81be9d7 100755 --- a/icons/square.pl +++ b/icons/square.pl @@ -17,13 +17,13 @@ $ident =~ /(\d+) (\d+)/ or die "unable to get size for $infile\n"; # Read the input image data. $data = []; -open IDATA, "convert $infile rgb:- |"; +open IDATA, "convert -depth 8 $infile rgb:- |"; push @$data, $rgb while (read IDATA,$rgb,3,0) == 3; close IDATA; # Check we have the right amount of data. $xl = $w * $h; $al = scalar @$data; -die "wrong amount of image data ($al, expected $xl) from $img\n" +die "wrong amount of image data ($al, expected $xl) from $infile\n" unless $al == $xl; # Find the background colour. We assume the image already has a diff --git a/icons/win16pal.xpm b/icons/win16pal.xpm new file mode 100644 index 0000000..66fd60a --- /dev/null +++ b/icons/win16pal.xpm @@ -0,0 +1,23 @@ +/* XPM */ +static char *win16pal[] = { +/* columns rows colors chars-per-pixel */ +"16 1 16 1", +" c #000000", +". c #800000", +"X c #008000", +"o c #808000", +"O c #000080", +"+ c #800080", +"@ c #008080", +"# c #C0C0C0", +"$ c #808080", +"% c #FF0000", +"& c #00FF00", +"* c #FFFF00", +"= c #0000FF", +"- c #FF00FF", +"; c #00FFFF", +": c #FFFFFF", +/* pixels */ +" .XoO+@#$%&*=-;:" +};