Browse Source

Plan 9 from Bell Labs 2003-12-23

David du Colombier 20 years ago
parent
commit
83930279d6
7 changed files with 886 additions and 6 deletions
  1. 6 4
      dist/replica/plan9.db
  2. 8 0
      dist/replica/plan9.log
  3. 29 1
      sys/man/1/jpg
  4. 13 1
      sys/src/cmd/cmp.c
  5. 506 0
      sys/src/cmd/jpg/ico.c
  6. 2 0
      sys/src/cmd/jpg/mkfile
  7. 322 0
      sys/src/cmd/jpg/toico.c

+ 6 - 4
dist/replica/plan9.db

@@ -3928,7 +3928,7 @@ sys/lib/man/lookman - 20000000775 sys sys 956337764 0
 sys/lib/man/lookman/index - 664 sys sys 1045538133 1405018
 sys/lib/man/lookman/junkwords - 664 sys sys 956337764 3059
 sys/lib/man/lookman/mkindex - 775 sys sys 1017679307 306
-sys/lib/man/mkhtmlindex - 775 sys sys 959447575 1235
+sys/lib/man/mkhtmlindex - 775 sys sys 1072129593 1235
 sys/lib/man/mksearchindex - 775 sys sys 959447575 197
 sys/lib/man/permind - 20000000775 sys sys 959447712 0
 sys/lib/man/permind/bfile - 664 sys sys 956337769 17
@@ -4640,7 +4640,7 @@ sys/man/1/history - 664 sys sys 1044909169 1709
 sys/man/1/hoc - 664 sys sys 944959676 2357
 sys/man/1/idiff - 664 sys sys 1018386774 927
 sys/man/1/join - 664 sys sys 957920006 2562
-sys/man/1/jpg - 664 sys sys 1068209494 4003
+sys/man/1/jpg - 664 sys sys 1072106983 4789
 sys/man/1/kill - 664 sys sys 1018369246 1193
 sys/man/1/ktrace - 664 sys sys 957920006 1330
 sys/man/1/leak - 664 sys sys 1017423522 2871
@@ -7249,7 +7249,7 @@ sys/src/cmd/chgrp.c - 664 sys sys 1014926580 624
 sys/src/cmd/chmod.c - 664 sys sys 1014926580 1858
 sys/src/cmd/cleanname.c - 664 sys sys 944960760 715
 sys/src/cmd/clock.c - 664 sys sys 1014925410 1881
-sys/src/cmd/cmp.c - 664 sys sys 1019525053 2098
+sys/src/cmd/cmp.c - 664 sys sys 1072145760 2379
 sys/src/cmd/colors.c - 664 sys sys 951763912 3186
 sys/src/cmd/comm.c - 664 sys sys 944961350 2140
 sys/src/cmd/con - 20000000775 sys sys 944961996 0
@@ -9519,10 +9519,11 @@ sys/src/cmd/join.c - 664 sys sys 944961364 7488
 sys/src/cmd/jpg - 20000000775 sys sys 988249983 0
 sys/src/cmd/jpg/close.c - 664 sys sys 944961292 1930
 sys/src/cmd/jpg/gif.c - 664 sys sys 1045505400 8844
+sys/src/cmd/jpg/ico.c - 664 sys sys 1072107030 8799
 sys/src/cmd/jpg/imagefile.h - 664 sys sys 988225303 2246
 sys/src/cmd/jpg/jpegdump.c - 664 sys sys 1018802871 7161
 sys/src/cmd/jpg/jpg.c - 664 sys sys 1014926432 7389
-sys/src/cmd/jpg/mkfile - 664 sys sys 1067719079 1000
+sys/src/cmd/jpg/mkfile - 664 sys sys 1072107030 1014
 sys/src/cmd/jpg/multichan.c - 664 sys sys 1039753047 825
 sys/src/cmd/jpg/onechan.c - 664 sys sys 1039753044 3732
 sys/src/cmd/jpg/png.c - 664 sys sys 1014926432 4655
@@ -9535,6 +9536,7 @@ sys/src/cmd/jpg/readyuv.c - 664 sys sys 1067742240 3502
 sys/src/cmd/jpg/rgbrgbv.c - 664 sys sys 984718829 1236
 sys/src/cmd/jpg/rgbycc.c - 664 sys sys 944961292 2486
 sys/src/cmd/jpg/togif.c - 664 sys sys 944961292 2828
+sys/src/cmd/jpg/toico.c - 664 sys sys 1072107030 5638
 sys/src/cmd/jpg/topng.c - 664 sys sys 988225303 1116
 sys/src/cmd/jpg/toppm.c - 664 sys sys 944961292 1571
 sys/src/cmd/jpg/torgbv.c - 664 sys sys 1067719080 6443

+ 8 - 0
dist/replica/plan9.log

@@ -13169,3 +13169,11 @@
 1071885716 0 a sys/src/cmd/rc/compiling.on.unix - 664 sys sys 1071884773 37638
 1071927122 0 c sys/lib/mimetype - 664 sys sys 1071926670 5633
 1072020615 0 c sys/src/cmd/acme/look.c - 664 sys sys 1072019591 14096
+1072107026 0 c sys/man/1/jpg - 664 sys sys 1072106983 4789
+1072107026 1 a sys/src/cmd/jpg/ico.c - 664 sys sys 1072107030 8799
+1072107026 2 c sys/src/cmd/jpg/mkfile - 664 sys sys 1072107030 1014
+1072107026 3 a sys/src/cmd/jpg/toico.c - 664 sys sys 1072107030 5638
+1072114227 0 c sys/src/cmd/cmp.c - 664 sys sys 1072114096 2379
+1072121428 0 c sys/lib/man/mkhtmlindex - 775 sys sys 1072120754 1313
+1072130430 0 c sys/lib/man/mkhtmlindex - 775 sys sys 1072129593 1235
+1072146632 0 c sys/src/cmd/cmp.c - 664 sys sys 1072145760 2379

+ 29 - 1
sys/man/1/jpg

@@ -1,6 +1,6 @@
 .TH JPG 1
 .SH NAME
-jpg, gif, png, ppm, togif, toppm, topng \- view and convert pictures
+jpg, gif, png, ppm, ico, togif, toppm, topng, toico \- view and convert pictures
 .SH SYNOPSIS
 .B jpg
 [
@@ -70,6 +70,16 @@ jpg, gif, png, ppm, togif, toppm, topng \- view and convert pictures
 ] [
 .I file
 ]
+.PP
+.B ico
+[
+.I file
+]
+.PP
+.B toico
+[
+.I file ...
+]
 .SH DESCRIPTION
 These programs read, display, and write image files in public formats.
 .IR Jpg ,
@@ -192,6 +202,24 @@ displaying the next named
 .I Gif
 translates files that contain a `transparency' index by attaching
 an alpha channel to the converted image.
+.PP
+.I Ico
+displays a Windows icon (.ico) file.  If no file is
+specified,
+.I ico
+reads from standad input.
+Icon files
+contain sets of icons represeted by an image and a mask.
+Clicking the right button pops up a menu that lets you
+write any icon's image as a Plan 9 image (\fIwidth\fBx\fIheight\fB.image),
+write any icon's mask as a Plan 9 image (\fIwidth\fBx\fIheight\fB.mask),
+or exit.  Selecting one of the write menu items yields a sight cursor.
+Move the sight over the icon and right click again to write.
+.PP
+.I Toico
+takes a list of Plan 9 image files (or standard input) and creates
+a single icon file.  The masks in the icon file will be the white
+space in the image.  The icon file is written to standard output.
 .SH SOURCE
 .B /sys/src/cmd/jpg
 .SH "SEE ALSO"

+ 13 - 1
sys/src/cmd/cmp.c

@@ -97,10 +97,22 @@ main(int argc, char *argv[])
 		b1s += n;
 		b2s += n;
 	}
+	if (b1e - b1s < 0 || b2e - b2s < 0) {
+		if (!sflag) {
+			if (b1e - b1s < 0)
+				print("error on %s after %lld bytes\n",
+					name1, nc-1);
+			if (b2e - b2s < 0)
+				print("error on %s after %lld bytes\n",
+					name2, nc-1);
+		}
+		exits("read error");
+	}
 	if(b1e - b1s == b2e - b2s)
 		exits((char *)0);
 	if(!sflag)
-		print("EOF on %s\n", (b1e - b1s > b2e - b2s)? name2 : name1);
+		print("EOF on %s after %lld bytes\n",
+			(b1e - b1s > b2e - b2s)? name2 : name1, nc-1);
 	exits("EOF");
 }
 

+ 506 - 0
sys/src/cmd/jpg/ico.c

@@ -0,0 +1,506 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include <cursor.h>
+
+typedef struct Icon Icon;
+struct Icon
+{
+	Icon	*next;
+
+	uchar	w;		/* icon width */
+	uchar	h;		/* icon height */
+	ushort	ncolor;		/* number of colors */
+	ushort	nplane;		/* number of bit planes */
+	ushort	bits;		/* bits per pixel */
+	ulong	len;		/* length of data */
+	ulong	offset;		/* file offset to data */
+
+	Image	*img;
+	Image	*mask;
+
+	Rectangle r;		/* relative */
+	Rectangle sr;		/* abs */
+};
+
+typedef struct Header Header;
+struct Header
+{
+	uint	n;
+	Icon	*first;
+	Icon	*last;
+};
+
+int debug;
+Mouse mouse;
+Header h;
+Image *background;
+
+ushort
+gets(uchar *p)
+{
+	return p[0] | (p[1]<<8);
+}
+
+ulong
+getl(uchar *p)
+{
+	return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
+}
+
+int
+Bgetheader(Biobuf *b, Header *h)
+{
+	Icon *icon;
+	int i;
+	uchar buf[40];
+
+	memset(h, 0, sizeof(*h));
+	if(Bread(b, buf, 6) != 6)
+		goto eof;
+	if(gets(&buf[0]) != 0)
+		goto header;
+	if(gets(&buf[2]) != 1)
+		goto header;
+	h->n = gets(&buf[4]);
+
+	for(i = 0; i < h->n; i++){
+		icon = mallocz(sizeof(*icon), 1);
+		if(icon == nil)
+			sysfatal("malloc: %r");
+		if(Bread(b, buf, 16) != 16)
+			goto eof;
+		icon->w = buf[0];
+		icon->h = buf[1];
+		icon->ncolor = buf[2] == 0 ? 256 : buf[2];
+		if(buf[3] != 0)
+			goto header;
+		icon->nplane = gets(&buf[4]);
+		icon->bits = gets(&buf[6]);
+		icon->len = getl(&buf[8]);
+		icon->offset = getl(&buf[12]);
+
+		if(i == 0)
+			h->first = icon;
+		else
+			h->last->next = icon;
+		h->last = icon;
+	}
+	return 0;
+
+eof:
+	werrstr("unexpected EOF");
+	return -1;
+header:
+	werrstr("unknown header format");
+	return -1;
+}
+
+uchar*
+transcmap(Icon *icon, uchar *map)
+{
+	uchar *m, *p;
+	int i;
+
+	p = m = malloc(sizeof(int)*(1<<icon->bits));
+	for(i = 0; i < icon->ncolor; i++){
+		*p++ = rgb2cmap(map[2], map[1], map[0]);
+		map += 4;
+	}
+	return m;
+}
+
+Image*
+xor2img(Icon *icon, uchar *xor, uchar *map)
+{
+	uchar *data;
+	Image *img;
+	int inxlen;
+	uchar *from, *to;
+	int s, byte, mask;
+	int x, y;
+
+	inxlen = 4*((icon->bits*icon->w+31)/32);
+	to = data = malloc(icon->w*icon->h);
+
+	/* rotate around the y axis, go to 8 bits, and convert color */
+	mask = (1<<icon->bits)-1;
+	for(y = 0; y < icon->h; y++){
+		s = -1;
+		byte = 0;
+		from = xor + (icon->h - 1 - y)*inxlen;
+		for(x = 0; x < icon->w; x++){
+			if(s < 0){
+				byte = *from++;
+				s = 8-icon->bits;
+			}
+			*to++ = map[(byte>>s) & mask];
+			s -= icon->bits;
+		}
+	}
+
+	/* stick in an image */
+	img = allocimage(display, Rect(0,0,icon->w,icon->h), CMAP8, 0, DNofill);
+	loadimage(img, Rect(0,0,icon->w,icon->h), data, icon->h*icon->w);
+
+	free(data);
+	return img;
+}
+
+Image*
+and2img(Icon *icon, uchar *and)
+{
+	uchar *data;
+	Image *img;
+	int inxlen;
+	int outxlen;
+	uchar *from, *to;
+	int x, y;
+
+	inxlen = 4*((icon->w+31)/32);
+	to = data = malloc(inxlen*icon->h);
+
+	/* rotate around the y axis and invert bits */
+	outxlen = (icon->w+7)/8;
+	for(y = 0; y < icon->h; y++){
+		from = and + (icon->h - 1 - y)*inxlen;
+		for(x = 0; x < outxlen; x++){
+			*to++ = ~(*from++);
+		}
+	}
+
+	/* stick in an image */
+	img = allocimage(display, Rect(0,0,icon->w,icon->h), GREY1, 0, DNofill);
+	loadimage(img, Rect(0,0,icon->w,icon->h), data, icon->h*outxlen);
+
+	free(data);
+	return img;
+}
+
+int
+Bgeticon(Biobuf *b, Icon *icon)
+{
+	ulong l;
+	ushort s;
+	uchar *xor;
+	uchar *and;
+	uchar *cm;
+	uchar *buf;
+	uchar *map2map;
+	Image *img;
+
+	Bseek(b, icon->offset, 0);
+	buf = malloc(icon->len);
+	if(buf == nil)
+		return -1;
+	if(Bread(b, buf, icon->len) != icon->len){
+		werrstr("unexpected EOF");
+		return -1;
+	}
+
+	/* this header's info takes precedence over previous one */
+	if(getl(buf) != 40){
+		werrstr("bad icon header");
+		return -1;
+	}
+	l = getl(buf+4);
+	if(l != icon->w)
+		icon->w = l;
+	l = getl(buf+8);
+	if(l>>1 != icon->h)
+		icon->h = l>>1;
+	s = gets(buf+12);
+	if(s != icon->nplane)
+		icon->nplane = s;
+	s = gets(buf+14);
+	if(s != icon->bits)
+		icon->bits = s;
+
+	/* limit what we handle */
+	switch(icon->bits){
+	case 1:
+	case 2:
+	case 4:
+	case 8:
+		break;
+	default:
+		werrstr("don't support %d bit pixels", icon->bits);
+		return -1;
+	}
+	if(icon->nplane != 1){
+		werrstr("don't support %d planes", icon->nplane);
+		return -1;
+	}
+
+	cm = buf + 40;
+	xor = cm + 4*icon->ncolor;
+	and = xor + icon->h*4*((icon->bits*icon->w+31)/32);
+
+	/* translate the color map to a plan 9 one */
+	map2map = transcmap(icon, cm);
+
+	/* convert the images */
+	icon->img = xor2img(icon, xor, map2map);
+	icon->mask = and2img(icon, and);
+
+	/* so that we save an image with a white background */
+	img = allocimage(display, icon->img->r, CMAP8, 0, DWhite);
+	draw(img, icon->img->r, icon->img, icon->mask, ZP);
+	icon->img = img;
+
+	free(buf);
+	free(map2map);
+	return 0;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [file]\n", argv0);
+	exits("usage");
+}
+
+enum
+{
+	Mimage,
+	Mmask,
+	Mexit,
+
+	Up= 1,
+	Down= 0,
+};
+
+char	*menu3str[] = {
+	[Mimage]	"write image",
+	[Mmask]		"write mask",
+	[Mexit]		"exit",
+	0,
+};
+
+Menu	menu3 = {
+	menu3str
+};
+
+Cursor sight = {
+	{-7, -7},
+	{0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
+	 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
+	 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
+	 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
+	{0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
+	 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
+	 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
+	 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
+};
+
+void
+buttons(int ud)
+{
+	while((mouse.buttons==0) != ud)
+		mouse = emouse();
+}
+
+void
+mesg(char *fmt, ...)
+{
+	va_list arg;
+	char buf[1024];
+	static char obuf[1024];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof(buf), fmt, arg);
+	va_end(arg);
+	string(screen, screen->r.min, background, ZP, font, obuf);
+	string(screen, screen->r.min, display->white, ZP, font, buf);
+	strcpy(obuf, buf);
+}
+
+void
+doimage(Icon *icon)
+{
+	int rv;
+	char file[256];
+	int fd;
+
+	rv = -1;
+	snprint(file, sizeof(file), "%dx%d.img", icon->w, icon->h);
+	fd = create(file, OWRITE, 0664);
+	if(fd >= 0){
+		rv = writeimage(fd, icon->img, 0);
+		close(fd);
+	}
+	if(rv < 0)
+		mesg("error writing %s: %r", file);
+	else
+		mesg("created %s", file);
+}
+
+void
+domask(Icon *icon)
+{
+	int rv;
+	char file[64];
+	int fd;
+
+	rv = -1;
+	snprint(file, sizeof(file), "%dx%d.mask", icon->w, icon->h);
+	fd = create(file, OWRITE, 0664);
+	if(fd >= 0){
+		rv = writeimage(fd, icon->mask, 0);
+		close(fd);
+	}
+	if(rv < 0)
+		mesg("error writing %s: %r", file);
+	else
+		mesg("created %s", file);
+}
+
+void
+apply(void (*f)(Icon*))
+{
+	Icon *icon;
+
+	esetcursor(&sight);
+	buttons(Down);
+	if(mouse.buttons == 4)
+		for(icon = h.first; icon; icon = icon->next)
+			if(ptinrect(mouse.xy, icon->sr)){
+				buttons(Up);
+				f(icon);
+				break;
+			}
+	buttons(Up);
+	esetcursor(0);
+}
+
+void
+menu(void)
+{
+	int sel;
+
+	sel = emenuhit(3, &mouse, &menu3);
+	switch(sel){
+	case Mimage:
+		apply(doimage);
+		break;
+	case Mmask:
+		apply(domask);
+		break;
+	case Mexit:
+		exits(0);
+		break;
+	}
+}
+
+void
+mousemoved(void)
+{
+	Icon *icon;
+
+	for(icon = h.first; icon; icon = icon->next)
+		if(ptinrect(mouse.xy, icon->sr)){
+			mesg("%dx%d", icon->w, icon->h);
+			return;
+		}
+	mesg("");
+}
+
+enum
+{
+	BORDER= 1,
+};
+
+void
+eresized(int new)
+{
+	Icon *icon;
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0)
+		sysfatal("can't reattach to window");
+	draw(screen, screen->clipr, background, nil, ZP);
+	r.max.x = screen->r.min.x;
+	r.min.y = screen->r.min.y + font->height + 2*BORDER;
+	for(icon = h.first; icon != nil; icon = icon->next){
+		r.min.x = r.max.x + BORDER;
+		r.max.x = r.min.x + Dx(icon->img->r);
+		r.max.y = r.min.y + Dy(icon->img->r);
+		draw(screen, r, icon->img, nil, ZP);
+		border(screen, r, -BORDER, display->black, ZP);
+		icon->sr = r;
+	}
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char **argv)
+{
+	Biobuf in;
+	Icon *icon;
+	int fd;
+	Rectangle r;
+	Event e;
+
+	ARGBEGIN{
+	case 'd':
+		debug = 1;
+		break;
+	}ARGEND;
+
+	fd = -1;
+	switch(argc){
+	case 0:
+		fd = 0;
+		break;
+	case 1:
+		fd = open(argv[0], OREAD);
+		if(fd < 0)
+			sysfatal("opening: %r");
+		break;
+	default:
+		usage();
+		break;
+	}
+
+	Binit(&in, fd, OREAD);
+
+	if(Bgetheader(&in, &h) < 0)
+		sysfatal("reading header: %r");
+
+	initdraw(nil, nil, "ico");
+	background = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (128<<24)|(128<<16)|(128<<8)|0xFF);
+
+	einit(Emouse|Ekeyboard);
+
+	r.min = Pt(4, 4);
+	for(icon = h.first; icon != nil; icon = icon->next){
+		if(Bgeticon(&in, icon) < 0){
+			fprint(2, "bad rectangle: %r\n");
+			continue;
+		}
+		if(debug)
+			fprint(2, "w %ud h %ud ncolor %ud bits %ud len %lud offset %lud\n",
+			   icon->w, icon->h, icon->ncolor, icon->bits, icon->len, icon->offset);
+		r.max = addpt(r.min, Pt(icon->w, icon->h));
+		icon->r = r;
+		r.min.x += r.max.x;
+	}
+	eresized(0);
+
+	for(;;)
+		switch(event(&e)){
+		case Ekeyboard:
+			break;
+		case Emouse:
+			mouse = e.mouse;
+			if(mouse.buttons & 4)
+				menu();
+			else
+				mousemoved();
+			break;
+		}
+
+	exits(0);
+}

+ 2 - 0
sys/src/cmd/jpg/mkfile

@@ -8,6 +8,8 @@ TARG=jpg\
 	png\
 	topng\
 	yuv\
+	ico\
+	toico\
 
 IMFILES=\
 	torgbv.$O\

+ 322 - 0
sys/src/cmd/jpg/toico.c

@@ -0,0 +1,322 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+
+enum
+{
+	FileHdrLen=	6,
+	IconDescrLen=	16,
+	IconHdrLen=	40,
+};
+
+typedef struct Icon Icon;
+struct Icon
+{
+	Icon	*next;
+	char	*file;
+
+	uchar	w;		/* icon width */
+	uchar	h;		/* icon height */
+	ushort	ncolor;		/* number of colors */
+	ushort	nplane;		/* number of bit planes */
+	ushort	bits;		/* bits per pixel */
+	ulong	len;		/* length of data */
+	ulong	offset;		/* file offset to data */
+	uchar	map[4*256];	/* color map */
+
+	Image	*img;
+
+	uchar	*xor;
+	int	xorlen;
+	uchar	*and;
+	int	andlen;
+};
+
+typedef struct Header Header;
+struct Header
+{
+	uint	n;
+	Icon	*first;
+	Icon	*last;
+};
+
+void
+Bputs(Biobuf *b, ushort x)
+{
+	Bputc(b, x&0xff);
+	Bputc(b, x>>8);
+}
+
+void
+Bputl(Biobuf *b, ulong x)
+{
+	Bputs(b, x&0xffff);
+	Bputs(b, x>>16);
+}
+
+Header h;
+
+void*	emalloc(int);
+void	mk8bit(Icon*, int);
+void	mkxorand(Icon*, int);
+void	readicon(char*);
+
+void
+main(int argc, char **argv)
+{
+	int i;
+	Biobuf *b, out;
+	Icon *icon;
+	ulong offset;
+	ulong len;
+
+	ARGBEGIN{
+	}ARGEND;
+
+	/* read in all the images */
+	display = initdisplay(nil, nil, nil);
+	if(argc < 1){
+		readicon("/fd/0");
+	} else {
+		for(i = 0; i < argc; i++)
+			readicon(argv[i]);
+	}
+
+	/* create the .ico file */
+	b = &out;
+	Binit(b, 1, OWRITE);
+
+	/* offset to first icon */
+	offset = FileHdrLen + h.n*IconDescrLen;
+
+	/* file header is */
+	Bputs(b, 0);
+	Bputs(b, 1);
+	Bputs(b, h.n);
+
+	/* icon description */
+	for(icon = h.first; icon != nil; icon = icon->next){
+		Bputc(b, icon->w);
+		Bputc(b, icon->h);
+		Bputc(b, icon->ncolor);
+		Bputc(b, 0);
+		Bputs(b, icon->nplane);
+		Bputs(b, icon->bits);
+		len = IconHdrLen + icon->ncolor*4 + icon->xorlen + icon->andlen;
+		Bputl(b, len);
+		Bputl(b, offset);
+		offset += len;
+	}
+
+	/* icons */
+	for(icon = h.first; icon != nil; icon = icon->next){
+		/* icon header (BMP like) */
+		Bputl(b, IconHdrLen);
+		Bputl(b, icon->w);
+		Bputl(b, 2*icon->h);
+		Bputs(b, icon->nplane);
+		Bputs(b, icon->bits);
+		Bputl(b, 0);	/* compression info */
+		Bputl(b, 0);
+		Bputl(b, 0);
+		Bputl(b, 0);
+		Bputl(b, 0);
+		Bputl(b, 0);
+
+		/* color map */
+		if(Bwrite(b, icon->map, 4*icon->ncolor) < 0)
+			sysfatal("writing color map: %r");
+
+		/* xor bits */
+		if(Bwrite(b, icon->xor, icon->xorlen) < 0)
+			sysfatal("writing xor bits: %r");
+
+		/* and bits */
+		if(Bwrite(b, icon->and, icon->andlen) < 0)
+			sysfatal("writing and bits: %r");
+	}
+
+	Bterm(b);
+	exits(0);
+}
+
+void
+readicon(char *file)
+{
+	int fd;
+	Icon *icon;
+
+	fd = open(file, OREAD);
+	if(fd < 0)
+		sysfatal("opening %s: %r", file);
+	icon = emalloc(sizeof(Icon));
+	icon->img = readimage(display, fd, 0);
+	if(icon->img == nil)
+		sysfatal("reading image %s: %r", file);
+	close(fd);
+
+	if(h.first)
+		h.last->next = icon;
+	else
+		h.first = icon;
+	h.last = icon;
+	h.n++;
+
+	icon->h = Dy(icon->img->r);
+	icon->w = Dx(icon->img->r);
+	icon->bits = 1<<icon->img->depth;
+	icon->nplane = 1;
+
+	/* convert to 8 bits per pixel */
+	switch(icon->img->chan){
+	case GREY8:
+	case CMAP8:
+		break;
+	case GREY1:
+	case GREY2:
+	case GREY4:
+		mk8bit(icon, 1);
+		break;
+	default:
+		mk8bit(icon, 0);
+		break;
+	}
+	icon->bits = 8;
+	icon->file = file;
+
+	/* create xor/and masks, minimizing bits per pixel */
+	mkxorand(icon, icon->img->chan == GREY8);
+}
+
+void*
+emalloc(int len)
+{
+	void *x;
+
+	x = mallocz(len, 1);
+	if(x == nil)
+		sysfatal("memory: %r");
+	return x;
+}
+
+/* convert to 8 bit */
+void
+mk8bit(Icon *icon, int grey)
+{
+	Image *img;
+
+	img = allocimage(display, icon->img->r, grey ? GREY8 : CMAP8, 0, DNofill);
+	if(img == nil)
+		sysfatal("can't allocimage: %r");
+	draw(img, img->r, icon->img, nil, ZP);
+	freeimage(icon->img);
+	icon->img = img;
+}
+
+/* make xor and and mask */
+void
+mkxorand(Icon *icon, int grey)
+{
+	int i, x, y, s, sa;
+	uchar xx[256];
+	uchar *data, *p, *e;
+	int ndata;
+	uchar *mp;
+	int ncolor;
+	ulong color;
+	int bits;
+	uchar andbyte, xorbyte;
+	uchar *ato, *xto;
+	int xorrl, andrl;
+
+	ndata = icon->h * icon->w;
+	data = emalloc(ndata);
+	if(unloadimage(icon->img, icon->img->r, data, ndata) < 0)
+		sysfatal("can't unload %s: %r", icon->file);
+	e = data + ndata;
+
+	/* find colors used */
+	memset(xx, 0, sizeof xx);
+	for(p = data; p < e; p++)
+		xx[*p]++;
+
+	/* count the colors and create a mapping from plan 9 */
+	mp = icon->map;
+	ncolor = 0;
+	for(i = 0; i < 256; i++){
+		if(xx[i] == 0)
+			continue;
+		if(grey){
+			*mp++ = i;
+			*mp++ = i;
+			*mp++ = i;
+			*mp++ = 0;
+		} else {
+			color = cmap2rgb(i);
+			*mp++ = color;
+			*mp++ = color>>8;
+			*mp++ = color>>16;
+			*mp++ = 0;
+		}
+		xx[i] = ncolor;
+		ncolor++;
+	}
+
+	/* get minimum number of pixels per bit (with a color map) */
+	if(ncolor <= 2){
+		ncolor = 2;
+		bits = 1;
+	} else if(ncolor <= 4){
+		ncolor = 4;
+		bits = 2;
+	} else if(ncolor <= 16){
+		ncolor = 16;
+		bits = 4;
+	} else {
+		ncolor = 256;
+		bits = 8;
+	}
+	icon->bits = bits;
+	icon->ncolor = ncolor;
+
+	/* the xor mask rows are justified to a 32 bit boundary */
+	/* the and mask is 1 bit grey */
+	xorrl = 4*((bits*icon->w + 31)/32);
+	andrl = 4*((icon->w + 31)/32);
+	icon->xor = emalloc(xorrl * icon->h);
+	icon->and = emalloc(andrl * icon->h);
+	icon->xorlen = xorrl*icon->h;
+	icon->andlen = andrl*icon->h;
+
+	/* make both masks.  they're upside down relative to plan9 ones */
+	p = data;
+	for(y = 0; y < icon->h; y++){
+		andbyte = 0;
+		xorbyte = 0;
+		sa = s = 0;
+		xto = icon->xor + (icon->h-1-y)*xorrl;
+		ato = icon->and + (icon->h-1-y)*andrl;
+		for(x = 0; x < icon->w; x++){
+			xorbyte <<= bits;
+			xorbyte |= xx[*p];
+			s += bits;
+			if(s == 8){
+				*xto++ = xorbyte;
+				xorbyte = 0;
+				s = 0;
+			}
+			andbyte <<= 1;
+			if(*p == 0xff)
+				andbyte |= 1;
+			sa++;
+			if(sa == 0){
+				*ato++ = andbyte;
+				sa = 0;
+				andbyte = 0;
+			}
+			p++;
+		}
+	}
+	free(data);
+}