Why not?
diff --git a/src/cmd/jpg/bmp.c b/src/cmd/jpg/bmp.c
new file mode 100644
index 0000000..f7e07a0
--- /dev/null
+++ b/src/cmd/jpg/bmp.c
@@ -0,0 +1,210 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+int		cflag = 0;
+int		dflag = 0;
+int		eflag = 0;
+int		nineflag = 0;
+int		threeflag = 0;
+int		output = 0;
+ulong		outchan = CMAP8;
+int		defaultcolor = 1;
+Image		*image;
+
+enum{
+	Border	= 2,
+	Edge	= 5
+};
+
+char	*show(int, char*);
+
+Rawimage** readbmp(int fd, int colorspace);
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "bmp: can't reattach to window\n");
+		exits("resize");
+	}
+	if(image == nil)
+		return;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(image->r);
+	r.max.y = r.min.y+Dy(image->r);
+	border(screen, r, -Border, nil, ZP);
+	draw(screen, r, image, nil, image->r.min);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i;
+	char *err;
+
+	ARGBEGIN{
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: bmp -39cdektv  [file.bmp ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>");
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "bmp: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i]);
+				close(fd);
+			}
+			if((nineflag || cflag) && argc>1 && err==nil){
+				fprint(2, "bmp: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+int
+init(void)
+{
+	static int inited;
+
+	if(inited == 0){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "bmp: initdraw failed: %r");
+			return -1;
+		}
+		einit(Ekeyboard|Emouse);
+		inited++;
+	}
+	return 1;
+}
+
+char*
+show(int fd, char *name)
+{
+	Rawimage **array, *r, *c;
+	Image *i;
+	int j, ch;
+	char buf[32];
+
+	array = readbmp(fd, CRGB);
+	if(array == nil || array[0]==nil){
+		fprint(2, "bmp: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	if(!dflag){
+		if(init() < 0)
+			return "initdraw";
+		if(defaultcolor && screen->depth>8)
+			outchan = RGB24;
+	}
+	r = array[0];
+	if(outchan == CMAP8)
+		c = torgbv(r, !eflag);
+	else{
+		if(outchan==GREY8 || (r->chandesc==CY && threeflag==0))
+			c = totruecolor(r, CY);
+		else
+			c = totruecolor(r, CRGB24);
+	}
+	if(c == nil){
+		fprint(2, "bmp: converting %s to local format failed: %r\n", name);
+		return "torgbv";
+	}
+	if(!dflag){
+		if(r->chandesc == CY)
+			i = allocimage(display, c->r, GREY8, 0, 0);
+		else
+			i = allocimage(display, c->r, outchan, 0, 0);
+		if(i == nil){
+			fprint(2, "bmp: allocimage %s failed: %r\n", name);
+			return "allocimage";
+		}
+		if(loadimage(i, i->r, c->chans[0], c->chanlen) < 0){
+			fprint(2, "bmp: loadimage %s failed: %r\n", name);
+			return "loadimage";
+		}
+		image = i;
+		eresized(0);
+		if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)
+			exits(nil);
+		draw(screen, screen->clipr, display->white, nil, ZP);
+		image = nil;
+		freeimage(i);
+	}
+	if(nineflag){
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			c->r.min.x, c->r.min.y, c->r.max.x, c->r.max.y);
+		if(write(1, c->chans[0], c->chanlen) != c->chanlen){
+			fprint(2, "bmp: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(writerawimage(1, c) < 0){
+			fprint(2, "bmp: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+	for(j=0; j<r->nchans; j++)
+		free(r->chans[j]);
+	free(r);
+	free(array);
+	if(c){
+		free(c->chans[0]);
+		free(c);
+	}
+	return nil;
+}
diff --git a/src/cmd/jpg/bmp.h b/src/cmd/jpg/bmp.h
new file mode 100644
index 0000000..ca00368
--- /dev/null
+++ b/src/cmd/jpg/bmp.h
@@ -0,0 +1,37 @@
+
+#define BMP_RGB      	0
+#define BMP_RLE8     	1
+#define BMP_RLE4     	2
+#define BMP_BITFIELDS	3
+
+typedef struct {
+	uchar red;
+	uchar green;
+	uchar blue;
+	uchar alpha;
+} Rgb;
+
+typedef struct {
+        short	type;
+        long	size;
+        short	reserved1;
+        short	reserved2;
+        long	offbits;
+} Filehdr;
+
+typedef struct {
+	long	size;		/* Size of the Bitmap-file */
+	long	lReserved;	/* Reserved */
+	long	dataoff;	/* Picture data location */
+	long	hsize;		/* Header-Size */
+	long	width;		/* Picture width (pixels) */
+	long	height;		/* Picture height (pixels) */
+	short	planes;		/* Planes (must be 1) */
+	short	bpp;		/* Bits per pixel (1, 4, 8 or 24) */
+	long	compression;	/* Compression mode */
+	long	imagesize;	/* Image size (bytes) */
+	long	hres;		/* Horizontal Resolution (pels/meter) */
+	long	vres;		/* Vertical Resolution (pels/meter) */
+	long	colours;	/* Used Colours (Col-Table index) */
+	long	impcolours;	/* Important colours (Col-Table index) */
+} Infohdr;
diff --git a/src/cmd/jpg/close.c b/src/cmd/jpg/close.c
new file mode 100644
index 0000000..7a260c1
--- /dev/null
+++ b/src/cmd/jpg/close.c
@@ -0,0 +1,121 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+float c1 = 1.402;
+float c2 = 0.34414;
+float c3 = 0.71414;
+float c4 = 1.772;
+
+
+int
+closest(int Y, int Cb, int Cr)
+{
+	double r, g, b;
+	double diff, min;
+	int rgb, R, G, B, v, i;
+	int y1, cb1, cr1;
+
+	Cb -= 128;
+	Cr -= 128;
+	r = Y+c1*Cr;
+	g = Y-c2*Cb-c3*Cr;
+	b = Y+c4*Cb;
+
+//print("YCbCr: %d %d %d, RGB: %g %g %g\n", Y, Cb, Cr, r, g, b);
+
+	min = 1000000.;
+	v = 1000;
+	for(i=0; i<256; i++){
+		rgb =  cmap2rgb(i);
+		R = (rgb >> 16) & 0xFF;
+		G = (rgb >> 8) & 0xFF;
+		B = (rgb >> 0) & 0xFF;
+		diff = (R-r)*(R-r) + (G-g)*(G-g) + (B-b)*(B-b);
+//		y1 = 0.5870*G + 0.114*B + 0.299*R;
+//		cb1 = (B-y1)/1.772;
+//		cr1 = (R-y1)/1.402;
+		if(diff < min){
+//			if(Y==0 && y1!=0)
+//				continue;
+//			if(Y==256-16 && y1<256-16)
+//				continue;
+//			if(Cb==0 && cb1!=0)
+//				continue;
+//			if(Cb==256-16 && cb1<256-16)
+//				continue;
+//			if(Cr==0 && cr1!=0)
+//				continue;
+//			if(Cr==256-16 && cr1<256-16)
+//				continue;
+//print("%d %d %d\n", R, G, B);
+			min = diff;
+			v = i;
+		}
+	}
+	if(v > 255)
+		abort();
+	return v;
+}
+
+#define 	SHIFT	5
+#define	INC		(1<<SHIFT)
+
+typedef struct Color Color;
+
+struct Color
+{
+	int		col;
+	Color	*next;
+};
+
+Color	*col[INC*INC*INC];
+
+void
+add(int c, int y, int cb, int cr)
+{
+	Color *cp;
+
+	y >>= 8-SHIFT;
+	cb >>= 8-SHIFT;
+	cr >>= 8-SHIFT;
+	cp = col[cr+INC*(cb+INC*y)];
+	while(cp != nil){
+		if(cp->col == c)
+			return;
+		cp = cp->next;
+	}
+	cp = malloc(sizeof(Color));
+	cp->col = c;
+	cp->next = col[cr+INC*(cb+INC*y)];
+	col[cr+INC*(cb+INC*y)] = cp;
+}
+
+void
+main(void)
+{
+	int y, cb, cr, n;
+	Color *cp;
+
+	for(y=0; y<256; y++){
+		for(cb=0; cb<256; cb++)
+			for(cr=0;cr<256;cr++)
+				add(closest(y, cb, cr), y, cb, cr);
+		fprint(2, "%d done\n", y);
+	}
+	for(y=0; y<INC*INC*INC; y++){
+		n = 0;
+		cp = col[y];
+		while(cp != nil){
+			n++;
+			cp = cp->next;
+		}
+		cp = col[y];
+		while(cp != nil){
+			n++;
+			print("%d ", cp->col);
+			cp = cp->next;
+		}
+		print("\n");
+	}
+}
diff --git a/src/cmd/jpg/gif.c b/src/cmd/jpg/gif.c
new file mode 100644
index 0000000..f992701
--- /dev/null
+++ b/src/cmd/jpg/gif.c
@@ -0,0 +1,421 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+int		cflag = 0;
+int		dflag = 0;
+int		eflag = 0;
+int		nineflag = 0;
+int		threeflag = 0;
+int		output = 0;
+ulong	outchan = CMAP8;
+Image	**allims;
+Image	**allmasks;
+int		which;
+int		defaultcolor = 1;
+
+enum{
+	Border	= 2,
+	Edge		= 5
+};
+
+char	*show(int, char*);
+
+Rectangle
+imager(void)
+{
+	Rectangle r;
+
+	if(allims==nil || allims[0]==nil)
+		return screen->r;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(allims[0]->r);
+	r.max.y = r.min.y+Dy(allims[0]->r);
+	return r;
+}
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "gif: can't reattach to window\n");
+		exits("resize");
+	}
+	if(allims==nil || allims[which]==nil)
+		return;
+	r = imager();
+	border(screen, r, -Border, nil, ZP);
+	r.min.x += allims[which]->r.min.x - allims[0]->r.min.x;
+	r.min.y += allims[which]->r.min.y - allims[0]->r.min.y;
+	drawop(screen, r, allims[which], allmasks[which], allims[which]->r.min, S);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i;
+	char *err;
+
+	ARGBEGIN{
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: gif -39cdektv  [file.gif ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>");
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "gif: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i]);
+				close(fd);
+			}
+			if(output && argc>1 && err==nil){
+				fprint(2, "gif: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+Image*
+transparency(Rawimage *r, char *name)
+{
+	Image *i;
+	int j, index;
+	uchar *pic, *mpic, *mask;
+
+	if((r->gifflags&TRANSP) == 0)
+		return nil;
+	i = allocimage(display, r->r, GREY8, 0, 0);
+	if(i == nil){
+		fprint(2, "gif: allocimage for mask of %s failed: %r\n", name);
+		return nil;
+	}
+	pic = r->chans[0];
+	mask = malloc(r->chanlen);
+	if(mask == nil){
+		fprint(2, "gif: malloc for mask of %s failed: %r\n", name);
+		freeimage(i);
+		return nil;
+	}
+	index = r->giftrindex;
+	mpic = mask;
+	for(j=0; j<r->chanlen; j++)
+		if(*pic++ == index)
+			*mpic++ = 0;
+		else
+			*mpic++ = 0xFF;
+	if(loadimage(i, i->r, mask, r->chanlen) < 0){
+		fprint(2, "gif: loadimage for mask of %s failed: %r\n", name);
+		free(mask);
+		freeimage(i);
+		return nil;
+	}
+	free(mask);
+	return i;
+}
+
+/* interleave alpha values of 0xFF in data stream. alpha value comes first, then b g r */
+uchar*
+expand(uchar *u, int chanlen, int nchan)
+{
+	int j, k;
+	uchar *v, *up, *vp;
+
+	v = malloc(chanlen*(nchan+1));
+	if(v == nil){
+		fprint(2, "gif: malloc fails: %r\n");
+		exits("malloc");
+	}
+	up = u;
+	vp = v;
+	for(j=0; j<chanlen; j++){
+		*vp++ = 0xFF;
+		for(k=0; k<nchan; k++)
+			*vp++ = *up++;
+	}
+	return v;
+}
+
+void
+addalpha(Rawimage *i)
+{
+	char buf[32];
+
+	switch(outchan){
+	case CMAP8:
+		i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
+		i->chanlen = 2*(i->chanlen/1);
+		i->chandesc = CRGBVA16;
+		outchan = CHAN2(CMap, 8, CAlpha, 8);
+		break;
+
+	case GREY8:
+		i->chans[0] = expand(i->chans[0], i->chanlen/1, 1);
+		i->chanlen = 2*(i->chanlen/1);
+		i->chandesc = CYA16;
+		outchan = CHAN2(CGrey, 8, CAlpha, 8);
+		break;
+
+	case RGB24:
+		i->chans[0] = expand(i->chans[0], i->chanlen/3, 3);
+		i->chanlen = 4*(i->chanlen/3);
+		i->chandesc = CRGBA32;
+		outchan = RGBA32;
+		break;
+
+	default:
+		chantostr(buf, outchan);
+		fprint(2, "gif: can't add alpha to type %s\n", buf);
+		exits("err");
+	}
+}
+
+/*
+ * Called only when writing output.  If the output is RGBA32,
+ * we must write four bytes per pixel instead of two.
+ * There's always at least two: data plus alpha.
+ * r is used only for reference; the image is already in c.
+ */
+void
+whiteout(Rawimage *r, Rawimage *c)
+{
+	int i, trindex;
+	uchar *rp, *cp;
+
+	rp = r->chans[0];
+	cp = c->chans[0];
+	trindex = r->giftrindex;
+	if(outchan == RGBA32)
+		for(i=0; i<r->chanlen; i++){
+			if(*rp == trindex){
+				*cp++ = 0x00;
+				*cp++ = 0xFF;
+				*cp++ = 0xFF;
+				*cp++ = 0xFF;
+			}else{
+				*cp++ = 0xFF;
+				cp += 3;
+			}
+			rp++;
+		}
+	else
+		for(i=0; i<r->chanlen; i++){
+			if(*rp == trindex){
+				*cp++ = 0x00;
+				*cp++ = 0xFF;
+			}else{
+				*cp++ = 0xFF;
+				cp++;
+			}
+			rp++;
+		}
+}
+
+int
+init(void)
+{
+	static int inited;
+
+	if(inited == 0){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "gif: initdraw failed: %r\n");
+			return -1;
+		}
+		einit(Ekeyboard|Emouse);
+		inited++;
+	}
+	return 1;
+}
+
+char*
+show(int fd, char *name)
+{
+	Rawimage **images, **rgbv;
+	Image **ims, **masks;
+	int j, k, n, ch, nloop, loopcount, dt;
+	char *err;
+	char buf[32];
+
+	err = nil;
+	images = readgif(fd, CRGB);
+	if(images == nil){
+		fprint(2, "gif: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	for(n=0; images[n]; n++)
+		;
+	ims = malloc((n+1)*sizeof(Image*));
+	masks = malloc((n+1)*sizeof(Image*));
+	rgbv = malloc((n+1)*sizeof(Rawimage*));
+	if(masks==nil || rgbv==nil || ims==nil){
+		fprint(2, "gif: malloc of masks for %s failed: %r\n", name);
+		err = "malloc";
+		goto Return;
+	}
+	memset(masks, 0, (n+1)*sizeof(Image*));
+	memset(ims, 0, (n+1)*sizeof(Image*));
+	memset(rgbv, 0, (n+1)*sizeof(Rawimage*));
+	if(!dflag){
+		if(init() < 0){
+			err = "initdraw";
+			goto Return;
+		}
+		if(defaultcolor && screen->depth>8)
+			outchan = RGB24;
+	}
+
+	for(k=0; k<n; k++){
+		if(outchan == CMAP8)
+			rgbv[k] = torgbv(images[k], !eflag);
+		else{
+			if(outchan==GREY8 || (images[k]->chandesc==CY && threeflag==0))
+				rgbv[k] = totruecolor(images[k], CY);
+			else
+				rgbv[k] = totruecolor(images[k], CRGB24);
+		}
+		if(rgbv[k] == nil){
+			fprint(2, "gif: converting %s to local format failed: %r\n", name);
+			err = "torgbv";
+			goto Return;
+		}
+		if(!dflag){
+			masks[k] = transparency(images[k], name);
+			if(rgbv[k]->chandesc == CY)
+				ims[k] = allocimage(display, rgbv[k]->r, GREY8, 0, 0);
+			else
+				ims[k] = allocimage(display, rgbv[k]->r, outchan, 0, 0);
+			if(ims[k] == nil){
+				fprint(2, "gif: allocimage %s failed: %r\n", name);
+				err = "allocimage";
+				goto Return;
+			}
+			if(loadimage(ims[k], ims[k]->r, rgbv[k]->chans[0], rgbv[k]->chanlen) < 0){
+				fprint(2, "gif: loadimage %s failed: %r\n", name);
+				err = "loadimage";
+				goto Return;
+			}
+		}
+	}
+
+	allims = ims;
+	allmasks = masks;
+	loopcount = images[0]->gifloopcount;
+	if(!dflag){
+		nloop = 0;
+		do{
+			for(k=0; k<n; k++){
+				which = k;
+				eresized(0);
+				dt = images[k]->gifdelay*10;
+				if(dt < 50)
+					dt = 50;
+				while(n==1 || ecankbd()){
+					if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)	/* an odd, democratic list */
+						exits(nil);
+					if(ch == '\n')
+						goto Out;
+				}sleep(dt);
+			}
+			/* loopcount -1 means no loop (this code's rule), loopcount 0 means loop forever (netscape's rule)*/
+		}while(loopcount==0 || ++nloop<loopcount);
+		/* loop count has run out */
+		ekbd();
+    Out:
+		drawop(screen, screen->clipr, display->white, nil, ZP, S);
+	}
+	if(n>1 && output)
+		fprint(2, "gif: warning: only writing first image in %d-image GIF %s\n", n, name);
+	if(nineflag){
+		if(images[0]->gifflags&TRANSP){
+			addalpha(rgbv[0]);
+			whiteout(images[0], rgbv[0]);
+		}
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			rgbv[0]->r.min.x, rgbv[0]->r.min.y, rgbv[0]->r.max.x, rgbv[0]->r.max.y);
+		if(write(1, rgbv[0]->chans[0], rgbv[0]->chanlen) != rgbv[0]->chanlen){
+			fprint(2, "gif: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(images[0]->gifflags&TRANSP){
+			addalpha(rgbv[0]);
+			whiteout(images[0], rgbv[0]);
+		}
+		if(writerawimage(1, rgbv[0]) < 0){
+			fprint(2, "gif: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+
+    Return:
+	allims = nil;
+	allmasks = nil;
+	for(k=0; images[k]; k++){
+		for(j=0; j<images[k]->nchans; j++)
+			free(images[k]->chans[j]);
+		free(images[k]->cmap);
+		if(rgbv[k])
+			free(rgbv[k]->chans[0]);
+		freeimage(ims[k]);
+		freeimage(masks[k]);
+		free(images[k]);
+		free(rgbv[k]);
+	}
+	free(images);
+	free(masks);
+	free(ims);
+	return err;
+}
diff --git a/src/cmd/jpg/ico.c b/src/cmd/jpg/ico.c
new file mode 100644
index 0000000..31806af
--- /dev/null
+++ b/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);
+}
diff --git a/src/cmd/jpg/imagefile.h b/src/cmd/jpg/imagefile.h
new file mode 100644
index 0000000..592c5a2
--- /dev/null
+++ b/src/cmd/jpg/imagefile.h
@@ -0,0 +1,82 @@
+typedef struct Rawimage Rawimage;
+
+struct Rawimage
+{
+	Rectangle	r;
+	uchar	*cmap;
+	int		cmaplen;
+	int		nchans;
+	uchar	*chans[4];
+	int		chandesc;
+	int		chanlen;
+
+	int		fields;    	/* defined by format */
+	int		gifflags;	/* gif only; graphics control extension flag word */
+	int		gifdelay;	/* gif only; graphics control extension delay in cs */
+	int		giftrindex;	/* gif only; graphics control extension transparency index */
+	int		gifloopcount;	/* number of times to loop in animation; 0 means forever */
+};
+
+enum
+{
+	/* Channel descriptors */
+	CRGB	= 0,	/* three channels, no map */
+	CYCbCr	= 1,	/* three channels, no map, level-shifted 601 color space */
+	CY	= 2,	/* one channel, luminance */
+	CRGB1	= 3,	/* one channel, map present */
+	CRGBV	= 4,	/* one channel, map is RGBV, understood */
+	CRGB24	= 5,	/* one channel in correct data order for loadimage(RGB24) */
+	CRGBA32	= 6,	/* one channel in correct data order for loadimage(RGBA32) */
+	CYA16	= 7,	/* one channel in correct data order for loadimage(Grey8+Alpha8) */
+	CRGBVA16= 8,	/* one channel in correct data order for loadimage(CMAP8+Alpha8) */
+
+	/* GIF flags */
+	TRANSP	= 1,
+	INPUT	= 2,
+	DISPMASK = 7<<2
+};
+
+
+enum{	/* PNG flags */
+	II_GAMMA =	1 << 0,
+	II_COMMENT =	1 << 1,
+};
+
+typedef struct ImageInfo {
+	ulong	fields_set;
+	double	gamma;
+	char	*comment;
+} ImageInfo;
+
+
+Rawimage**	readjpg(int, int);
+Rawimage**	Breadjpg(Biobuf *b, int);
+Rawimage**	readpng(int, int);
+Rawimage**	Breadpng(Biobuf *b, int);
+Rawimage**	readgif(int, int);
+Rawimage**	readpixmap(int, int);
+Rawimage*	torgbv(Rawimage*, int);
+Rawimage*	totruecolor(Rawimage*, int);
+int		writerawimage(int, Rawimage*);
+void*		_remaperror(char*, ...);
+
+#ifndef _MEMDRAW_H_
+typedef struct Memimage Memimage;	/* avoid necessity to include memdraw.h */
+#endif
+
+char*		startgif(Biobuf*, Image*, int);
+char*		writegif(Biobuf*, Image*, char*, int, int);
+void		endgif(Biobuf*);
+char*		memstartgif(Biobuf*, Memimage*, int);
+char*		memwritegif(Biobuf*, Memimage*, char*, int, int);
+void		memendgif(Biobuf*);
+Image*		onechan(Image*);
+Memimage*	memonechan(Memimage*);
+
+char*		writeppm(Biobuf*, Image*, char*);
+char*		memwriteppm(Biobuf*, Memimage*, char*);
+Image*		multichan(Image*);
+Memimage*	memmultichan(Memimage*);
+
+char*		memwritepng(Biobuf*, Memimage*, ImageInfo*);
+extern int drawlog2[];
diff --git a/src/cmd/jpg/jpegdump.c b/src/cmd/jpg/jpegdump.c
new file mode 100644
index 0000000..0b132cb
--- /dev/null
+++ b/src/cmd/jpg/jpegdump.c
@@ -0,0 +1,346 @@
+/* jpeg parser by tom szymanski */
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+
+/* subroutines done by macros */
+#define min(A,B)	((A)<(B) ? (A) : (B))
+#define max(A,B)	((A)>(B) ? (A) : (B))
+#define maxeql(A,B)	if (A < (B)) A = (B);
+#define mineql(A,B)	if (A > (B)) A = (B);
+#define eatarg0		(argc--, argv++)
+#define arrayLength(A) ((sizeof A)/ (sizeof A[0]))
+
+FILE *infile;
+char *fname;
+
+/* Routines to print error messages of varying severity */
+
+/* externally visible variables */
+int   warncnt;
+char *myname;
+
+void getname (char *arg) {
+	/* Save name of invoking program for use by error routines */
+	register char *p;
+	p = strrchr (arg, '/');
+	if (p == NULL)
+		myname = arg;
+	else
+		myname = ++p;
+}
+
+static void introduction (void) {
+	warncnt++;
+	fflush (stdout);
+	if (myname != NULL)
+		fprintf (stderr, "%s: ", myname);
+}
+
+void warn (char *fmt, ...) {
+	va_list args;
+	introduction ();
+	va_start (args, fmt);
+	vfprintf (stderr, fmt, args);
+	va_end (args);
+	fputc ('\n', stderr);
+	fflush (stderr);
+}
+
+void quit (char *fmt, ...) {
+	va_list args;
+	introduction ();
+	va_start (args, fmt);
+	vfprintf (stderr, fmt, args);
+	va_end (args);
+	fputc ('\n', stderr);
+	fflush (stderr);
+	exit (1);
+}
+
+void fatal (char *fmt, ...) {
+	va_list args;
+	introduction ();
+	va_start (args, fmt);
+	vfprintf (stderr, fmt, args);
+	va_end (args);
+	fprintf (stderr, "\nbetter get help!\n");
+	fflush (stderr);
+	abort ();
+}
+
+int toption = 0;
+int dqt[16][64];
+
+int get1 (void) {
+	unsigned char x;
+	if (fread(&x, 1, 1, infile) == 0)
+		quit ("unexpected EOF");
+	return x;
+}
+
+int get2 (void) {
+	int x;
+
+	x = get1() << 8;
+	return x | get1();
+}
+
+void eatmarker (int kind) {
+	int l, c;
+	l = get2();
+	printf ("%02x len=%d\n", kind, l);
+	for (l -= 2; l > 0; l--)
+		get1();
+}
+
+char *sofName[16] = {
+	"Baseline sequential DCT - Huffman coding",
+	"Extended sequential DCT - Huffman coding",
+	"Progressive DCT - Huffman coding",
+	"Lossless - Huffman coding",
+	"4 is otherwise used",
+	"Sequential DCT - differential Huffman coding",
+	"Progressive DCT - differential Huffman coding",
+	"Lossless - differential Huffman coding",
+	"8 is reserved",
+	"Extended Sequential DCT - arithmetic coding",
+	"Progressive DCT - arithmetic coding",
+	"Lossless - arithmetic coding",
+	"c is otherwise used",
+	"Sequential DCT - differential arithmetic coding",
+	"Progressive DCT - differential arithmetic coding",
+	"Lossless - differential arithmetic coding",
+};
+
+void get_sof (int kind) {
+	int i, length, height, width, precision, ncomponents;
+	int id, sf, tab;
+	length = get2();
+	precision = get1();
+	height = get2();
+	width = get2();
+	ncomponents = get1();
+	printf ("SOF%d:\t%s\n", kind - 0xc0, sofName[kind - 0xc0]);
+	printf ("\t%d wide, %d high, %d deep, %d components\n",
+		width, height, precision, ncomponents);
+	for (i = 0; i < ncomponents; i++) {
+		id = get1();
+		sf = get1();
+		tab = get1();
+		printf ("\tcomponent %d: %d hsample, %d vsample, quantization table %d\n",
+			id, sf >> 4, sf & 0xf, tab);
+	}		
+}
+
+void get_com (int kind) {
+	int l, c;
+	l = get2();
+	printf ("COM len=%d '", l);
+	for (l -= 2; l > 0; l--)
+		putchar (c = get1());
+	printf ("'\n");
+}
+
+void get_app (int kind) {
+	int l, c, first;
+	char buf[6];
+	int nbuf, nok;
+	l = get2();
+	printf ("APP%d len=%d\n", kind - 0xe0, l);
+	nbuf = 0;
+	nok = 0;
+	first = 1;
+	/* dump printable strings in comment */
+	for (l -= 2; l > 0; l--){
+		c = get1();
+		if(isprint(c)){
+			if(nbuf >= sizeof buf){
+				if(!first && nbuf == nok)
+					printf(" ");
+				printf("%.*s", nbuf, buf);
+				nbuf = 0;
+				first = 0;
+			}
+			buf[nbuf++] = c;
+			nok++;
+		}else{
+			if(nok >= sizeof buf)
+				if(nbuf > 0)
+					printf("%.*s", nbuf, buf);
+			nbuf = 0;
+			nok = 0;
+		}
+	}
+	if(nok >= sizeof buf)
+		if(nbuf > 0){
+			if(!first && nbuf == nok)
+				printf(" ");
+			printf("%.*s", nbuf, buf);
+		}
+}
+
+void get_dac (int kind) {
+	eatmarker (kind);
+}
+
+int get1dqt (void) {
+	int t, p, i, *tab;
+	t = get1();
+	p = t >> 4;
+	t = t & 0xf;
+	printf ("DQT:\tp = %d, table = %d\n", p, t);
+	tab = &dqt[t][0];
+	for (i = 0; i < 64; i++)
+		tab[i] = p ? get2() : get1();
+	if (toption) {
+		for (i = 0; i < 64; i++)
+			printf ("\t%q[%02d] = %d\n", i, tab[i]);
+	}
+	return p ? 65 : 129;
+}
+
+void get_dqt (int kind) {
+	int length;
+	length = get2() - 2;
+	while (length > 0)
+		length -= get1dqt();
+}
+
+int get1dht (void) {
+	int l, tcth, p, i, j, v[16], vv[16][256];
+	tcth = get1();
+	printf ("DHT:\tclass = %d, table = %d\n", tcth >> 4, tcth & 0xf);
+	for (i = 0; i < 16; i++)
+		v[i] = get1();
+	l = 17;
+	for (i = 0; i < 16; i++)
+		for (j = 0; j < v[i]; j++) {
+			vv[i][j] = get1();
+			l += 1;
+		}
+	if (toption) {
+		for (i = 0; i < 16; i++)
+			printf ("\t%l[%02d] = %d\n", i+1, v[i]);
+		for (i = 0; i < 16; i++)
+			for (j = 0; j < v[i]; j++)
+				printf ("\t%v[%02d,%02d] = %d\n", i+1, j+1, vv[i][j]);
+	}
+	return l;
+}
+
+void get_dht (int kind) {
+	int length;
+	length = get2() - 2;
+	while (length > 0)
+		length -= get1dht();
+}
+
+void get_sos (int kind) {
+	int i, length, ncomponents, id, dcac, ahal;
+	length = get2();
+	ncomponents = get1();
+	printf ("SOS:\t%d components\n", ncomponents);
+	for (i = 0; i < ncomponents; i++) {
+		id = get1();
+		dcac = get1();
+		printf ("\tcomponent %d: %d DC, %d AC\n", id, dcac >> 4, dcac & 0xf);
+	}
+	printf ("\tstart spectral %d\n", get1());
+	printf ("\tend spectral %d\n", get1());
+	ahal = get1();
+	printf ("\tah = %d, al = %d\n", ahal >> 4, ahal &0xf);
+}
+
+main (int argc, char *argv[]) {
+	int l, stuff, i, j, c;
+	while (argc > 1 && argv[1][0] == '-') {
+		switch (argv[1][1]) {
+		case 't':
+			toption = 1;
+			break;
+		default:
+			warn ("bad option '%c'", argv[1][1]);
+		}
+		eatarg0;
+	}
+	fname = argv[1];
+	infile = fopen (fname, "r");
+	if (infile == NULL)
+		quit ("can't open %s\n", fname);
+    Start:
+//	if (get1() != 0xff || get1() != 0xd8)
+//		quit ("not JFIF");
+//	printf ("SOI\n");
+//	get_app (0xe0);
+	for (;;) {
+		c = get1();
+		if (c != 0xff)
+			quit ("expected marker, got %2x", c);
+		do {
+			c = get1();
+		} while (c == 0xff);
+marker:
+		switch (c) {
+		case 0xc0: case 0xc1: case 0xc2: case 0xc3:
+		case 0xc5: case 0xc6: case 0xc7:
+		case 0xc8: case 0xc9: case 0xca: case 0xcb:
+		case 0xcd: case 0xce: case 0xcf:
+			get_sof (c);
+			break;
+		case 0xc4:
+			get_dht (c);
+			break;
+		case 0xcc:
+			get_dac (c);
+			break;
+		case 0xd8:
+			printf ("SOI\n");
+			break;
+		case 0xe0: case 0xe1: case 0xe2: case 0xe3: 
+		case 0xe4: case 0xe5: case 0xe6: case 0xe7: 
+		case 0xe8: case 0xe9: case 0xea: case 0xeb: 
+		case 0xec: case 0xed: case 0xee: case 0xef: 
+			get_app(c);
+			break;
+		case 0xda:
+			get_sos (c);
+			goto newentropy;
+		case 0xdb:
+			get_dqt (c);
+			break;
+		case 0xfe:
+			get_com (c);
+			break;
+		case 0xd9:
+			printf ("EOI\n");
+			if((c=getc(infile)) == EOF)
+				exit(0);
+			ungetc(c, infile);
+			goto Start;
+		default:
+			eatmarker (c);
+		}
+		continue;
+newentropy:
+		l = stuff = 0;
+entropy:
+		while ((c = get1()) != 0xff)
+			l += 1;
+		while (c == 0xff)
+			c = get1();
+		if (c == 0) {
+			stuff += 1;
+			goto entropy;
+		}
+		printf ("sequence length %d with %d stuffs\n", l, stuff);
+		if (0xd0 <= c && c <= 0xd7) {
+			printf ("restart %d\n", c - 0xd0);
+			goto newentropy;
+		}
+		goto marker;
+	}
+}
diff --git a/src/cmd/jpg/jpg.c b/src/cmd/jpg/jpg.c
new file mode 100644
index 0000000..cf438a9
--- /dev/null
+++ b/src/cmd/jpg/jpg.c
@@ -0,0 +1,342 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+int		cflag = 0;
+int		dflag = 0;
+int		eflag = 0;
+int		jflag = 0;
+int		fflag = 0;
+int		Fflag = 0;
+int		nineflag = 0;
+int		threeflag = 0;
+int		colorspace = CYCbCr;	/* default for 8-bit displays: combine color rotation with dither */
+int		output = 0;
+ulong	outchan = CMAP8;
+Image	*image;
+int		defaultcolor = 1;
+
+enum{
+	Border	= 2,
+	Edge		= 5
+};
+
+char	*show(int, char*, int);
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "jpg: can't reattach to window\n");
+		exits("resize");
+	}
+	if(image == nil)
+		return;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(image->r);
+	r.max.y = r.min.y+Dy(image->r);
+	border(screen, r, -Border, nil, ZP);
+	draw(screen, r, image, nil, image->r.min);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i, yflag;
+	char *err;
+	char buf[12+1];
+
+	yflag = 0;
+	ARGBEGIN{
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'F':
+		Fflag++;	/* make a movie */
+		fflag++;	/* merge two fields per image */
+		break;
+	case 'f':
+		fflag++;	/* merge two fields per image */
+		break;
+	case 'J':		/* decode jpeg only; no display or remap (for debugging, etc.) */
+		jflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'r':
+		colorspace = CRGB;
+		break;
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case 'y':	/* leave it in CYCbCr; for debugging only */
+		yflag = 1;
+		colorspace = CYCbCr;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: jpg -39cdefFkJrtv [file.jpg ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	if(yflag==0 && dflag==0 && colorspace==CYCbCr){	/* see if we should convert right to RGB */
+		fd = open("/dev/screen", OREAD);
+		if(fd > 0){
+			buf[12] = '\0';
+			if(read(fd, buf, 12)==12 && chantodepth(strtochan(buf))>8)
+				colorspace = CRGB;
+			close(fd);
+		}
+	}
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>", outchan);
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "jpg: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i], outchan);
+				close(fd);
+			}
+			if((nineflag || cflag) && argc>1 && err==nil){
+				fprint(2, "jpg: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+Rawimage**
+vidmerge(Rawimage **aa1, Rawimage **aa2)
+{
+	Rawimage **aao, *ao, *a1, *a2;
+	int i, c, row, col;
+
+	aao = nil;
+	for (i = 0; aa1[i]; i++) {
+
+		a1 = aa1[i];
+		a2 = aa2[i];
+		if (a2 == nil){
+			fprint(2, "jpg: vidmerge: unequal lengths\n");
+			return nil;
+		}
+		aao = realloc(aao, (i+2)*sizeof(Rawimage *));
+		if (aao == nil){
+			fprint(2, "jpg: vidmerge: realloc\n");
+			return nil;
+		}
+		aao[i+1] = nil;
+		ao = aao[i] = malloc(sizeof(Rawimage));
+		if (ao == nil){
+			fprint(2, "jpg: vidmerge: realloc\n");
+			return nil;
+		}
+		memcpy(ao, a1, sizeof(Rawimage));
+		if (!eqrect(a1->r , a2->r)){
+			fprint(2, "jpg: vidmerge: rects different in img %d\n", i);
+			return nil;
+		}
+		if (a1->cmaplen != a2->cmaplen){
+			fprint(2, "jpg: vidmerge: cmaplen different in img %d\n", i);
+			return nil;
+		}
+		if (a1->nchans != a2->nchans){
+			fprint(2, "jpg: vidmerge: nchans different in img %d\n", i);
+			return nil;
+		}
+		if (a1->fields != a2->fields){
+			fprint(2, "jpg: vidmerge: fields different in img %d\n", i);
+			return nil;
+		}
+		ao->r.max.y += Dy(ao->r);
+		ao->chanlen += ao->chanlen;
+		if (ao->chanlen != Dx(ao->r)*Dy(ao->r)){
+			fprint(2, "jpg: vidmerge: chanlen wrong %d != %d*%d\n",
+				ao->chanlen, Dx(ao->r), Dy(ao->r));
+			return nil;
+		}
+		row = Dx(a1->r);
+		for (c = 0; c < ao->nchans; c++) {
+			uchar *po, *p1, *p2;
+
+			ao->chans[c] = malloc(ao->chanlen);
+			po = ao->chans[c];
+			p1 = a1->chans[c];
+			p2 = a2->chans[c];
+			for (col = 0; col < Dy(a1->r); col++) {
+				memcpy(po, p1, row);
+				po += row, p1 += row;
+				memcpy(po, p2, row);
+				po += row, p2 += row;
+			}
+			free(a1->chans[c]);
+			free(a2->chans[c]);
+		}
+		if(a2->cmap != nil)
+			free(a2->cmap);
+		free(a1);
+		free(a2);
+	}	
+	if (aa2[i] != nil)
+		fprint(2, "jpg: vidmerge: unequal lengths\n");
+	free(aa1);
+	free(aa2);
+	return aao;
+}
+
+char*
+show(int fd, char *name, int outc)
+{
+	Rawimage **array, *r, *c;
+	static int inited;
+	Image *i;
+	int j, ch, outchan;
+	Biobuf b;
+	char buf[32];
+
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	outchan = outc;
+rpt:	array = Breadjpg(&b, colorspace);
+	if(array == nil || array[0]==nil){
+		fprint(2, "jpg: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	if (fflag) {
+		Rawimage **a;
+
+		a = Breadjpg(&b, colorspace);
+		if(a == nil || a[0]==nil){
+			fprint(2, "jpg: decode %s-2 failed: %r\n", name);
+			return "decode";
+		}
+		array = vidmerge(a, array);
+	} else
+		Bterm(&b);
+
+	r = array[0];
+	c = nil;
+	if(jflag)
+		goto Return;
+	if(!dflag && !inited){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "jpg: initdraw failed: %r\n");
+			return "initdraw";
+		}
+		if(Fflag == 0)
+			einit(Ekeyboard|Emouse);
+		if(defaultcolor && screen->depth>8 && outchan==CMAP8)
+			outchan = RGB24;
+		inited++;
+	}
+	if(outchan == CMAP8)
+		c = torgbv(r, !eflag);
+	else{
+		if(outchan==GREY8 || (r->chandesc==CY && threeflag==0)){
+			c = totruecolor(r, CY);
+			outchan = GREY8;
+		}else
+			c = totruecolor(r, CRGB24);
+	}
+	if(c == nil){
+		fprint(2, "jpg: conversion of %s failed: %r\n", name);
+		return "torgbv";
+	}
+	if(!dflag){
+		if(c->chandesc == CY)
+			i = allocimage(display, c->r, GREY8, 0, 0);
+		else
+			i = allocimage(display, c->r, outchan, 0, 0);
+		if(i == nil){
+			fprint(2, "jpg: allocimage %s failed: %r\n", name);
+			return "allocimage";
+		}
+		if(loadimage(i, i->r, c->chans[0], c->chanlen) < 0){
+			fprint(2, "jpg: loadimage %s failed: %r\n", name);
+			return "loadimage";
+		}
+		image = i;
+		eresized(0);
+		if (Fflag) {
+			freeimage(i);
+			for(j=0; j<r->nchans; j++)
+				free(r->chans[j]);
+			free(r->cmap);
+			free(r);
+			free(array);
+			goto rpt;
+		}
+		if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)
+			exits(nil);
+		draw(screen, screen->clipr, display->white, nil, ZP);
+		image = nil;
+		freeimage(i);
+	}
+	if(nineflag){
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			c->r.min.x, c->r.min.y, c->r.max.x, c->r.max.y);
+		if(write(1, c->chans[0], c->chanlen) != c->chanlen){
+			fprint(2, "jpg: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(writerawimage(1, c) < 0){
+			fprint(2, "jpg: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+    Return:
+	for(j=0; j<r->nchans; j++)
+		free(r->chans[j]);
+	free(r->cmap);
+	free(r);
+	free(array);
+	if(c){
+		free(c->chans[0]);
+		free(c);
+	}
+	if (Fflag) goto rpt;
+	return nil;
+}
diff --git a/src/cmd/jpg/mkfile b/src/cmd/jpg/mkfile
new file mode 100644
index 0000000..198f8e2
--- /dev/null
+++ b/src/cmd/jpg/mkfile
@@ -0,0 +1,52 @@
+<$PLAN9/src/mkhdr
+
+TARG=jpg\
+	gif\
+	togif\
+	ppm\
+	toppm\
+	png\
+	topng\
+	yuv\
+	ico\
+	toico\
+	bmp\
+
+IMFILES=\
+	torgbv.$O\
+	totruecolor.$O\
+	writerawimage.$O\
+
+HFILES=imagefile.h\
+
+SHORTLIB=draw flate bio 9
+LDFLAGS=$LDFLAGS -L$X11/lib -lX11
+
+<$PLAN9/src/mkmany
+
+$O.jpg:		$IMFILES readjpg.$O jpg.$O
+$O.gif:		$IMFILES readgif.$O gif.$O
+$O.togif:	writegif.$O onechan.$O togif.$O torgbv.$O multichan.$O
+$O.ppm:		$IMFILES readppm.$O ppm.$O
+$O.toppm:	writeppm.$O multichan.$O toppm.$O
+$O.png:		$IMFILES readpng.$O png.$O multichan.$O
+$O.topng:	writepng.$O topng.$O
+$O.yuv:		$IMFILES readyuv.$O yuv.$O
+$O.bmp:		$IMFILES readbmp.$O bmp.$O
+
+torgbv.$O:	ycbcr.h rgbv.h
+
+ycbcr.h:	rgbycc.c
+	9c rgbycc.c
+	9l -o o.rgbycc rgbycc.c
+	./o.rgbycc >ycbcr.h
+
+rgbv.h:	rgbrgbv.c
+	9c rgbrgbv.c
+	9l -o o.rgbrgbv rgbrgbv.c
+	./o.rgbrgbv >rgbv.h
+
+nuke:V:	nuke-headers
+
+nuke-headers:V:
+	rm -f rgbv.h ycbcr.h
diff --git a/src/cmd/jpg/multichan.c b/src/cmd/jpg/multichan.c
new file mode 100644
index 0000000..0397121
--- /dev/null
+++ b/src/cmd/jpg/multichan.c
@@ -0,0 +1,60 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+#include "imagefile.h"
+
+/* Separate colors, if not a grey scale or bitmap, into one byte per color per pixel, no alpha or X */
+/* Result is GREY[1248] or RGB24 */
+
+int drawlog2[] = {
+	0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
+	4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	5
+};
+
+static
+int
+notrans(ulong chan)
+{
+	switch(chan){
+	case GREY1:
+	case GREY2:
+	case GREY4:
+	case GREY8:
+	case RGB24:
+		return 1;
+	}
+	return 0;
+}
+
+Image*
+multichan(Image *i)
+{
+	Image *ni;
+
+	if(notrans(i->chan))
+		return i;
+
+	ni = allocimage(display, i->r, RGB24, 0, DNofill);
+	if(ni == nil)
+		return ni;
+	draw(ni, ni->r, i, nil, i->r.min);
+	return ni;
+}
+
+Memimage*
+memmultichan(Memimage *i)
+{
+	Memimage *ni;
+
+	if(notrans(i->chan))
+		return i;
+
+	ni = allocmemimage(i->r, RGB24);
+	if(ni == nil)
+		return ni;
+	memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S);
+	return ni;
+}
diff --git a/src/cmd/jpg/onechan.c b/src/cmd/jpg/onechan.c
new file mode 100644
index 0000000..ea1c489
--- /dev/null
+++ b/src/cmd/jpg/onechan.c
@@ -0,0 +1,229 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+#include "imagefile.h"
+
+/* Convert image to a single channel, one byte per pixel */
+
+static
+int
+notrans(ulong chan)
+{
+	switch(chan){
+	case GREY1:
+	case GREY2:
+	case GREY4:
+	case	CMAP8:
+	case GREY8:
+		return 1;
+	}
+	return 0;
+}
+
+static
+int
+easycase(ulong chan)
+{
+	switch(chan){
+	case RGB16:
+	case RGB24:
+	case RGBA32:
+	case ARGB32:
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Convert to one byte per pixel, RGBV or grey, depending
+ */
+
+static
+uchar*
+load(Image *image, Memimage *memimage)
+{
+	uchar *data, *p, *q0, *q1, *q2;
+	uchar *rgbv;
+	int depth, ndata, dx, dy, i, v;
+	ulong chan, pixel;
+	Rectangle r;
+	Rawimage ri, *nri;
+
+	if(memimage == nil){
+		r = image->r;
+		depth = image->depth;
+		chan = image->chan;
+	}else{
+		r = memimage->r;
+		depth = memimage->depth;
+		chan = memimage->chan;
+	}
+	dx = Dx(r);
+	dy = Dy(r);
+
+	/* 
+	 * Read image data into memory
+	 * potentially one extra byte on each end of each scan line.
+	 */
+	ndata = dy*(2+bytesperline(r, depth));
+	data = malloc(ndata);
+	if(data == nil)
+		return nil;
+	if(memimage != nil)
+		ndata = unloadmemimage(memimage, r, data, ndata);
+	else
+		ndata = unloadimage(image, r, data, ndata);
+	if(ndata < 0){
+		werrstr("onechan: %r");
+		free(data);
+		return nil;
+	}
+
+	/*
+	 * Repack
+	 */
+	memset(&ri, 0, sizeof(ri));
+	ri.r = r;
+	ri.cmap = nil;
+	ri.cmaplen = 0;
+	ri.nchans = 3;
+	ri.chanlen = dx*dy;
+	ri.chans[0] = malloc(ri.chanlen);
+	ri.chans[1] = malloc(ri.chanlen);
+	ri.chans[2] = malloc(ri.chanlen);
+	if(ri.chans[0]==nil || ri.chans[1]==nil || ri.chans[2]==nil){
+    Err:
+		free(ri.chans[0]);
+		free(ri.chans[1]);
+		free(ri.chans[2]);
+		free(data);
+		return nil;
+	}
+	ri.chandesc = CRGB;
+
+	p = data;
+	q0 = ri.chans[0];
+	q1 = ri.chans[1];
+	q2 = ri.chans[2];
+
+	switch(chan){
+	default:
+		werrstr("can't handle image type 0x%lux", chan);
+		goto Err;
+	case RGB16:
+		for(i=0; i<ri.chanlen; i++, p+=2){
+			pixel = (p[1]<<8)|p[0];	/* rrrrrggg gggbbbbb */
+			v = (pixel & 0xF800) >> 8;
+			*q0++ = v | (v>>5);
+			v = (pixel & 0x07E0) >> 3;
+			*q1++ = v | (v>>6);
+			v = (pixel & 0x001F) << 3;
+			*q2++ = v | (v>>5);
+		}
+		break;
+	case RGB24:
+		for(i=0; i<ri.chanlen; i++){
+			*q2++ = *p++;
+			*q1++ = *p++;
+			*q0++ = *p++;
+		}
+		break;
+	case RGBA32:
+		for(i=0; i<ri.chanlen; i++){
+			*q2++ = *p++;
+			*q1++ = *p++;
+			*q0++ = *p++;
+			p++;
+		}
+		break;
+	case ARGB32:
+		for(i=0; i<ri.chanlen; i++){
+			p++;
+			*q2++ = *p++;
+			*q1++ = *p++;
+			*q0++ = *p++;
+		}
+		break;
+	}
+
+	rgbv = nil;
+	nri = torgbv(&ri, 1);
+	if(nri != nil){
+		rgbv = nri->chans[0];
+		free(nri);
+	}
+
+	free(ri.chans[0]);
+	free(ri.chans[1]);
+	free(ri.chans[2]);
+	free(data);
+	return rgbv;
+}
+
+Image*
+onechan(Image *i)
+{
+	uchar *data;
+	Image *ni;
+
+	if(notrans(i->chan))
+		return i;
+
+	if(easycase(i->chan))
+		data = load(i, nil);
+	else{
+		ni = allocimage(display, i->r, RGB24, 0, DNofill);
+		if(ni == nil)
+			return ni;
+		draw(ni, ni->r, i, nil, i->r.min);
+		data = load(ni, nil);
+		freeimage(ni);
+	}
+
+	if(data == nil)
+		return nil;
+
+	ni = allocimage(display, i->r, CMAP8, 0, DNofill);
+	if(ni != nil)
+		if(loadimage(ni, ni->r, data, Dx(ni->r)*Dy(ni->r)) < 0){
+			freeimage(ni);
+			ni = nil;
+		}
+	free(data);
+	return ni;
+}
+
+Memimage*
+memonechan(Memimage *i)
+{
+	uchar *data;
+	Memimage *ni;
+
+	if(notrans(i->chan))
+		return i;
+
+	if(easycase(i->chan))
+		data = load(nil, i);
+	else{
+		ni = allocmemimage(i->r, RGB24);
+		if(ni == nil)
+			return ni;
+		memimagedraw(ni, ni->r, i, i->r.min, nil, ZP, S);
+		data = load(nil, ni);
+		freememimage(ni);
+	}
+
+	if(data == nil)
+		return nil;
+
+	ni = allocmemimage(i->r, CMAP8);
+	if(ni != nil)
+		if(loadmemimage(ni, ni->r, data, Dx(ni->r)*Dy(ni->r)) < 0){
+			freememimage(ni);
+			ni = nil;
+		}
+	free(data);
+	return ni;
+}
diff --git a/src/cmd/jpg/png.c b/src/cmd/jpg/png.c
new file mode 100644
index 0000000..d653fe6
--- /dev/null
+++ b/src/cmd/jpg/png.c
@@ -0,0 +1,224 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+extern int	debug;
+int	cflag = 0;
+int	dflag = 0;
+int	eflag = 0;
+int	nineflag = 0;
+int	threeflag = 0;
+int	colorspace = CRGB;
+int	output = 0;
+ulong	outchan = CMAP8;
+Image	*image;
+int	defaultcolor = 1;
+
+enum{
+	Border	= 2,
+	Edge	= 5
+};
+
+char	*show(int, char*, int);
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "png: can't reattach to window\n");
+		exits("resize");
+	}
+	if(image == nil)
+		return;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(image->r);
+	r.max.y = r.min.y+Dy(image->r);
+	border(screen, r, -Border, nil, ZP);
+	draw(screen, r, image, nil, image->r.min);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i;
+	char *err;
+	char buf[12+1];
+
+	ARGBEGIN{
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'D':
+		debug++;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'r':
+		colorspace = CRGB;
+		break;
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: png -39cdekrtv [file.png ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	if(dflag==0 && colorspace==CYCbCr){	/* see if we should convert right to RGB */
+		fd = open("/dev/screen", OREAD);
+		if(fd > 0){
+			buf[12] = '\0';
+			if(read(fd, buf, 12)==12 && chantodepth(strtochan(buf))>8)
+				colorspace = CRGB;
+			close(fd);
+		}
+	}
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>", outchan);
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "png: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i], outchan);
+				close(fd);
+			}
+			if((nineflag || cflag) && argc>1 && err==nil){
+				fprint(2, "png: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+char*
+show(int fd, char *name, int outc)
+{
+	Rawimage **array, *r, *c;
+	static int inited;
+	Image *i;
+	int j, ch, outchan;
+	Biobuf b;
+	char buf[32];
+
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	outchan = outc;
+	array = Breadpng(&b, colorspace);
+	if(array == nil || array[0]==nil){
+		fprint(2, "png: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	Bterm(&b);
+
+	r = array[0];
+	if(!dflag && !inited){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "png: initdraw failed: %r\n");
+			return "initdraw";
+		}
+		einit(Ekeyboard|Emouse);
+		if(defaultcolor && screen->depth>8 && outchan==CMAP8)
+			outchan = RGB24;
+		inited++;
+	}
+	if(outchan == CMAP8)
+		c = torgbv(r, !eflag);
+	else{
+		if(outchan==GREY8 || (r->chandesc==CY && threeflag==0)){
+			c = totruecolor(r, CY);
+			outchan = GREY8;
+		}else
+			c = totruecolor(r, CRGB24);
+	}
+	if(c == nil){
+		fprint(2, "png: conversion of %s failed: %r\n", name);
+		return "torgbv";
+	}
+	if(!dflag){
+		if(c->chandesc == CY)
+			i = allocimage(display, c->r, GREY8, 0, 0);
+		else
+			i = allocimage(display, c->r, outchan, 0, 0);
+		if(i == nil){
+			fprint(2, "png: allocimage %s failed: %r\n", name);
+			return "allocimage";
+		}
+		if(loadimage(i, i->r, c->chans[0], c->chanlen) < 0){
+			fprint(2, "png: loadimage %s failed: %r\n", name);
+			return "loadimage";
+		}
+		image = i;
+		eresized(0);
+		if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)
+			exits(nil);
+		draw(screen, screen->clipr, display->white, nil, ZP);
+		image = nil;
+		freeimage(i);
+	}
+	if(nineflag){
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			c->r.min.x, c->r.min.y, c->r.max.x, c->r.max.y);
+		if(write(1, c->chans[0], c->chanlen) != c->chanlen){
+			fprint(2, "png: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(writerawimage(1, c) < 0){
+			fprint(2, "png: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+	for(j=0; j<r->nchans; j++)
+		free(r->chans[j]);
+	free(r->cmap);
+	free(r);
+	free(array);
+	if(c){
+		free(c->chans[0]);
+		free(c);
+	}
+	return nil;
+}
diff --git a/src/cmd/jpg/ppm.c b/src/cmd/jpg/ppm.c
new file mode 100644
index 0000000..24019df
--- /dev/null
+++ b/src/cmd/jpg/ppm.c
@@ -0,0 +1,209 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+int		cflag = 0;
+int		dflag = 0;
+int		eflag = 0;
+int		nineflag = 0;
+int		threeflag = 0;
+int		output = 0;
+ulong	outchan = CMAP8;
+int		defaultcolor = 1;
+Image	*image;
+
+enum{
+	Border	= 2,
+	Edge		= 5
+};
+
+char	*show(int, char*);
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "ppm: can't reattach to window\n");
+		exits("resize");
+	}
+	if(image == nil)
+		return;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(image->r);
+	r.max.y = r.min.y+Dy(image->r);
+	border(screen, r, -Border, nil, ZP);
+	draw(screen, r, image, nil, image->r.min);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i;
+	char *err;
+
+	ARGBEGIN{
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: ppm -39cdektv  [file.ppm ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>");
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "ppm: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i]);
+				close(fd);
+			}
+			if((nineflag || cflag) && argc>1 && err==nil){
+				fprint(2, "ppm: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+int
+init(void)
+{
+	static int inited;
+
+	if(inited == 0){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "ppm: initdraw failed: %r");
+			return -1;
+		}
+		einit(Ekeyboard|Emouse);
+		inited++;
+	}
+	return 1;
+}
+
+char*
+show(int fd, char *name)
+{
+	Rawimage **array, *r, *c;
+	Image *i;
+	int j, ch;
+	char buf[32];
+
+	array = readpixmap(fd, CRGB);
+	if(array == nil || array[0]==nil){
+		fprint(2, "ppm: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	if(!dflag){
+		if(init() < 0)
+			return "initdraw";
+		if(defaultcolor && screen->depth>8)
+			outchan = RGB24;
+	}
+	r = array[0];
+	if(outchan == CMAP8)
+		c = torgbv(r, !eflag);
+	else{
+		if(outchan==GREY8 || (r->chandesc==CY && threeflag==0))
+			c = totruecolor(r, CY);
+		else
+			c = totruecolor(r, CRGB24);
+	}
+	if(c == nil){
+		fprint(2, "ppm: converting %s to local format failed: %r\n", name);
+		return "torgbv";
+	}
+	if(!dflag){
+		if(r->chandesc == CY)
+			i = allocimage(display, c->r, GREY8, 0, 0);
+		else
+			i = allocimage(display, c->r, outchan, 0, 0);
+		if(i == nil){
+			fprint(2, "ppm: allocimage %s failed: %r\n", name);
+			return "allocimage";
+		}
+		if(loadimage(i, i->r, c->chans[0], c->chanlen) < 0){
+			fprint(2, "ppm: loadimage %s failed: %r\n", name);
+			return "loadimage";
+		}
+		image = i;
+		eresized(0);
+		if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)
+			exits(nil);
+		draw(screen, screen->clipr, display->white, nil, ZP);
+		image = nil;
+		freeimage(i);
+	}
+	if(nineflag){
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			c->r.min.x, c->r.min.y, c->r.max.x, c->r.max.y);
+		if(write(1, c->chans[0], c->chanlen) != c->chanlen){
+			fprint(2, "ppm: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(writerawimage(1, c) < 0){
+			fprint(2, "ppm: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+	for(j=0; j<r->nchans; j++)
+		free(r->chans[j]);
+	free(r->cmap);
+	free(r);
+	free(array);
+	if(c){
+		free(c->chans[0]);
+		free(c);
+	}
+	return nil;
+}
diff --git a/src/cmd/jpg/readbmp.c b/src/cmd/jpg/readbmp.c
new file mode 100644
index 0000000..154cb48
--- /dev/null
+++ b/src/cmd/jpg/readbmp.c
@@ -0,0 +1,626 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+#include "bmp.h"
+
+/*
+ MS-BMP file reader
+ (c) 2003, I.P.Keller
+
+ aims to decode *all* valid bitmap formats, although some of the
+ flavours couldn't be verified due to lack of suitable test-files.
+ the following flavours are supported:
+
+	Bit/Pix	Orientation	Compression	Tested?
+	  1	top->bottom	n/a		yes
+	  1	bottom->top	n/a		yes
+	  4	top->bottom	no		yes
+	  4	bottom->top	no		yes
+	  4	top->bottom	RLE4		yes, but not with displacement
+	  8	top->bottom	no		yes
+	  8	bottom->top	no		yes
+	  8	top->bottom	RLE8		yes, but not with displacement
+	 16	top->bottom	no		no
+	 16	bottom->top	no		no
+	 16	top->bottom	BITMASK		no
+	 16	bottom->top	BITMASK		no
+	 24	top->bottom	n/a		yes
+	 24	bottom->top	n/a		yes
+	 32	top->bottom	no		no
+	 32	bottom->top	no		no
+	 32	top->bottom	BITMASK		no
+	 32	bottom->top	BITMASK		no
+
+ OS/2 1.x bmp files are recognised as well, but testing was very limited.
+
+ verifying was done with a number of test files, generated by
+ different tools. nevertheless, the tests were in no way exhaustive
+ enough to guarantee bug-free decoding. caveat emptor!
+*/
+
+static short
+r16(Biobuf*b)
+{
+	short s;
+
+	s = Bgetc(b);
+	s |= ((short)Bgetc(b)) << 8;
+	return s;
+}
+
+
+static long
+r32(Biobuf*b)
+{
+	long l;
+
+	l = Bgetc(b);
+	l |= ((long)Bgetc(b)) << 8;
+	l |= ((long)Bgetc(b)) << 16;
+	l |= ((long)Bgetc(b)) << 24;
+	return l;
+}
+
+
+/* get highest bit set */
+static int
+msb(ulong x)
+{
+	int i;
+	for(i = 32; i; i--, x <<= 1)
+		if(x & 0x80000000L)
+			return i;
+	return 0;
+}
+
+/* Load a 1-Bit encoded BMP file (uncompressed) */
+static int
+load_1T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	long ix, iy, i = 0, step_up = 0, padded_width = ((width + 31) / 32) * 32;
+	int val = 0, n;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	for(iy = height; iy; iy--, i += step_up)
+		for(ix = 0, n = 0; ix < padded_width; ix++, n--) {
+			if(!n) {
+				val = Bgetc(b);
+				n = 8;
+			}
+			if(ix < width) {
+				buf[i] = clut[val & 0x80 ? 1 : 0];
+				i++;
+			}
+			val <<= 1;
+		}
+	return 0;
+}
+
+/* Load a 4-Bit encoded BMP file (uncompressed) */
+static int
+load_4T(Biobuf* b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	long ix, iy, i = 0, step_up = 0, skip = (4 - (((width % 8) + 1) / 2)) & 3;
+	uint valH, valL;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	for(iy = height; iy; iy--, i += step_up) {
+		for(ix = 0; ix < width; ) {
+			valH = valL = Bgetc(b) & 0xff;
+			valH >>= 4;
+
+			buf[i] = clut[valH];
+			i++; ix++;
+
+			if(ix < width) {
+				valL &= 0xf;
+				buf[i] = clut[valL];
+				i++; ix++;
+			}
+		}
+		Bseek(b, skip, 1);
+	}
+	return 0;
+}
+
+/* Load a 4-Bit encoded BMP file (RLE4-compressed) */
+static int
+load_4C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	long ix, iy = height -1;
+	uint val, valS, skip;
+	Rgb* p;
+
+	while(iy >= 0) {
+		ix = 0;
+		while(ix < width) {
+			val = Bgetc(b);
+
+			if(0 != val) {
+				valS = (uint)Bgetc(b);
+				p = &buf[ix + iy * width];
+				while(val--) {
+					*p = clut[0xf & (valS >> 4)];
+					p++;
+					ix++;
+					if(0 < val) {
+						*p = clut[0xf & valS];
+						p++;
+						ix++;
+						val--;
+					}
+				}
+			} else {
+				/* Special modes... */
+				val = Bgetc(b);
+				switch(val) {
+					case 0:	/* End-Of-Line detected */
+						ix = width;
+						iy--;
+						break;
+					case 1:	/* End-Of-Picture detected -->> abort */
+						ix = width;
+						iy = -1;
+						break;
+					case 2:	/* Position change detected */
+						val = Bgetc(b);
+						ix += val;
+						val = Bgetc(b);
+						iy -= val;
+						break;
+
+					default:/* Transparent data sequence detected */
+						p = &buf[ix + iy * width];
+						if((1 == (val & 3)) || (2 == (val & 3)))
+							skip = 1;
+						else 
+							skip = 0;
+
+						while(val--) {
+							valS = (uint)Bgetc(b);
+							*p = clut[0xf & (valS >> 4)];
+							p++;
+							ix++;
+							if(0 < val) {
+								*p = clut[0xf & valS];
+								p++;
+								ix++;
+								val--;
+							}
+						}
+						if(skip)
+							Bgetc(b);
+						break;
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+/* Load a 8-Bit encoded BMP file (uncompressed) */
+static int
+load_8T(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	long ix, iy, i = 0, step_up = 0, skip = (4 - (width % 4)) & 3;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	for(iy = height; iy; iy--, i += step_up) {
+		for(ix = 0; ix < width; ix++, i++)
+			buf[i] = clut[Bgetc(b) & 0xff];
+		Bseek(b, skip, 1);
+	}
+	return 0;
+}
+
+/* Load a 8-Bit encoded BMP file (RLE8-compressed) */
+static int
+load_8C(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	long ix, iy = height -1;
+	int val, valS, skip;
+	Rgb* p;
+
+	while(iy >= 0) {
+		ix = 0;
+		while(ix < width) {
+			val = Bgetc(b);
+
+			if(0 != val) {
+				valS = Bgetc(b);
+				p = &buf[ix + iy * width];
+				while(val--) {
+					*p = clut[valS];
+					p++;
+					ix++;
+				}
+			} else {
+				/* Special modes... */
+				val = Bgetc(b);
+				switch(val) {
+					case 0: /* End-Of-Line detected */
+						ix = width;
+						iy--;
+						break;
+					case 1: /* End-Of-Picture detected */
+						ix = width;
+						iy = -1;
+						break;
+					case 2: /* Position change detected */
+						val = Bgetc(b);
+						ix += val;
+						val = Bgetc(b);
+						iy -= val;
+						break;
+					default: /* Transparent (not compressed) sequence detected */
+						p = &buf[ix + iy * width];
+						if(val & 1)
+							skip = 1;
+						else 
+							skip = 0;
+
+						while(val--) {
+							valS = Bgetc(b);
+							*p = clut[valS];
+							p++;
+							ix++;
+						}
+						if(skip)
+							/* Align data stream */
+							Bgetc(b);
+						break;
+				}
+			}
+		}
+	}
+	return 0;
+}
+
+/* Load a 16-Bit encoded BMP file (uncompressed) */
+static int
+load_16(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	uchar c[2];
+	long ix, iy, i = 0, step_up = 0;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	if(clut) {
+		unsigned mask_blue =  (unsigned)clut[0].blue +
+		                     ((unsigned)clut[0].green << 8);
+		unsigned mask_green =  (unsigned)clut[1].blue +
+		                      ((unsigned)clut[1].green << 8);
+		unsigned mask_red =  (unsigned)clut[2].blue +
+		                    ((unsigned)clut[2].green << 8);
+		int shft_blue = msb((ulong)mask_blue) - 8;
+		int shft_green = msb((ulong)mask_green) - 8;
+		int shft_red = msb((ulong)mask_red) - 8;
+
+		for(iy = height; iy; iy--, i += step_up)
+			for(ix = 0; ix < width; ix++, i++) {
+				unsigned val;
+				Bread(b, c, sizeof(c));
+				val = (unsigned)c[0] + ((unsigned)c[1] << 8);
+
+				buf[i].alpha = 0;
+				if(shft_blue >= 0)
+					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
+				else
+					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
+				if(shft_green >= 0)
+					buf[i].green = (uchar)((val & mask_green) >> shft_green);
+				else
+					buf[i].green = (uchar)((val & mask_green) << -shft_green);
+				if(shft_red >= 0)
+					buf[i].red = (uchar)((val & mask_red) >> shft_red);
+				else
+					buf[i].red = (uchar)((val & mask_red) << -shft_red);
+			}
+	} else
+		for(iy = height; iy; iy--, i += step_up)
+			for(ix = 0; ix < width; ix++, i++) {
+				Bread(b, c, sizeof(c));
+				buf[i].blue = (uchar)((c[0] << 3) & 0xf8);
+				buf[i].green = (uchar)(((((unsigned)c[1] << 6) +
+				                        (((unsigned)c[0]) >> 2))) & 0xf8);
+				buf[i].red = (uchar)((c[1] << 1) & 0xf8);
+			}
+	return 0;
+}
+
+/* Load a 24-Bit encoded BMP file (uncompressed) */
+static int
+load_24T(Biobuf* b, long width, long height, Rgb* buf)
+{
+	long ix, iy, i = 0, step_up = 0, skip = (4 - ((width * 3) % 4)) & 3;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	for(iy = height; iy; iy--, i += step_up) {
+		for(ix = 0; ix < width; ix++, i++) {
+			buf[i].alpha = 0;
+			buf[i].blue = Bgetc(b);
+			buf[i].green = Bgetc(b);
+			buf[i].red = Bgetc(b);
+		}
+		Bseek(b, skip, 1);
+	}
+	return 0;
+}
+
+/* Load a 32-Bit encoded BMP file (uncompressed) */
+static int
+load_32(Biobuf *b, long width, long height, Rgb* buf, Rgb* clut)
+{
+	uchar c[4];
+	long ix, iy, i = 0, step_up = 0;
+
+	if(height > 0) {	/* bottom-up */
+		i = (height - 1) * width;
+		step_up = -2 * width;
+	} else
+		height = -height;
+
+	if(clut) {
+		ulong mask_blue =  (ulong)clut[0].blue +
+		                          ((ulong)clut[0].green << 8) +
+		                          ((ulong)clut[0].red << 16) +
+		                          ((ulong)clut[0].alpha << 24);
+		ulong mask_green =  (ulong)clut[1].blue +
+		                           ((ulong)clut[1].green << 8) +
+		                           ((ulong)clut[1].red << 16) +
+		                           ((ulong)clut[1].alpha << 24);
+		ulong mask_red =  (ulong)clut[2].blue +
+		                         ((ulong)clut[2].green << 8) +
+		                         ((ulong)clut[2].red << 16) +
+		                         ((ulong)clut[2].alpha << 24);
+		int shft_blue = msb(mask_blue) - 8;
+		int shft_green = msb(mask_green) - 8;
+		int shft_red = msb(mask_red) - 8;
+
+		for(iy = height; iy; iy--, i += step_up)
+			for(ix = 0; ix < width; ix++, i++) {
+				ulong val;
+				Bread(b, c, sizeof(c));
+				val =  (ulong)c[0] + ((ulong)c[1] << 8) +
+				      ((ulong)c[2] << 16) + ((ulong)c[1] << 24);
+
+				buf[i].alpha = 0;
+				if(shft_blue >= 0)
+					buf[i].blue = (uchar)((val & mask_blue) >> shft_blue);
+				else
+					buf[i].blue = (uchar)((val & mask_blue) << -shft_blue);
+				if(shft_green >= 0)
+					buf[i].green = (uchar)((val & mask_green) >> shft_green);
+				else
+					buf[i].green = (uchar)((val & mask_green) << -shft_green);
+				if(shft_red >= 0)
+					buf[i].red = (uchar)((val & mask_red) >> shft_red);
+				else
+					buf[i].red = (uchar)((val & mask_red) << -shft_red);
+			}
+	} else
+		for(iy = height; iy; iy--, i += step_up)
+			for(ix = 0; ix < width; ix++, i++) {
+				Bread(b, c, nelem(c));
+				buf[i].blue = c[0];
+				buf[i].green = c[1];
+				buf[i].red = c[2];
+			}
+	return 0;
+}
+
+
+static Rgb*
+ReadBMP(Biobuf *b, int *width, int *height)
+{
+	int colours, num_coltab = 0;
+	Filehdr bmfh;
+	Infohdr bmih;
+	Rgb clut[256];
+	Rgb* buf;
+
+	bmfh.type = r16(b);
+	if(bmfh.type != 0x4d42) 	/* signature must be 'BM' */
+		sysfatal("bad magic number, not a BMP file");
+
+	bmfh.size = r32(b);
+	bmfh.reserved1 = r16(b);
+	bmfh.reserved2 = r16(b);
+	bmfh.offbits = r32(b);
+
+	memset(&bmih, 0, sizeof(bmih));
+	bmih.size = r32(b);
+
+	if(bmih.size == 0x0c) {			/* OS/2 1.x version */
+		bmih.width = r16(b);
+		bmih.height = r16(b);
+		bmih.planes = r16(b);
+		bmih.bpp = r16(b);
+		bmih.compression = BMP_RGB;
+	} else {				/* Windows */
+		bmih.width = r32(b);
+		bmih.height = r32(b);
+		bmih.planes = r16(b);
+		bmih.bpp = r16(b);
+		bmih.compression = r32(b);
+		bmih.imagesize = r32(b);
+		bmih.hres = r32(b);
+		bmih.vres = r32(b);
+		bmih.colours = r32(b);
+		bmih.impcolours = r32(b);
+	}
+
+	if(bmih.bpp < 16) {
+		/* load colour table */
+		if(bmih.impcolours)
+			num_coltab = (int)bmih.impcolours;
+		else
+			num_coltab = 1 << bmih.bpp;
+	} else if(bmih.compression == BMP_BITFIELDS &&
+	          (bmih.bpp == 16 || bmih.bpp == 32))
+		/* load bitmasks */
+		num_coltab = 3;
+
+	if(num_coltab) {
+		int i; 
+		Bseek(b, bmih.size + sizeof(Infohdr), 0);
+
+		for(i = 0; i < num_coltab; i++) {
+			clut[i].blue  = (uchar)Bgetc(b);
+			clut[i].green = (uchar)Bgetc(b);
+			clut[i].red   = (uchar)Bgetc(b);
+			clut[i].alpha = (uchar)Bgetc(b);
+		}
+	}
+
+	*width = bmih.width;
+	*height = bmih.height;
+	colours = bmih.bpp;
+
+	Bseek(b, bmfh.offbits, 0);
+
+	if ((buf = calloc(sizeof(Rgb), *width * abs(*height))) == nil)
+		sysfatal("no memory");
+
+	switch(colours) {
+		case 1:
+			load_1T(b, *width, *height, buf, clut);
+			break;
+		case 4:
+			if(bmih.compression == BMP_RLE4)
+				load_4C(b, *width, *height, buf, clut);
+			else
+				load_4T(b, *width, *height, buf, clut);
+			break;
+		case 8:
+			if(bmih.compression == BMP_RLE8)
+				load_8C(b, *width, *height, buf, clut);
+			else
+				load_8T(b, *width, *height, buf, clut);
+			break;
+		case 16:
+			load_16(b, *width, *height, buf,
+			        bmih.compression == BMP_BITFIELDS ? clut : nil);
+			break;
+		case 24:
+			load_24T(b, *width, *height, buf);
+			break;
+		case 32:
+			load_32(b, *width, *height, buf,
+			        bmih.compression == BMP_BITFIELDS ? clut : nil);
+			break;
+	}
+	return buf;
+}
+
+Rawimage**
+Breadbmp(Biobuf *bp, int colourspace)
+{
+	Rawimage *a, **array;
+	int c, width, height;
+	uchar *r, *g, *b;
+	Rgb *s, *e;
+	Rgb *bmp;
+	char ebuf[128];
+
+	a = nil;
+	bmp = nil;
+	array = nil;
+	USED(a);
+	USED(bmp);
+	if (colourspace != CRGB) {
+		errstr(ebuf, sizeof ebuf);	/* throw it away */
+		werrstr("ReadRGB: unknown colour space %d", colourspace);
+		return nil;
+	}
+
+	if ((bmp = ReadBMP(bp, &width, &height)) == nil)
+		return nil;
+
+	if ((a = calloc(sizeof(Rawimage), 1)) == nil)
+		goto Error;
+
+	for (c = 0; c  < 3; c++)
+		if ((a->chans[c] = calloc(width, height)) == nil)
+			goto Error;
+
+	if ((array = calloc(sizeof(Rawimage *), 2)) == nil)
+		goto Error;
+	array[0] = a;
+	array[1] = nil;
+
+	a->nchans = 3;
+	a->chandesc = CRGB;
+	a->chanlen = width * height;
+	a->r = Rect(0, 0, width, height);
+
+	s = bmp;
+	e = s + width * height;
+	r = a->chans[0];
+	g = a->chans[1];
+	b = a->chans[2];
+
+	do {
+		*r++ = s->red;
+		*g++ = s->green;
+		*b++ = s->blue;
+	}while(++s < e);
+
+	free(bmp);
+	return array;
+
+Error:
+	if (a)
+		for (c = 0; c < 3; c++)
+			if (a->chans[c])
+				free(a->chans[c]);
+	if (a)
+		free(a);
+	if (array)
+		free(array);
+	if (bmp)
+		free(bmp);
+	return nil;
+
+}
+
+Rawimage**
+readbmp(int fd, int colorspace)
+{
+	Rawimage * *a;
+	Biobuf b;
+
+	if (Binit(&b, fd, OREAD) < 0)
+		return nil;
+	a = Breadbmp(&b, colorspace);
+	Bterm(&b);
+	return a;
+}
+
+
diff --git a/src/cmd/jpg/readgif.c b/src/cmd/jpg/readgif.c
new file mode 100644
index 0000000..22a0b68
--- /dev/null
+++ b/src/cmd/jpg/readgif.c
@@ -0,0 +1,535 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+
+typedef struct Entry Entry;
+typedef struct Header Header;
+
+struct Entry{
+	int		prefix;
+	int		exten;
+};
+
+
+struct Header{
+	Biobuf	*fd;
+	char		err[256];
+	jmp_buf	errlab;
+	uchar 	buf[3*256];
+	char 		vers[8];
+	uchar 	*globalcmap;
+	int		screenw;
+	int		screenh;
+	int		fields;
+	int		bgrnd;
+	int		aspect;
+	int		flags;
+	int		delay;
+	int		trindex;
+	int		loopcount;
+	Entry	tbl[4096];
+	Rawimage	**array;
+	Rawimage	*new;
+
+	uchar	*pic;
+};
+
+static char		readerr[] = "ReadGIF: read error: %r";
+static char		extreaderr[] = "ReadGIF: can't read extension: %r";
+static char		memerr[] = "ReadGIF: malloc failed: %r";
+
+static Rawimage**	readarray(Header*);
+static Rawimage*	readone(Header*);
+static void			readheader(Header*);
+static void			skipextension(Header*);
+static uchar*		readcmap(Header*, int);
+static uchar*		decode(Header*, Rawimage*, Entry*);
+static void			interlace(Header*, Rawimage*);
+
+static
+void
+clear(void *pp)
+{
+	void **p = (void**)pp;
+
+	if(*p){
+		free(*p);
+		*p = nil;
+	}
+}
+
+static
+void
+giffreeall(Header *h, int freeimage)
+{
+	int i;
+
+	if(h->fd){
+		Bterm(h->fd);
+		h->fd = nil;
+	}
+	clear(&h->pic);
+	if(h->new){
+		clear(&h->new->cmap);
+		clear(&h->new->chans[0]);
+		clear(&h->new);
+	}
+	clear(&h->globalcmap);
+	if(freeimage && h->array!=nil){
+		for(i=0; h->array[i]; i++){
+			clear(&h->array[i]->cmap);
+			clear(&h->array[i]->chans[0]);
+		}
+		clear(&h->array);
+	}
+}
+
+static
+void
+giferror(Header *h, char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(h->err, h->err+sizeof h->err, fmt, arg);
+	va_end(arg);
+
+	werrstr(h->err);
+	giffreeall(h, 1);
+	longjmp(h->errlab, 1);
+}
+
+
+Rawimage**
+readgif(int fd, int colorspace)
+{
+	Rawimage **a;
+	Biobuf b;
+	Header *h;
+	char buf[ERRMAX];
+
+	buf[0] = '\0';
+	USED(colorspace);
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	h = malloc(sizeof(Header));
+	if(h == nil){
+		Bterm(&b);
+		return nil;
+	}
+	memset(h, 0, sizeof(Header));
+	h->fd = &b;
+	errstr(buf, sizeof buf);	/* throw it away */
+	if(setjmp(h->errlab))
+		a = nil;
+	else
+		a = readarray(h);
+	giffreeall(h, 0);
+	free(h);
+	return a;
+}
+
+static
+void
+inittbl(Header *h)
+{
+	int i;
+	Entry *tbl;
+
+	tbl = h->tbl;
+	for(i=0; i<258; i++) {
+		tbl[i].prefix = -1;
+		tbl[i].exten = i;
+	}
+}
+
+static
+Rawimage**
+readarray(Header *h)
+{
+	Entry *tbl;
+	Rawimage *new, **array;
+	int c, nimages;
+
+	tbl = h->tbl;
+
+	readheader(h);
+
+	if(h->fields & 0x80)
+		h->globalcmap = readcmap(h, (h->fields&7)+1);
+
+	array = malloc(sizeof(Rawimage**));
+	if(array == nil)
+		giferror(h, memerr);
+	nimages = 0;
+	array[0] = nil;
+	h->array = array;
+		
+	for(;;){
+		switch(c = Bgetc(h->fd)){
+		case Beof:
+			goto Return;
+
+		case 0x21:	/* Extension (ignored) */
+			skipextension(h);
+			break;
+
+		case 0x2C:	/* Image Descriptor */
+			inittbl(h);
+			new = readone(h);
+			if(new->fields & 0x80){
+				new->cmaplen = 3*(1<<((new->fields&7)+1));
+				new->cmap = readcmap(h, (new->fields&7)+1);
+			}else{
+				new->cmaplen = 3*(1<<((h->fields&7)+1));
+				new->cmap = malloc(new->cmaplen);
+				memmove(new->cmap, h->globalcmap, new->cmaplen);
+			}
+			h->new = new;
+			new->chans[0] = decode(h, new, tbl);
+			if(new->fields & 0x40)
+				interlace(h, new);
+			new->gifflags = h->flags;
+			new->gifdelay = h->delay;
+			new->giftrindex = h->trindex;
+			new->gifloopcount = h->loopcount;
+			array = realloc(h->array, (nimages+2)*sizeof(Rawimage*));
+			if(array == nil)
+				giferror(h, memerr);
+			array[nimages++] = new;
+			array[nimages] = nil;
+			h->array = array;
+			h->new = nil;
+			break;
+
+		case 0x3B:	/* Trailer */
+			goto Return;
+
+		default:
+			fprint(2, "ReadGIF: unknown block type: 0x%.2x\n", c);
+			goto Return;
+		}
+	}
+
+   Return:
+	if(array[0]==nil || array[0]->chans[0] == nil)
+		giferror(h, "ReadGIF: no picture in file");
+
+	return array;
+}
+
+static
+void
+readheader(Header *h)
+{
+	if(Bread(h->fd, h->buf, 13) != 13)
+		giferror(h, "ReadGIF: can't read header: %r");
+	memmove(h->vers, h->buf, 6);
+	if(strcmp(h->vers, "GIF87a")!=0 &&  strcmp(h->vers, "GIF89a")!=0)
+		giferror(h, "ReadGIF: can't recognize format %s", h->vers);
+	h->screenw = h->buf[6]+(h->buf[7]<<8);
+	h->screenh = h->buf[8]+(h->buf[9]<<8);
+	h->fields = h->buf[10];
+	h->bgrnd = h->buf[11];
+	h->aspect = h->buf[12];
+	h->flags = 0;
+	h->delay = 0;
+	h->trindex = 0;
+	h->loopcount = -1;
+}
+
+static
+uchar*
+readcmap(Header *h, int size)
+{
+	uchar *map;
+
+	if(size > 8)
+		giferror(h, "ReadGIF: can't handles %d bits per pixel", size);
+	size = 3*(1<<size);
+	if(Bread(h->fd, h->buf, size) != size)
+		giferror(h, "ReadGIF: short read on color map");
+	map = malloc(size);
+	if(map == nil)
+		giferror(h, memerr);
+	memmove(map, h->buf, size);
+	return map;
+}
+
+static
+Rawimage*
+readone(Header *h)
+{
+	Rawimage *i;
+	int left, top, width, height;
+
+	if(Bread(h->fd, h->buf, 9) != 9)
+		giferror(h, "ReadGIF: can't read image descriptor: %r");
+	i = malloc(sizeof(Rawimage));
+	if(i == nil)
+		giferror(h, memerr);
+	left = h->buf[0]+(h->buf[1]<<8);
+	top = h->buf[2]+(h->buf[3]<<8);
+	width = h->buf[4]+(h->buf[5]<<8);
+	height = h->buf[6]+(h->buf[7]<<8);
+	i->fields = h->buf[8];
+	i->r.min.x = left;
+	i->r.min.y = top;
+	i->r.max.x = left+width;
+	i->r.max.y = top+height;
+	i->nchans = 1;
+	i->chandesc = CRGB1;
+	return i;
+}
+
+
+static
+int
+readdata(Header *h, uchar *data)
+{
+	int nbytes, n;
+
+	nbytes = Bgetc(h->fd);
+	if(nbytes < 0)
+		giferror(h, "ReadGIF: can't read data: %r");
+	if(nbytes == 0)
+		return 0;
+	n = Bread(h->fd, data, nbytes);
+	if(n < 0)
+		giferror(h, "ReadGIF: can't read data: %r");
+	if(n != nbytes)
+		fprint(2, "ReadGIF: short data subblock\n");
+	return n;
+}
+
+static
+void
+graphiccontrol(Header *h)
+{
+	if(Bread(h->fd, h->buf, 5+1) != 5+1)
+		giferror(h, readerr);
+	h->flags = h->buf[1];
+	h->delay = h->buf[2]+(h->buf[3]<<8);
+	h->trindex = h->buf[4];
+}
+
+static
+void
+skipextension(Header *h)
+{
+	int type, hsize, hasdata, n;
+	uchar data[256];
+
+	hsize = 0;
+	hasdata = 0;
+
+	type = Bgetc(h->fd);
+	switch(type){
+	case Beof:
+		giferror(h, extreaderr);
+		break;
+	case 0x01:	/* Plain Text Extension */
+		hsize = 13;
+		hasdata = 1;
+		break;
+	case 0xF9:	/* Graphic Control Extension */
+		graphiccontrol(h);
+		return;
+	case 0xFE:	/* Comment Extension */
+		hasdata = 1;
+		break;
+	case 0xFF:	/* Application Extension */
+		hsize = Bgetc(h->fd);
+		/* standard says this must be 11, but Adobe likes to put out 10-byte ones,
+		 * so we pay attention to the field. */
+		hasdata = 1;
+		break;
+	default:
+		giferror(h, "ReadGIF: unknown extension");
+	}
+	if(hsize>0 && Bread(h->fd, h->buf, hsize) != hsize)
+		giferror(h, extreaderr);
+	if(!hasdata){
+		if(h->buf[hsize-1] != 0)
+			giferror(h, "ReadGIF: bad extension format");
+		return;
+	}
+
+	/* loop counter: Application Extension with NETSCAPE2.0 as string and 1 <loop.count> in data */
+	if(type == 0xFF && hsize==11 && memcmp(h->buf, "NETSCAPE2.0", 11)==0){
+		n = readdata(h, data);
+		if(n == 0)
+			return;
+		if(n==3 && data[0]==1)
+			h->loopcount = data[1] | (data[2]<<8);
+	}
+	while(readdata(h, data) != 0)
+		;
+}
+
+static
+uchar*
+decode(Header *h, Rawimage *i, Entry *tbl)
+{
+	int c, incode, codesize, CTM, EOD, pici, datai, stacki, nbits, sreg, fc, code, piclen;
+	int csize, nentry, maxentry, first, ocode, ndata, nb;
+	uchar *pic;
+	uchar stack[4096], data[256];
+
+	if(Bread(h->fd, h->buf, 1) != 1)
+		giferror(h, "ReadGIF: can't read data: %r");
+	codesize = h->buf[0];
+	if(codesize>8 || 0>codesize)
+		giferror(h, "ReadGIF: can't handle codesize %d", codesize);
+	if(i->cmap!=nil && i->cmaplen!=3*(1<<codesize)
+	  && (codesize!=2 || i->cmaplen!=3*2)) /* peculiar GIF bitmap files... */
+		giferror(h, "ReadGIF: codesize %d doesn't match color map 3*%d", codesize, i->cmaplen/3);
+
+	CTM =1<<codesize;
+	EOD = CTM+1;
+
+	piclen = (i->r.max.x-i->r.min.x)*(i->r.max.y-i->r.min.y);
+	i->chanlen = piclen;
+	pic = malloc(piclen);
+	if(pic == nil)
+		giferror(h, memerr);
+	h->pic = pic;
+	pici = 0;
+	ndata = 0;
+	datai = 0;
+	nbits = 0;
+	sreg = 0;
+	fc = 0;
+
+    Loop:
+	for(;;){
+		csize = codesize+1;
+		nentry = EOD+1;
+		maxentry = (1<<csize)-1;
+		first = 1;
+		ocode = -1;
+
+		for(;; ocode = incode) {
+			while(nbits < csize) {
+				if(datai == ndata){
+					ndata = readdata(h, data);
+					if(ndata == 0)
+						goto Return;
+					datai = 0;
+				}
+				c = data[datai++];
+				sreg |= c<<nbits;
+				nbits += 8;
+			}
+			code = sreg & ((1<<csize) - 1);
+			sreg >>= csize;
+			nbits -= csize;
+
+			if(code == EOD){
+				ndata = readdata(h, data);
+				if(ndata != 0)
+					fprint(2, "ReadGIF: unexpected data past EOD");
+				goto Return;
+			}
+
+			if(code == CTM)
+				goto Loop;
+
+			stacki = (sizeof stack)-1;
+
+			incode = code;
+
+			/* special case for KwKwK */
+			if(code == nentry) {
+				stack[stacki--] = fc;
+				code = ocode;
+			}
+
+			if(code > nentry)
+				giferror(h, "ReadGIF: bad code %x %x", code, nentry);
+
+			for(c=code; c>=0; c=tbl[c].prefix)
+				stack[stacki--] = tbl[c].exten;
+
+			nb = (sizeof stack)-(stacki+1);
+			if(pici+nb > piclen){
+				/* this common error is harmless
+				 * we have to keep reading to keep the blocks in sync */
+				;
+			}else{
+				memmove(pic+pici, stack+stacki+1, sizeof stack - (stacki+1));
+				pici += nb;
+			}
+
+			fc = stack[stacki+1];
+
+			if(first){
+				first = 0;
+				continue;
+			}
+			#define early 0 /* peculiar tiff feature here for reference */
+			if(nentry == maxentry-early) {
+				if(csize >= 12)
+					continue;
+				csize++;
+				maxentry = (1<<csize);
+				if(csize < 12)
+					maxentry--;
+			}
+			tbl[nentry].prefix = ocode;
+			tbl[nentry].exten = fc;
+			nentry++;
+		}
+	}
+
+Return:
+	h->pic = nil;
+	return pic;
+}
+
+static
+void
+interlace(Header *h, Rawimage *image)
+{
+	uchar *pic;
+	Rectangle r;
+	int dx, yy, y;
+	uchar *ipic;
+
+	pic = image->chans[0];
+	r = image->r;
+	dx = r.max.x-r.min.x;
+	ipic = malloc(dx*(r.max.y-r.min.y));
+	if(ipic == nil)
+		giferror(h, nil);
+
+	/* Group 1: every 8th row, starting with row 0 */
+	yy = 0;
+	for(y=r.min.y; y<r.max.y; y+=8){
+		memmove(&ipic[(y-r.min.y)*dx], &pic[yy*dx], dx);
+		yy++;
+	}
+
+	/* Group 2: every 8th row, starting with row 4 */
+	for(y=r.min.y+4; y<r.max.y; y+=8){
+		memmove(&ipic[(y-r.min.y)*dx], &pic[yy*dx], dx);
+		yy++;
+	}
+
+	/* Group 3: every 4th row, starting with row 2 */
+	for(y=r.min.y+2; y<r.max.y; y+=4){
+		memmove(&ipic[(y-r.min.y)*dx], &pic[yy*dx], dx);
+		yy++;
+	}
+
+	/* Group 4: every 2nd row, starting with row 1 */
+	for(y=r.min.y+1; y<r.max.y; y+=2){
+		memmove(&ipic[(y-r.min.y)*dx], &pic[yy*dx], dx);
+		yy++;
+	}
+
+	free(image->chans[0]);
+	image->chans[0] = ipic;
+}
diff --git a/src/cmd/jpg/readjpg.c b/src/cmd/jpg/readjpg.c
new file mode 100644
index 0000000..b154e75
--- /dev/null
+++ b/src/cmd/jpg/readjpg.c
@@ -0,0 +1,1661 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+
+enum {
+	/* Constants, all preceded by byte 0xFF */
+	SOF	=0xC0,	/* Start of Frame */
+	SOF2=0xC2,	/* Start of Frame; progressive Huffman */
+	JPG	=0xC8,	/* Reserved for JPEG extensions */
+	DHT	=0xC4,	/* Define Huffman Tables */
+	DAC	=0xCC,	/* Arithmetic coding conditioning */
+	RST	=0xD0,	/* Restart interval termination */
+	RST7	=0xD7,	/* Restart interval termination (highest value) */
+	SOI	=0xD8,	/* Start of Image */
+	EOI	=0xD9,	/* End of Image */
+	SOS	=0xDA,	/* Start of Scan */
+	DQT	=0xDB,	/* Define quantization tables */
+	DNL	=0xDC,	/* Define number of lines */
+	DRI	=0xDD,	/* Define restart interval */
+	DHP	=0xDE,	/* Define hierarchical progression */
+	EXP	=0xDF,	/* Expand reference components */
+	APPn	=0xE0,	/* Reserved for application segments */
+	JPGn	=0xF0,	/* Reserved for JPEG extensions */
+	COM	=0xFE,	/* Comment */
+
+	CLAMPOFF	= 300,
+	NCLAMP		= CLAMPOFF+700
+};
+
+typedef struct Framecomp Framecomp;
+typedef struct Header Header;
+typedef struct Huffman Huffman;
+
+struct Framecomp	/* Frame component specifier from SOF marker */
+{
+	int	C;
+	int	H;
+	int	V;
+	int	Tq;
+};
+
+struct Huffman
+{
+	int	*size;	/* malloc'ed */
+	int	*code;	/* malloc'ed */
+	int	*val;		/* malloc'ed */
+	int	mincode[17];
+	int	maxcode[17];
+	int	valptr[17];
+	/* fast lookup */
+	int	value[256];
+	int	shift[256];
+};
+
+
+struct Header
+{
+	Biobuf	*fd;
+	char		err[256];
+	jmp_buf	errlab;
+	/* variables in i/o routines */
+	int		sr;	/* shift register, right aligned */
+	int		cnt;	/* # bits in right part of sr */
+	uchar	*buf;
+	int		nbuf;
+	int		peek;
+
+	int		Nf;
+
+	Framecomp	comp[3];
+	uchar	mode;
+	int		X;
+	int		Y;
+	int		qt[4][64];		/* quantization tables */
+	Huffman	dcht[4];
+	Huffman	acht[4];
+	int		**data[3];
+	int		ndata[3];
+	
+	uchar	*sf;	/* start of frame; do better later */
+	uchar	*ss;	/* start of scan; do better later */
+	int		ri;	/* restart interval */
+
+	/* progressive scan */
+	Rawimage *image;
+	Rawimage **array;
+	int		*dccoeff[3];
+	int		**accoeff[3];	/* only need 8 bits plus quantization */
+	int		naccoeff[3];
+	int		nblock[3];
+	int		nacross;
+	int		ndown;
+	int		Hmax;
+	int		Vmax;
+};
+
+static	uchar	clamp[NCLAMP];
+
+static	Rawimage	*readslave(Header*, int);
+static	int			readsegment(Header*, int*);
+static	void			quanttables(Header*, uchar*, int);
+static	void			huffmantables(Header*, uchar*, int);
+static	void			soiheader(Header*);
+static	int			nextbyte(Header*, int);
+static	int			int2(uchar*, int);
+static	void			nibbles(int, int*, int*);
+static	int			receive(Header*, int);
+static	int			receiveEOB(Header*, int);
+static	int			receivebit(Header*);
+static	void			restart(Header*, int);
+static	int			decode(Header*, Huffman*);
+static	Rawimage*	baselinescan(Header*, int);
+static	void			progressivescan(Header*, int);
+static	Rawimage*	progressiveIDCT(Header*, int);
+static	void			idct(int*);
+static	void			colormap1(Header*, int, Rawimage*, int*, int, int);
+static	void			colormapall1(Header*, int, Rawimage*, int*, int*, int*, int, int);
+static	void			colormap(Header*, int, Rawimage*, int**, int**, int**, int, int, int, int, int*, int*);
+static	void			jpgerror(Header*, char*, ...);
+
+static	char		readerr[] = "ReadJPG: read error: %r";
+static	char		memerr[] = "ReadJPG: malloc failed: %r";
+
+static	int zig[64] = {
+	0, 1, 8, 16, 9, 2, 3, 10, 17, /* 0-7 */
+	24, 32, 25, 18, 11, 4, 5, /* 8-15 */
+	12, 19, 26, 33, 40, 48, 41, 34, /* 16-23 */
+	27, 20, 13, 6, 7, 14, 21, 28, /* 24-31 */
+	35, 42, 49, 56, 57, 50, 43, 36, /* 32-39 */
+	29, 22, 15, 23, 30, 37, 44, 51, /* 40-47 */
+	58, 59, 52, 45, 38, 31, 39, 46, /* 48-55 */
+	53, 60, 61, 54, 47, 55, 62, 63 /* 56-63 */
+};
+
+static
+void
+jpginit(void)
+{
+	int k;
+	static int inited;
+
+	if(inited)
+		return;
+	inited = 1;
+	for(k=0; k<CLAMPOFF; k++)
+		clamp[k] = 0;
+	for(; k<CLAMPOFF+256; k++)
+		clamp[k] = k-CLAMPOFF;
+	for(; k<NCLAMP; k++)
+		clamp[k] = 255;
+}
+
+static
+void*
+jpgmalloc(Header *h, int n, int clear)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil)
+		jpgerror(h, memerr);
+	if(clear)
+		memset(p, 0, n);
+	return p;
+}
+
+static
+void
+clear(void *pp)
+{
+	void **p = (void**)pp;
+
+	if(*p){
+		free(*p);
+		*p = nil;
+	}
+}
+
+static
+void
+jpgfreeall(Header *h, int freeimage)
+{
+	int i, j;
+
+	clear(&h->buf);
+	if(h->dccoeff[0])
+		for(i=0; i<3; i++)
+			clear(&h->dccoeff[i]);
+	if(h->accoeff[0])
+		for(i=0; i<3; i++){
+			if(h->accoeff[i])
+				for(j=0; j<h->naccoeff[i]; j++)
+					clear(&h->accoeff[i][j]);
+			clear(&h->accoeff[i]);
+		}
+	for(i=0; i<4; i++){
+		clear(&h->dcht[i].size);
+		clear(&h->acht[i].size);
+		clear(&h->dcht[i].code);
+		clear(&h->acht[i].code);
+		clear(&h->dcht[i].val);
+		clear(&h->acht[i].val);
+	}
+	if(h->data[0])
+		for(i=0; i<3; i++){
+			if(h->data[i])
+				for(j=0; j<h->ndata[i]; j++)
+					clear(&h->data[i][j]);
+			clear(&h->data[i]);
+		}
+	if(freeimage && h->image!=nil){
+		clear(&h->array);
+		clear(&h->image->cmap);
+		for(i=0; i<3; i++)
+			clear(&h->image->chans[i]);
+		clear(&h->image);
+	}
+}
+
+static
+void
+jpgerror(Header *h, char *fmt, ...)
+{
+	va_list arg;
+
+	va_start(arg, fmt);
+	vseprint(h->err, h->err+sizeof h->err, fmt, arg);
+	va_end(arg);
+
+	werrstr(h->err);
+	jpgfreeall(h, 1);
+	longjmp(h->errlab, 1);
+}
+
+Rawimage**
+Breadjpg(Biobuf *b, int colorspace)
+{
+	Rawimage *r, **array;
+	Header *h;
+	char buf[ERRMAX];
+
+	buf[0] = '\0';
+	if(colorspace!=CYCbCr && colorspace!=CRGB){
+		errstr(buf, sizeof buf);	/* throw it away */
+		werrstr("ReadJPG: unknown color space");
+		return nil;
+	}
+	jpginit();
+	h = malloc(sizeof(Header));
+	array = malloc(sizeof(Header));
+	if(h==nil || array==nil){
+		free(h);
+		free(array);
+		return nil;
+	}
+	h->array = array;
+	memset(h, 0, sizeof(Header));
+	h->fd = b;
+	errstr(buf, sizeof buf);	/* throw it away */
+	if(setjmp(h->errlab))
+		r = nil;
+	else
+		r = readslave(h, colorspace);
+	jpgfreeall(h, 0);
+	free(h);
+	array[0] = r;
+	array[1] = nil;
+	return array;
+}
+
+Rawimage**
+readjpg(int fd, int colorspace)
+{
+	Rawimage** a;
+	Biobuf b;
+
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	a = Breadjpg(&b, colorspace);
+	Bterm(&b);
+	return a;
+}
+
+static
+Rawimage*
+readslave(Header *header, int colorspace)
+{
+	Rawimage *image;
+	int nseg, i, H, V, m, n;
+	uchar *b;
+
+	soiheader(header);
+	nseg = 0;
+	image = nil;
+
+	header->buf = jpgmalloc(header, 4096, 0);
+	header->nbuf = 4096;
+	while(header->err[0] == '\0'){
+		nseg++;
+		n = readsegment(header, &m);
+		b = header->buf;
+		switch(m){
+		case -1:
+			return image;
+
+		case APPn+0:
+			if(nseg==1 && strncmp((char*)b, "JFIF", 4)==0)  /* JFIF header; check version */
+				if(b[5]>1 || b[6]>2)
+					sprint(header->err, "ReadJPG: can't handle JFIF version %d.%2d", b[5], b[6]);
+			break;
+
+		case APPn+1: case APPn+2: case APPn+3: case APPn+4: case APPn+5:
+		case APPn+6: case APPn+7: case APPn+8: case APPn+9: case APPn+10:
+		case APPn+11: case APPn+12: case APPn+13: case APPn+14: case APPn+15:
+			break;
+
+		case DQT:
+			quanttables(header, b, n);
+			break;
+
+		case SOF:
+		case SOF2:
+			header->Y = int2(b, 1);
+			header->X = int2(b, 3);
+			header->Nf =b[5];
+			for(i=0; i<header->Nf; i++){
+				header->comp[i].C = b[6+3*i+0];
+				nibbles(b[6+3*i+1], &H, &V);
+				if(H<=0 || V<=0)
+					jpgerror(header, "non-positive sampling factor (Hsamp or Vsamp)");
+				header->comp[i].H = H;
+				header->comp[i].V = V;
+				header->comp[i].Tq = b[6+3*i+2];
+			}
+			header->mode = m;
+			header->sf = b;
+			break;
+
+		case  SOS:
+			header->ss = b;
+			switch(header->mode){
+			case SOF:
+				image = baselinescan(header, colorspace);
+				break;
+			case SOF2:
+				progressivescan(header, colorspace);
+				break;
+			default:
+				sprint(header->err, "unrecognized or unspecified encoding %d", header->mode);
+				break;
+			}
+			break;
+
+		case  DHT:
+			huffmantables(header, b, n);
+			break;
+
+		case  DRI:
+			header->ri = int2(b, 0);
+			break;
+
+		case  COM:
+			break;
+
+		case EOI:
+			if(header->mode == SOF2)
+				image = progressiveIDCT(header, colorspace);
+			return image;
+
+		default:
+			sprint(header->err, "ReadJPG: unknown marker %.2x", m);
+			break;
+		}
+	}
+	return image;
+}
+
+/* readsegment is called after reading scan, which can have */
+/* read ahead a byte.  so we must check peek here */
+static
+int
+readbyte(Header *h)
+{
+	uchar x;
+
+	if(h->peek >= 0){
+		x = h->peek;
+		h->peek = -1;
+	}else if(Bread(h->fd, &x, 1) != 1)
+		jpgerror(h, readerr);
+	return x;
+}
+
+static
+int
+marker(Header *h)
+{
+	int c;
+
+	while((c=readbyte(h)) == 0)
+		fprint(2, "ReadJPG: skipping zero byte at offset %lld\n", Boffset(h->fd));
+	if(c != 0xFF)
+		jpgerror(h, "ReadJPG: expecting marker; found 0x%x at offset %lld\n", c, Boffset(h->fd));
+	while(c == 0xFF)
+		c = readbyte(h);
+	return c;
+}
+
+static
+int
+int2(uchar *buf, int n)
+{
+	return (buf[n]<<8) + buf[n+1];
+}
+
+static
+void
+nibbles(int b, int *p0, int *p1)
+{
+	*p0 = (b>>4) & 0xF;
+	*p1 = b & 0xF;
+}
+
+static
+void
+soiheader(Header *h)
+{
+	h->peek = -1;
+	if(marker(h) != SOI)
+		jpgerror(h, "ReadJPG: unrecognized marker in header");
+	h->err[0] = '\0';
+	h->mode = 0;
+	h->ri = 0;
+}
+
+static
+int
+readsegment(Header *h, int *markerp)
+{
+	int m, n;
+	uchar tmp[2];
+
+	m = marker(h);
+	switch(m){
+	case EOI:
+		*markerp = m;
+		return 0;
+	case 0:
+		jpgerror(h, "ReadJPG: expecting marker; saw %.2x at offset %lld", m, Boffset(h->fd));
+	}
+	if(Bread(h->fd, tmp, 2) != 2)
+    Readerr:
+		jpgerror(h, readerr);
+	n = int2(tmp, 0);
+	if(n < 2)
+		goto Readerr;
+	n -= 2;
+	if(n > h->nbuf){
+		free(h->buf);
+		h->buf = jpgmalloc(h, n+1, 0); /* +1 for sentinel */
+		h->nbuf = n;
+	}
+	if(Bread(h->fd, h->buf, n) != n)
+		goto Readerr;
+	*markerp = m;
+	return n;
+}
+
+static
+int
+huffmantable(Header *h, uchar *b)
+{
+	Huffman *t;
+	int Tc, th, n, nsize, i, j, k, v, cnt, code, si, sr, m;
+	int *maxcode;
+
+	nibbles(b[0], &Tc, &th);
+	if(Tc > 1)
+		jpgerror(h, "ReadJPG: unknown Huffman table class %d", Tc);
+	if(th>3 || (h->mode==SOF && th>1))
+		jpgerror(h, "ReadJPG: unknown Huffman table index %d", th);
+	if(Tc == 0)
+		t = &h->dcht[th];
+	else
+		t = &h->acht[th];
+
+	/* flow chart C-2 */
+	nsize = 0;
+	for(i=0; i<16; i++)
+		nsize += b[1+i];
+	t->size = jpgmalloc(h, (nsize+1)*sizeof(int), 1);
+	k = 0;
+	for(i=1; i<=16; i++){
+		n = b[i];
+		for(j=0; j<n; j++)
+			t->size[k++] = i;
+	}
+	t->size[k] = 0;
+
+	/* initialize HUFFVAL */
+	t->val = jpgmalloc(h, nsize*sizeof(int), 1);
+	for(i=0; i<nsize; i++)
+		t->val[i] = b[17+i];
+
+	/* flow chart C-3 */
+	t->code = jpgmalloc(h, (nsize+1)*sizeof(int), 1);
+	k = 0;
+	code = 0;
+	si = t->size[0];
+	for(;;){
+		do
+			t->code[k++] = code++;
+		while(t->size[k] == si);
+		if(t->size[k] == 0)
+			break;
+		do{
+			code <<= 1;
+			si++;
+		}while(t->size[k] != si);
+	}
+
+	/* flow chart F-25 */
+	i = 0;
+	j = 0;
+	for(;;){
+		for(;;){
+			i++;
+			if(i > 16)
+				goto outF25;
+			if(b[i] != 0)
+				break;
+			t->maxcode[i] = -1;
+		}
+		t->valptr[i] = j;
+		t->mincode[i] = t->code[j];
+		j += b[i]-1;
+		t->maxcode[i] = t->code[j];
+		j++;
+	}
+outF25:
+
+	/* create byte-indexed fast path tables */
+	maxcode = t->maxcode;
+	/* stupid startup algorithm: just run machine for each byte value */
+	for(v=0; v<256; ){
+		cnt = 7;
+		m = 1<<7;
+		code = 0;
+		sr = v;
+		i = 1;
+		for(;;i++){
+			if(sr & m)
+				code |= 1;
+			if(code <= maxcode[i])
+				break;
+			code <<= 1;
+			m >>= 1;
+			if(m == 0){
+				t->shift[v] = 0;
+				t->value[v] = -1;
+				goto continueBytes;
+			}
+			cnt--;
+		}
+		t->shift[v] = 8-cnt;
+		t->value[v] = t->val[t->valptr[i]+(code-t->mincode[i])];
+
+    continueBytes:
+		v++;
+	}
+
+	return nsize;
+}
+
+static
+void
+huffmantables(Header *h, uchar *b, int n)
+{
+	int l, mt;
+
+	for(l=0; l<n; l+=17+mt)
+		mt = huffmantable(h, &b[l]);
+}
+
+static
+int
+quanttable(Header *h, uchar *b)
+{
+	int i, pq, tq, *q;
+
+	nibbles(b[0], &pq, &tq);
+	if(pq > 1)
+		jpgerror(h, "ReadJPG: unknown quantization table class %d", pq);
+	if(tq > 3)
+		jpgerror(h, "ReadJPG: unknown quantization table index %d", tq);
+	q = h->qt[tq];
+	for(i=0; i<64; i++){
+		if(pq == 0)
+			q[i] = b[1+i];
+		else
+			q[i] = int2(b, 1+2*i);
+	}
+	return 64*(1+pq);
+}
+
+static
+void
+quanttables(Header *h, uchar *b, int n)
+{
+	int l, m;
+
+	for(l=0; l<n; l+=1+m)
+		m = quanttable(h, &b[l]);
+}
+
+static
+Rawimage*
+baselinescan(Header *h, int colorspace)
+{
+	int Ns, z, k, m, Hmax, Vmax, comp;
+	int allHV1, nblock, ri, mcu, nacross, nmcu;
+	Huffman *dcht, *acht;
+	int block, t, diff, *qt;
+	uchar *ss;
+	Rawimage *image;
+	int Td[3], Ta[3], H[3], V[3], DC[3];
+	int ***data, *zz;
+
+	ss = h->ss;
+	Ns = ss[0];
+	if((Ns!=3 && Ns!=1) || Ns!=h->Nf)
+		jpgerror(h, "ReadJPG: can't handle scan not 3 components");
+
+	image = jpgmalloc(h, sizeof(Rawimage), 1);
+	h->image = image;
+	image->r = Rect(0, 0, h->X, h->Y);
+	image->cmap = nil;
+	image->cmaplen = 0;
+	image->chanlen = h->X*h->Y;
+	image->fields = 0;
+	image->gifflags = 0;
+	image->gifdelay = 0;
+	image->giftrindex = 0;
+	if(Ns == 3)
+		image->chandesc = colorspace;
+	else
+		image->chandesc = CY;
+	image->nchans = h->Nf;
+	for(k=0; k<h->Nf; k++)
+		image->chans[k] = jpgmalloc(h, h->X*h->Y, 0);
+
+	/* compute maximum H and V */
+	Hmax = 0;
+	Vmax = 0;
+	for(comp=0; comp<Ns; comp++){
+		if(h->comp[comp].H > Hmax)
+			Hmax = h->comp[comp].H;
+		if(h->comp[comp].V > Vmax)
+			Vmax = h->comp[comp].V;
+	}
+
+	/* initialize data structures */
+	allHV1 = 1;
+	data = h->data;
+	for(comp=0; comp<Ns; comp++){
+		/* JPEG requires scan components to be in same order as in frame, */
+		/* so if both have 3 we know scan is Y Cb Cr and there's no need to */
+		/* reorder */
+		nibbles(ss[2+2*comp], &Td[comp], &Ta[comp]);
+		H[comp] = h->comp[comp].H;
+		V[comp] = h->comp[comp].V;
+		nblock = H[comp]*V[comp];
+		if(nblock != 1)
+			allHV1 = 0;
+		data[comp] = jpgmalloc(h, nblock*sizeof(int*), 0);
+		h->ndata[comp] = nblock;
+		DC[comp] = 0;
+		for(m=0; m<nblock; m++)
+			data[comp][m] = jpgmalloc(h, 8*8*sizeof(int), 0);
+	}
+
+	ri = h->ri;
+
+	h->cnt = 0;
+	h->sr = 0;
+	h->peek = -1;
+	nacross = ((h->X+(8*Hmax-1))/(8*Hmax));
+	nmcu = ((h->Y+(8*Vmax-1))/(8*Vmax))*nacross;
+	for(mcu=0; mcu<nmcu; ){
+		for(comp=0; comp<Ns; comp++){
+			dcht = &h->dcht[Td[comp]];
+			acht = &h->acht[Ta[comp]];
+			qt = h->qt[h->comp[comp].Tq];
+
+			for(block=0; block<H[comp]*V[comp]; block++){
+				/* F-22 */
+				t = decode(h, dcht);
+				diff = receive(h, t);
+				DC[comp] += diff;
+
+				/* F-23 */
+				zz = data[comp][block];
+				memset(zz, 0, 8*8*sizeof(int));
+				zz[0] = qt[0]*DC[comp];
+				k = 1;
+
+				for(;;){
+					t = decode(h, acht);
+					if((t&0x0F) == 0){
+						if((t&0xF0) != 0xF0)
+							break;
+						k += 16;
+					}else{
+						k += t>>4;
+						z = receive(h, t&0xF);
+						zz[zig[k]] = z*qt[k];
+						if(k == 63)
+							break;
+						k++;
+					}
+				}
+
+				idct(zz);
+			}
+		}
+
+		/* rotate colors to RGB and assign to bytes */
+		if(Ns == 1) /* very easy */
+			colormap1(h, colorspace, image, data[0][0], mcu, nacross);
+		else if(allHV1) /* fairly easy */
+			colormapall1(h, colorspace, image, data[0][0], data[1][0], data[2][0], mcu, nacross);
+		else /* miserable general case */
+			colormap(h, colorspace, image, data[0], data[1], data[2], mcu, nacross, Hmax, Vmax, H, V);
+		/* process restart marker, if present */
+		mcu++;
+		if(ri>0 && mcu<nmcu && mcu%ri==0){
+			restart(h, mcu);
+			for(comp=0; comp<Ns; comp++)
+				DC[comp] = 0;
+		}
+	}
+	return image;
+}
+
+static
+void
+restart(Header *h, int mcu)
+{
+	int rest, rst, nskip;
+
+	rest = mcu/h->ri-1;
+	nskip = 0;
+	do{
+		do{
+			rst = nextbyte(h, 1);
+			nskip++;
+		}while(rst>=0 && rst!=0xFF);
+		if(rst == 0xFF){
+			rst = nextbyte(h, 1);
+			nskip++;
+		}
+	}while(rst>=0 && (rst&~7)!=RST);
+	if(nskip != 2)
+		sprint(h->err, "ReadJPG: skipped %d bytes at restart %d\n", nskip-2, rest);
+	if(rst < 0)
+		jpgerror(h, readerr);
+	if((rst&7) != (rest&7))
+		jpgerror(h, "ReadJPG: expected RST%d got %d", rest&7, rst&7);
+	h->cnt = 0;
+	h->sr = 0;
+}
+
+static
+Rawimage*
+progressiveIDCT(Header *h, int colorspace)
+{
+	int k, m, comp, block, Nf, bn;
+	int allHV1, nblock, mcu, nmcu;
+	int H[3], V[3], blockno[3];
+	int *dccoeff, **accoeff;
+	int ***data, *zz;
+
+	Nf = h->Nf;
+	allHV1 = 1;
+	data = h->data;
+
+	for(comp=0; comp<Nf; comp++){
+		H[comp] = h->comp[comp].H;
+		V[comp] = h->comp[comp].V;
+		nblock = h->nblock[comp];
+		if(nblock != 1)
+			allHV1 = 0;
+		h->ndata[comp] = nblock;
+		data[comp] = jpgmalloc(h, nblock*sizeof(int*), 0);
+		for(m=0; m<nblock; m++)
+			data[comp][m] = jpgmalloc(h, 8*8*sizeof(int), 0);
+	}
+
+	memset(blockno, 0, sizeof blockno);
+	nmcu = h->nacross*h->ndown;
+	for(mcu=0; mcu<nmcu; mcu++){
+		for(comp=0; comp<Nf; comp++){
+			dccoeff = h->dccoeff[comp];
+			accoeff = h->accoeff[comp];
+			bn = blockno[comp];
+			for(block=0; block<h->nblock[comp]; block++){
+				zz = data[comp][block];
+				memset(zz, 0, 8*8*sizeof(int));
+				zz[0] = dccoeff[bn];
+
+				for(k=1; k<64; k++)
+					zz[zig[k]] = accoeff[bn][k];
+
+				idct(zz);
+				bn++;
+			}
+			blockno[comp] = bn;
+		}
+
+		/* rotate colors to RGB and assign to bytes */
+		if(Nf == 1) /* very easy */
+			colormap1(h, colorspace, h->image, data[0][0], mcu, h->nacross);
+		else if(allHV1) /* fairly easy */
+			colormapall1(h, colorspace, h->image, data[0][0], data[1][0], data[2][0], mcu, h->nacross);
+		else /* miserable general case */
+			colormap(h, colorspace, h->image, data[0], data[1], data[2], mcu, h->nacross, h->Hmax, h->Vmax, H, V);
+	}
+
+	return h->image;
+}
+
+static
+void
+progressiveinit(Header *h, int colorspace)
+{
+	int Nf, Ns, j, k, nmcu, comp;
+	uchar *ss;
+	Rawimage *image;
+
+	ss = h->ss;
+	Ns = ss[0];
+	Nf = h->Nf;
+	if((Ns!=3 && Ns!=1) || Ns!=Nf)
+		jpgerror(h, "ReadJPG: image must have 1 or 3 components");
+
+	image = jpgmalloc(h, sizeof(Rawimage), 1);
+	h->image = image;
+	image->r = Rect(0, 0, h->X, h->Y);
+	image->cmap = nil;
+	image->cmaplen = 0;
+	image->chanlen = h->X*h->Y;
+	image->fields = 0;
+	image->gifflags = 0;
+	image->gifdelay = 0;
+	image->giftrindex = 0;
+	if(Nf == 3)
+		image->chandesc = colorspace;
+	else
+		image->chandesc = CY;
+	image->nchans = h->Nf;
+	for(k=0; k<Nf; k++){
+		image->chans[k] = jpgmalloc(h, h->X*h->Y, 0);
+		h->nblock[k] = h->comp[k].H*h->comp[k].V;
+	}
+
+	/* compute maximum H and V */
+	h->Hmax = 0;
+	h->Vmax = 0;
+	for(comp=0; comp<Nf; comp++){
+		if(h->comp[comp].H > h->Hmax)
+			h->Hmax = h->comp[comp].H;
+		if(h->comp[comp].V > h->Vmax)
+			h->Vmax = h->comp[comp].V;
+	}
+	h->nacross = ((h->X+(8*h->Hmax-1))/(8*h->Hmax));
+	h->ndown = ((h->Y+(8*h->Vmax-1))/(8*h->Vmax));
+	nmcu = h->nacross*h->ndown;
+
+	for(k=0; k<Nf; k++){
+		h->dccoeff[k] = jpgmalloc(h, h->nblock[k]*nmcu * sizeof(int), 1);
+		h->accoeff[k] = jpgmalloc(h, h->nblock[k]*nmcu * sizeof(int*), 1);
+		h->naccoeff[k] = h->nblock[k]*nmcu;
+		for(j=0; j<h->nblock[k]*nmcu; j++)
+			h->accoeff[k][j] = jpgmalloc(h, 64*sizeof(int), 1);
+	}
+
+}
+
+static
+void
+progressivedc(Header *h, int comp, int Ah, int Al)
+{
+	int Ns, z, ri, mcu,  nmcu;
+	int block, t, diff, qt, *dc, bn;
+	Huffman *dcht;
+	uchar *ss;
+	int Td[3], DC[3], blockno[3];
+
+	ss= h->ss;
+	Ns = ss[0];
+	if(Ns!=h->Nf)
+		jpgerror(h, "ReadJPG: can't handle progressive with Nf!=Ns in DC scan");
+
+	/* initialize data structures */
+	h->cnt = 0;
+	h->sr = 0;
+	h->peek = -1;
+	for(comp=0; comp<Ns; comp++){
+		/*
+		 * JPEG requires scan components to be in same order as in frame,
+		 * so if both have 3 we know scan is Y Cb Cr and there's no need to
+		 * reorder
+		 */
+		nibbles(ss[2+2*comp], &Td[comp], &z);	/* z is ignored */
+		DC[comp] = 0;
+	}
+
+	ri = h->ri;
+
+	nmcu = h->nacross*h->ndown;
+	memset(blockno, 0, sizeof blockno);
+	for(mcu=0; mcu<nmcu; ){
+		for(comp=0; comp<Ns; comp++){
+			dcht = &h->dcht[Td[comp]];
+			qt = h->qt[h->comp[comp].Tq][0];
+			dc = h->dccoeff[comp];
+			bn = blockno[comp];
+
+			for(block=0; block<h->nblock[comp]; block++){
+				if(Ah == 0){
+					t = decode(h, dcht);
+					diff = receive(h, t);
+					DC[comp] += diff;
+					dc[bn] = qt*DC[comp]<<Al;
+				}else
+					dc[bn] |= qt*receivebit(h)<<Al;
+				bn++;
+			}
+			blockno[comp] = bn;
+		}
+
+		/* process restart marker, if present */
+		mcu++;
+		if(ri>0 && mcu<nmcu && mcu%ri==0){
+			restart(h, mcu);
+			for(comp=0; comp<Ns; comp++)
+				DC[comp] = 0;
+		}
+	}
+}
+
+static
+void
+progressiveac(Header *h, int comp, int Al)
+{
+	int Ns, Ss, Se, z, k, eobrun, x, y, nver, tmcu, blockno, *acc, rs;
+	int ri, mcu, nacross, ndown, nmcu, nhor;
+	Huffman *acht;
+	int *qt, rrrr, ssss, q;
+	uchar *ss;
+	int Ta, H, V;
+
+	ss = h->ss;
+	Ns = ss[0];
+	if(Ns != 1)
+		jpgerror(h, "ReadJPG: illegal Ns>1 in progressive AC scan");
+	Ss = ss[1+2];
+	Se = ss[2+2];
+	H = h->comp[comp].H;
+	V = h->comp[comp].V;
+
+	nacross = h->nacross*H;
+	ndown = h->ndown*V;
+	q = 8*h->Hmax/H;
+	nhor = (h->X+q-1)/q;
+	q = 8*h->Vmax/V;
+	nver = (h->Y+q-1)/q;
+
+	/* initialize data structures */
+	h->cnt = 0;
+	h->sr = 0;
+	h->peek = -1;
+	nibbles(ss[1+1], &z, &Ta);	/* z is thrown away */
+
+	ri = h->ri;
+
+	eobrun = 0;
+	acht = &h->acht[Ta];
+	qt = h->qt[h->comp[comp].Tq];
+	nmcu = nacross*ndown;
+	mcu = 0;
+	for(y=0; y<nver; y++){
+		for(x=0; x<nhor; x++){
+			/* Figure G-3  */
+			if(eobrun > 0){
+				--eobrun;
+				continue;
+			}
+
+			/* arrange blockno to be in same sequence as original scan calculation. */
+			tmcu = x/H + (nacross/H)*(y/V);
+			blockno = tmcu*H*V + H*(y%V) + x%H;
+			acc = h->accoeff[comp][blockno];
+			k = Ss;
+			for(;;){
+				rs = decode(h, acht);
+				/* XXX remove rrrr ssss as in baselinescan */
+				nibbles(rs, &rrrr, &ssss);
+				if(ssss == 0){
+					if(rrrr < 15){
+						eobrun = 0;
+						if(rrrr > 0)
+							eobrun = receiveEOB(h, rrrr)-1;
+						break;
+					}
+					k += 16;
+				}else{
+					k += rrrr;
+					z = receive(h, ssss);
+					acc[k] = z*qt[k]<<Al;
+					if(k == Se)
+						break;
+					k++;
+				}
+			}
+		}
+
+		/* process restart marker, if present */
+		mcu++;
+		if(ri>0 && mcu<nmcu && mcu%ri==0){
+			restart(h, mcu);
+			eobrun = 0;
+		}
+	}
+}
+
+static
+void
+increment(Header *h, int acc[], int k, int Pt)
+{
+	if(acc[k] == 0)
+		return;
+	if(receivebit(h) != 0)
+		if(acc[k] < 0)
+			acc[k] -= Pt;
+		else
+			acc[k] += Pt;
+}
+
+static
+void
+progressiveacinc(Header *h, int comp, int Al)
+{
+	int Ns, i, z, k, Ss, Se, Ta, **ac, H, V;
+	int ri, mcu, nacross, ndown, nhor, nver, eobrun, nzeros, pending, x, y, tmcu, blockno, q, nmcu;
+	Huffman *acht;
+	int *qt, rrrr, ssss, *acc, rs;
+	uchar *ss;
+
+	ss = h->ss;
+	Ns = ss[0];
+	if(Ns != 1)
+		jpgerror(h, "ReadJPG: illegal Ns>1 in progressive AC scan");
+	Ss = ss[1+2];
+	Se = ss[2+2];
+	H = h->comp[comp].H;
+	V = h->comp[comp].V;
+
+	nacross = h->nacross*H;
+	ndown = h->ndown*V;
+	q = 8*h->Hmax/H;
+	nhor = (h->X+q-1)/q;
+	q = 8*h->Vmax/V;
+	nver = (h->Y+q-1)/q;
+
+	/* initialize data structures */
+	h->cnt = 0;
+	h->sr = 0;
+	h->peek = -1;
+	nibbles(ss[1+1], &z, &Ta);	/* z is thrown away */
+	ri = h->ri;
+
+	eobrun = 0;
+	ac = h->accoeff[comp];
+	acht = &h->acht[Ta];
+	qt = h->qt[h->comp[comp].Tq];
+	nmcu = nacross*ndown;
+	mcu = 0;
+	pending = 0;
+	nzeros = -1;
+	for(y=0; y<nver; y++){
+		for(x=0; x<nhor; x++){
+			/* Figure G-7 */
+
+			/*  arrange blockno to be in same sequence as original scan calculation. */
+			tmcu = x/H + (nacross/H)*(y/V);
+			blockno = tmcu*H*V + H*(y%V) + x%H;
+			acc = ac[blockno];
+			if(eobrun > 0){
+				if(nzeros > 0)
+					jpgerror(h, "ReadJPG: zeros pending at block start");
+				for(k=Ss; k<=Se; k++)
+					increment(h, acc, k, qt[k]<<Al);
+				--eobrun;
+				continue;
+			}
+
+			for(k=Ss; k<=Se; ){
+				if(nzeros >= 0){
+					if(acc[k] != 0)
+						increment(h, acc, k, qt[k]<<Al);
+					else if(nzeros-- == 0)
+						acc[k] = pending;
+					k++;
+					continue;
+				}
+				rs = decode(h, acht);
+				nibbles(rs, &rrrr, &ssss);
+				if(ssss == 0){
+					if(rrrr < 15){
+						eobrun = 0;
+						if(rrrr > 0)
+							eobrun = receiveEOB(h, rrrr)-1;
+						while(k <= Se){
+							increment(h, acc, k, qt[k]<<Al);
+							k++;
+						}
+						break;
+					}
+					for(i=0; i<16; k++){
+						increment(h, acc, k, qt[k]<<Al);
+						if(acc[k] == 0)
+							i++;
+					}
+					continue;
+				}else if(ssss != 1)
+					jpgerror(h, "ReadJPG: ssss!=1 in progressive increment");
+				nzeros = rrrr;
+				pending = receivebit(h);
+				if(pending == 0)
+					pending = -1;
+				pending *= qt[k]<<Al;
+			}
+		}
+
+		/* process restart marker, if present */
+		mcu++;
+		if(ri>0 && mcu<nmcu && mcu%ri==0){
+			restart(h, mcu);
+			eobrun = 0;
+			nzeros = -1;
+		}
+	}
+}
+
+static
+void
+progressivescan(Header *h, int colorspace)
+{
+	uchar *ss;
+	int Ns, Ss, Ah, Al, c, comp, i;
+
+	if(h->dccoeff[0] == nil)
+		progressiveinit(h, colorspace);
+
+	ss = h->ss;
+	Ns = ss[0];
+	Ss = ss[1+2*Ns];
+	nibbles(ss[3+2*Ns], &Ah, &Al);
+	c = ss[1];
+	comp = -1;
+	for(i=0; i<h->Nf; i++)
+		if(h->comp[i].C == c)
+			comp = i;
+	if(comp == -1)
+		jpgerror(h, "ReadJPG: bad component index in scan header");
+
+	if(Ss == 0){
+		progressivedc(h, comp, Ah, Al);
+		return;
+	}
+	if(Ah == 0){
+		progressiveac(h, comp, Al);
+		return;
+	}
+	progressiveacinc(h, comp, Al);
+}
+
+enum {
+	c1 = 2871,	/* 1.402 * 2048 */
+	c2 = 705,		/* 0.34414 * 2048 */
+	c3 = 1463,	/* 0.71414 * 2048 */
+	c4 = 3629,	/* 1.772 * 2048 */
+};
+
+static
+void
+colormap1(Header *h, int colorspace, Rawimage *image, int data[8*8], int mcu, int nacross)
+{
+	uchar *pic;
+	int x, y, dx, dy, minx, miny;
+	int r, k, pici;
+
+	USED(colorspace);
+	pic = image->chans[0];
+	minx = 8*(mcu%nacross);
+	dx = 8;
+	if(minx+dx > h->X)
+		dx = h->X-minx;
+	miny = 8*(mcu/nacross);
+	dy = 8;
+	if(miny+dy > h->Y)
+		dy = h->Y-miny;
+	pici = miny*h->X+minx;
+	k = 0;
+	for(y=0; y<dy; y++){
+		for(x=0; x<dx; x++){
+			r = clamp[(data[k+x]+128)+CLAMPOFF];
+			pic[pici+x] = r;
+		}
+		pici += h->X;
+		k += 8;
+	}
+}
+
+static
+void
+colormapall1(Header *h, int colorspace, Rawimage *image, int data0[8*8], int data1[8*8], int data2[8*8], int mcu, int nacross)
+{
+	uchar *rpic, *gpic, *bpic, *rp, *gp, *bp;
+	int *p0, *p1, *p2;
+	int x, y, dx, dy, minx, miny;
+	int r, g, b, k, pici;
+	int Y, Cr, Cb;
+
+	rpic = image->chans[0];
+	gpic = image->chans[1];
+	bpic = image->chans[2];
+	minx = 8*(mcu%nacross);
+	dx = 8;
+	if(minx+dx > h->X)
+		dx = h->X-minx;
+	miny = 8*(mcu/nacross);
+	dy = 8;
+	if(miny+dy > h->Y)
+		dy = h->Y-miny;
+	pici = miny*h->X+minx;
+	k = 0;
+	for(y=0; y<dy; y++){
+		p0 = data0+k;
+		p1 = data1+k;
+		p2 = data2+k;
+		rp = rpic+pici;
+		gp = gpic+pici;
+		bp = bpic+pici;
+		if(colorspace == CYCbCr)
+			for(x=0; x<dx; x++){
+				*rp++ = clamp[*p0++ + 128 + CLAMPOFF];
+				*gp++ = clamp[*p1++ + 128 + CLAMPOFF];
+				*bp++ = clamp[*p2++ + 128 + CLAMPOFF];
+			}
+		else
+			for(x=0; x<dx; x++){
+				Y = (*p0++ + 128) << 11;
+				Cb = *p1++;
+				Cr = *p2++;
+				r = Y+c1*Cr;
+				g = Y-c2*Cb-c3*Cr;
+				b = Y+c4*Cb;
+				*rp++ = clamp[(r>>11)+CLAMPOFF];
+				*gp++ = clamp[(g>>11)+CLAMPOFF];
+				*bp++ = clamp[(b>>11)+CLAMPOFF];
+			}
+		pici += h->X;
+		k += 8;
+	}
+}
+
+static
+void
+colormap(Header *h, int colorspace, Rawimage *image, int *data0[8*8], int *data1[8*8], int *data2[8*8], int mcu, int nacross, int Hmax, int Vmax,  int *H, int *V)
+{
+	uchar *rpic, *gpic, *bpic;
+	int x, y, dx, dy, minx, miny;
+	int r, g, b, pici, H0, H1, H2;
+	int t, b0, b1, b2, y0, y1, y2, x0, x1, x2;
+	int Y, Cr, Cb;
+
+	rpic = image->chans[0];
+	gpic = image->chans[1];
+	bpic = image->chans[2];
+	minx = 8*Hmax*(mcu%nacross);
+	dx = 8*Hmax;
+	if(minx+dx > h->X)
+		dx = h->X-minx;
+	miny = 8*Vmax*(mcu/nacross);
+	dy = 8*Vmax;
+	if(miny+dy > h->Y)
+		dy = h->Y-miny;
+	pici = miny*h->X+minx;
+	H0 = H[0];
+	H1 = H[1];
+	H2 = H[2];
+	for(y=0; y<dy; y++){
+		t = y*V[0];
+		b0 = H0*(t/(8*Vmax));
+		y0 = 8*((t/Vmax)&7);
+		t = y*V[1];
+		b1 = H1*(t/(8*Vmax));
+		y1 = 8*((t/Vmax)&7);
+		t = y*V[2];
+		b2 = H2*(t/(8*Vmax));
+		y2 = 8*((t/Vmax)&7);
+		x0 = 0;
+		x1 = 0;
+		x2 = 0;
+		for(x=0; x<dx; x++){
+			if(colorspace == CYCbCr){
+				rpic[pici+x] = clamp[data0[b0][y0+x0++*H0/Hmax] + 128 + CLAMPOFF];
+				gpic[pici+x] = clamp[data1[b1][y1+x1++*H1/Hmax] + 128 + CLAMPOFF];
+				bpic[pici+x] = clamp[data2[b2][y2+x2++*H2/Hmax] + 128 + CLAMPOFF];
+			}else{
+				Y = (data0[b0][y0+x0++*H0/Hmax]+128)<<11;
+				Cb = data1[b1][y1+x1++*H1/Hmax];
+				Cr = data2[b2][y2+x2++*H2/Hmax];
+				r = Y+c1*Cr;
+				g = Y-c2*Cb-c3*Cr;
+				b = Y+c4*Cb;
+				rpic[pici+x] = clamp[(r>>11)+CLAMPOFF];
+				gpic[pici+x] = clamp[(g>>11)+CLAMPOFF];
+				bpic[pici+x] = clamp[(b>>11)+CLAMPOFF];
+			}
+			if(x0*H0/Hmax >= 8){
+				x0 = 0;
+				b0++;
+			}
+			if(x1*H1/Hmax >= 8){
+				x1 = 0;
+				b1++;
+			}
+			if(x2*H2/Hmax >= 8){
+				x2 = 0;
+				b2++;
+			}
+		}
+		pici += h->X;
+	}
+}
+
+/*
+ * decode next 8-bit value from entropy-coded input.  chart F-26
+ */
+static
+int
+decode(Header *h, Huffman *t)
+{
+	int code, v, cnt, m, sr, i;
+	int *maxcode;
+	static int badcode;
+
+	maxcode = t->maxcode;
+	if(h->cnt < 8)
+		nextbyte(h, 0);
+	/* fast lookup */
+	code = (h->sr>>(h->cnt-8))&0xFF;
+	v = t->value[code];
+	if(v >= 0){
+		h->cnt -= t->shift[code];
+		return v;
+	}
+
+	h->cnt -= 8;
+	if(h->cnt == 0)
+		nextbyte(h, 0);
+	h->cnt--;
+	cnt = h->cnt;
+	m = 1<<cnt;
+	sr = h->sr;
+	code <<= 1;
+	i = 9;
+	for(;;i++){
+		if(sr & m)
+			code |= 1;
+		if(code <= maxcode[i])
+			break;
+		code <<= 1;
+		m >>= 1;
+		if(m == 0){
+			sr = nextbyte(h, 0);
+			m = 0x80;
+			cnt = 8;
+		}
+		cnt--;
+	}
+	if(i >= 17){
+		if(badcode == 0)
+			fprint(2, "badly encoded %dx%d JPEG file; ignoring bad value\n", h->X, h->Y);
+		badcode = 1;
+		i = 0;
+	}
+	h->cnt = cnt;
+	return t->val[t->valptr[i]+(code-t->mincode[i])];
+}
+
+/*
+ * load next byte of input
+ */
+static
+int
+nextbyte(Header *h, int marker)
+{
+	int b, b2;
+
+	if(h->peek >= 0){
+		b = h->peek;
+		h->peek = -1;
+	}else{
+		b = Bgetc(h->fd);
+		if(b == Beof)
+			jpgerror(h, "truncated file");
+		b &= 0xFF;
+	}
+
+	if(b == 0xFF){
+		if(marker)
+			return b;
+		b2 = Bgetc(h->fd);
+		if(b2 != 0){
+			if(b2 == Beof)
+				jpgerror(h, "truncated file");
+			b2 &= 0xFF;
+			if(b2 == DNL)
+				jpgerror(h, "ReadJPG: DNL marker unimplemented");
+			/* decoder is reading into marker; satisfy it and restore state */
+			Bungetc(h->fd);
+			h->peek = b;
+		}
+	}
+	h->cnt += 8;
+	h->sr = (h->sr<<8) | b;
+	return b;
+}
+
+/*
+ * return next s bits of input, MSB first, and level shift it
+ */
+static
+int
+receive(Header *h, int s)
+{
+	int v, m;
+
+	while(h->cnt < s)
+		nextbyte(h, 0);
+	h->cnt -= s;
+	v = h->sr >> h->cnt;
+	m = (1<<s);
+	v &= m-1;
+	/* level shift */
+	if(v < (m>>1))
+		v += ~(m-1)+1;
+	return v;
+}
+
+/*
+ * return next s bits of input, decode as EOB
+ */
+static
+int
+receiveEOB(Header *h, int s)
+{
+	int v, m;
+
+	while(h->cnt < s)
+		nextbyte(h, 0);
+	h->cnt -= s;
+	v = h->sr >> h->cnt;
+	m = (1<<s);
+	v &= m-1;
+	/* level shift */
+	v += m;
+	return v;
+}
+
+/* 
+ * return next bit of input
+ */
+static
+int
+receivebit(Header *h)
+{
+	if(h->cnt < 1)
+		nextbyte(h, 0);
+	h->cnt--;
+	return (h->sr >> h->cnt) & 1;
+}
+
+/*
+ *  Scaled integer implementation.
+ *  inverse two dimensional DCT, Chen-Wang algorithm
+ * (IEEE ASSP-32, pp. 803-816, Aug. 1984)
+ * 32-bit integer arithmetic (8 bit coefficients)
+ * 11 mults, 29 adds per DCT
+ *
+ * coefficients extended to 12 bit for IEEE1180-1990 compliance
+ */
+
+enum {
+	W1		= 2841,	/* 2048*sqrt(2)*cos(1*pi/16)*/
+	W2		= 2676,	/* 2048*sqrt(2)*cos(2*pi/16)*/
+	W3		= 2408,	/* 2048*sqrt(2)*cos(3*pi/16)*/
+	W5		= 1609,	/* 2048*sqrt(2)*cos(5*pi/16)*/
+	W6		= 1108,	/* 2048*sqrt(2)*cos(6*pi/16)*/
+	W7		= 565,	/* 2048*sqrt(2)*cos(7*pi/16)*/
+
+	W1pW7	= 3406,	/* W1+W7*/
+	W1mW7	= 2276,	/* W1-W7*/
+	W3pW5	= 4017,	/* W3+W5*/
+	W3mW5	= 799,	/* W3-W5*/
+	W2pW6	= 3784,	/* W2+W6*/
+	W2mW6	= 1567,	/* W2-W6*/
+
+	R2		= 181	/* 256/sqrt(2)*/
+};
+
+static
+void
+idct(int b[8*8])
+{
+	int x, y, eighty, v;
+	int x0, x1, x2, x3, x4, x5, x6, x7, x8;
+	int *p;
+
+	/* transform horizontally*/
+	for(y=0; y<8; y++){
+		eighty = y<<3;
+		/* if all non-DC components are zero, just propagate the DC term*/
+		p = b+eighty;
+		if(p[1]==0)
+		if(p[2]==0 && p[3]==0)
+		if(p[4]==0 && p[5]==0)
+		if(p[6]==0 && p[7]==0){
+			v = p[0]<<3;
+			p[0] = v;
+			p[1] = v;
+			p[2] = v;
+			p[3] = v;
+			p[4] = v;
+			p[5] = v;
+			p[6] = v;
+			p[7] = v;
+			continue;
+		}
+		/* prescale*/
+		x0 = (p[0]<<11)+128;
+		x1 = p[4]<<11;
+		x2 = p[6];
+		x3 = p[2];
+		x4 = p[1];
+		x5 = p[7];
+		x6 = p[5];
+		x7 = p[3];
+		/* first stage*/
+		x8 = W7*(x4+x5);
+		x4 = x8 + W1mW7*x4;
+		x5 = x8 - W1pW7*x5;
+		x8 = W3*(x6+x7);
+		x6 = x8 - W3mW5*x6;
+		x7 = x8 - W3pW5*x7;
+		/* second stage*/
+		x8 = x0 + x1;
+		x0 -= x1;
+		x1 = W6*(x3+x2);
+		x2 = x1 - W2pW6*x2;
+		x3 = x1 + W2mW6*x3;
+		x1 = x4 + x6;
+		x4 -= x6;
+		x6 = x5 + x7;
+		x5 -= x7;
+		/* third stage*/
+		x7 = x8 + x3;
+		x8 -= x3;
+		x3 = x0 + x2;
+		x0 -= x2;
+		x2 = (R2*(x4+x5)+128)>>8;
+		x4 = (R2*(x4-x5)+128)>>8;
+		/* fourth stage*/
+		p[0] = (x7+x1)>>8;
+		p[1] = (x3+x2)>>8;
+		p[2] = (x0+x4)>>8;
+		p[3] = (x8+x6)>>8;
+		p[4] = (x8-x6)>>8;
+		p[5] = (x0-x4)>>8;
+		p[6] = (x3-x2)>>8;
+		p[7] = (x7-x1)>>8;
+	}
+	/* transform vertically*/
+	for(x=0; x<8; x++){
+		/* if all non-DC components are zero, just propagate the DC term*/
+		p = b+x;
+		if(p[8*1]==0)
+		if(p[8*2]==0 && p[8*3]==0)
+		if(p[8*4]==0 && p[8*5]==0)
+		if(p[8*6]==0 && p[8*7]==0){
+			v = (p[8*0]+32)>>6;
+			p[8*0] = v;
+			p[8*1] = v;
+			p[8*2] = v;
+			p[8*3] = v;
+			p[8*4] = v;
+			p[8*5] = v;
+			p[8*6] = v;
+			p[8*7] = v;
+			continue;
+		}
+		/* prescale*/
+		x0 = (p[8*0]<<8)+8192;
+		x1 = p[8*4]<<8;
+		x2 = p[8*6];
+		x3 = p[8*2];
+		x4 = p[8*1];
+		x5 = p[8*7];
+		x6 = p[8*5];
+		x7 = p[8*3];
+		/* first stage*/
+		x8 = W7*(x4+x5) + 4;
+		x4 = (x8+W1mW7*x4)>>3;
+		x5 = (x8-W1pW7*x5)>>3;
+		x8 = W3*(x6+x7) + 4;
+		x6 = (x8-W3mW5*x6)>>3;
+		x7 = (x8-W3pW5*x7)>>3;
+		/* second stage*/
+		x8 = x0 + x1;
+		x0 -= x1;
+		x1 = W6*(x3+x2) + 4;
+		x2 = (x1-W2pW6*x2)>>3;
+		x3 = (x1+W2mW6*x3)>>3;
+		x1 = x4 + x6;
+		x4 -= x6;
+		x6 = x5 + x7;
+		x5 -= x7;
+		/* third stage*/
+		x7 = x8 + x3;
+		x8 -= x3;
+		x3 = x0 + x2;
+		x0 -= x2;
+		x2 = (R2*(x4+x5)+128)>>8;
+		x4 = (R2*(x4-x5)+128)>>8;
+		/* fourth stage*/
+		p[8*0] = (x7+x1)>>14;
+		p[8*1] = (x3+x2)>>14;
+		p[8*2] = (x0+x4)>>14;
+		p[8*3] = (x8+x6)>>14;
+		p[8*4] = (x8-x6)>>14;
+		p[8*5] = (x0-x4)>>14;
+		p[8*6] = (x3-x2)>>14;
+		p[8*7] = (x7-x1)>>14;
+	}
+}
diff --git a/src/cmd/jpg/readpng.c b/src/cmd/jpg/readpng.c
new file mode 100644
index 0000000..1cb8594
--- /dev/null
+++ b/src/cmd/jpg/readpng.c
@@ -0,0 +1,334 @@
+//  work in progress...  this version only good enough to read
+//  non-interleaved, 24bit RGB images
+
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+#include <flate.h>
+#include <draw.h>
+#include "imagefile.h"
+
+int debug;
+
+enum{  IDATSIZE=1000000,
+	/* filtering algorithms, supposedly increase compression */
+	FilterNone =	0,	/* new[x][y] = buf[x][y] */
+	FilterSub	=	1,	/* new[x][y] = buf[x][y] + new[x-1][y] */ 
+	FilterUp	=	2,	/* new[x][y] = buf[x][y] + new[x][y-1] */ 
+	FilterAvg	=	3,	/* new[x][y] = buf[x][y] + (new[x-1][y]+new[x][y-1])/2 */ 
+	FilterPaeth=	4,	/* new[x][y] = buf[x][y] + paeth(new[x-1][y],new[x][y-1],new[x-1][y-1]) */ 
+	FilterLast	=	5,
+	PropertyBit =	1<<5,
+};
+
+typedef struct ZlibR{
+	Biobuf *bi;
+	uchar *buf;
+	uchar *b;	// next byte to decompress
+	uchar *e;	// past end of buf
+} ZlibR;
+
+typedef struct ZlibW{
+	uchar *r, *g, *b; // Rawimage channels
+	int chan;	// next channel to write
+	int col;	// column index of current pixel
+			// -1 = one-byte pseudo-column for filter spec
+	int row;	// row index of current pixel
+	int ncol, nrow;	// image width, height
+	int filter;	// algorithm for current scanline
+} ZlibW;
+
+static ulong *crctab;
+static uchar PNGmagic[] = {137,80,78,71,13,10,26,10};
+static char memerr[] = "ReadPNG: malloc failed: %r";
+
+static ulong
+get4(uchar *a)
+{
+	return (a[0]<<24) | (a[1]<<16) | (a[2]<<8) | a[3];
+}
+
+static
+void
+pnginit(void)
+{
+	static int inited;
+
+	if(inited)
+		return;
+	inited = 1;
+	crctab = mkcrctab(0xedb88320);
+	if(crctab == nil)
+		sysfatal("mkcrctab error");
+	inflateinit();
+}
+
+static
+void*
+pngmalloc(ulong n, int clear)
+{
+	void *p;
+
+	p = malloc(n);
+	if(p == nil)
+		sysfatal(memerr);
+	if(clear)
+		memset(p, 0, n);
+	return p;
+}
+
+static int
+getchunk(Biobuf *b, char *type, uchar *d, int m)
+{
+	uchar buf[8];
+	ulong crc = 0, crc2;
+	int n, nr;
+
+	if(Bread(b, buf, 8) != 8)
+		return -1;
+	n = get4(buf);
+	memmove(type, buf+4, 4);
+	type[4] = 0;
+	if(n > m)
+		sysfatal("getchunk needed %d, had %d", n, m);
+	nr = Bread(b, d, n);
+	if(nr != n)
+		sysfatal("getchunk read %d, expected %d", nr, n);
+	crc = blockcrc(crctab, crc, type, 4);
+	crc = blockcrc(crctab, crc, d, n);
+	if(Bread(b, buf, 4) != 4)
+		sysfatal("getchunk tlr failed");
+	crc2 = get4(buf);
+	if(crc != crc2)
+		sysfatal("getchunk crc failed");
+	return n;
+}
+
+static int
+zread(void *va)
+{
+	ZlibR *z = va;
+	char type[5];
+	int n;
+
+	if(z->b >= z->e){
+refill_buffer:
+		z->b = z->buf;
+		n = getchunk(z->bi, type, z->b, IDATSIZE);
+		if(n < 0 || strcmp(type, "IEND") == 0)
+			return -1;
+		z->e = z->b + n;
+		if(type[0] & PropertyBit)
+			goto refill_buffer;  /* skip auxiliary chunks for now */
+		if(strcmp(type,"IDAT") != 0)
+			sysfatal("unrecognized mandatory chunk %s", type);
+	}
+	return *z->b++;
+}
+
+static uchar 
+paeth(uchar a, uchar b, uchar c)
+{
+	int p, pa, pb, pc;
+	
+	p = (int)a + (int)b - (int)c;
+	pa = abs(p - (int)a);
+	pb = abs(p - (int)b);
+	pc = abs(p - (int)c);
+
+	if(pa <= pb && pa <= pc)
+		return a;
+	else if(pb <= pc)
+		return b;
+	return c;
+}
+
+static void
+unfilter(int alg, uchar *buf, uchar *ebuf, int up)
+{
+	switch(alg){
+	case FilterSub:
+		while (++buf < ebuf)
+			*buf += buf[-1];
+		break;
+	case FilterUp:
+		if (up != 0)
+			do
+				*buf += buf[up];
+			while (++buf < ebuf);
+		break;
+	case FilterAvg:
+		if (up == 0)
+			while (++buf < ebuf)
+				*buf += buf[-1]/2;
+		else{
+			*buf += buf[up]/2;
+			while (++buf < ebuf)
+				*buf += (buf[-1]+buf[up])/2;
+		}
+		break;
+	case FilterPaeth:
+		if (up == 0)
+			while (++buf < ebuf)
+				*buf += buf[-1];
+		else{
+			*buf += paeth(0, buf[up], 0);
+			while (++buf < ebuf)
+				*buf += paeth(buf[-1], buf[up], buf[up-1]);
+		}
+		break;
+	}
+}
+
+static int
+zwrite(void *va, void *vb, int n)
+{
+	ZlibW *z = va;
+	uchar *buf = vb;
+	int i, up;
+	for(i=0; i<n; i++){
+		if(z->col == -1){
+			// set filter byte
+			z->filter = *buf++;
+			if (z->filter >= FilterLast)
+				sysfatal("unknown filter algorithm %d for row %d", z->row, z->filter);
+			z->col++;
+			continue;
+		}
+		switch(z->chan){
+		case 0:
+			*z->r++ = *buf++;
+			z->chan = 1;
+			break;
+		case 1:
+			*z->g++ = *buf++;
+			z->chan = 2;
+			break;
+		case 2:
+			*z->b++ = *buf++;
+			z->chan = 0;
+			z->col++;
+			if(z->col == z->ncol){
+				if (z->filter){
+					if(z->row == 0)
+						up = 0;
+					else
+						up = -z->ncol;
+					unfilter(z->filter, z->r - z->col, z->r, up);
+					unfilter(z->filter, z->g - z->col, z->g, up);
+					unfilter(z->filter, z->b - z->col, z->b, up);
+				}
+				z->col = -1;
+				z->row++;
+				if((z->row >= z->nrow) && (i < n-1) )
+					sysfatal("header said %d rows; data goes further", z->nrow);
+			}
+			break;
+		}
+	}
+	return n;
+}
+
+static Rawimage*
+readslave(Biobuf *b)
+{
+	ZlibR zr;
+	ZlibW zw;
+	Rawimage *image;
+	char type[5];
+	uchar *buf, *h;
+	int k, n, nrow, ncol, err;
+
+	buf = pngmalloc(IDATSIZE, 0);
+	Bread(b, buf, sizeof PNGmagic);
+	if(memcmp(PNGmagic, buf, sizeof PNGmagic) != 0)
+		sysfatal("bad PNGmagic");
+
+	n = getchunk(b, type, buf, IDATSIZE);
+	if(n < 13 || strcmp(type,"IHDR") != 0)
+		sysfatal("missing IHDR chunk");
+	h = buf;
+	ncol = get4(h);  h += 4;
+	nrow = get4(h);  h += 4;
+	if(ncol <= 0 || nrow <= 0)
+		sysfatal("impossible image size nrow=%d ncol=%d", nrow, ncol);
+	if(debug)
+		fprint(2, "readpng nrow=%d ncol=%d\n", nrow, ncol);
+	if(*h++ != 8)
+		sysfatal("only 24 bit per pixel supported for now [%d]", h[-1]);
+	if(*h++ != 2)
+		sysfatal("only rgb supported for now [%d]", h[-1]);
+	if(*h++ != 0)
+		sysfatal("only deflate supported for now [%d]", h[-1]);
+	if(*h++ != FilterNone)
+		sysfatal("only FilterNone supported for now [%d]", h[-1]);
+	if(*h != 0)
+		sysfatal("only non-interlaced supported for now [%d]", h[-1]);
+
+	image = pngmalloc(sizeof(Rawimage), 1);
+	image->r = Rect(0, 0, ncol, nrow);
+	image->cmap = nil;
+	image->cmaplen = 0;
+	image->chanlen = ncol*nrow;
+	image->fields = 0;
+	image->gifflags = 0;
+	image->gifdelay = 0;
+	image->giftrindex = 0;
+	image->chandesc = CRGB;
+	image->nchans = 3;
+	for(k=0; k<3; k++)
+		image->chans[k] = pngmalloc(ncol*nrow, 0);
+	zr.bi = b;
+	zr.buf = buf;
+	zr.b = zr.e = buf + IDATSIZE;
+	zw.r = image->chans[0];
+	zw.g = image->chans[1];
+	zw.b = image->chans[2];
+	zw.chan = 0;
+	zw.col = -1;
+	zw.row = 0;
+	zw.ncol = ncol;
+	zw.nrow = nrow;
+	err = inflatezlib(&zw, zwrite, &zr, zread);
+	if(err)
+		sysfatal("inflatezlib %s\n", flateerr(err));
+	free(buf);
+	return image;
+}
+
+Rawimage**
+Breadpng(Biobuf *b, int colorspace)
+{
+	Rawimage *r, **array;
+	char buf[ERRMAX];
+
+	buf[0] = '\0';
+	if(colorspace != CRGB){
+		errstr(buf, sizeof buf);	/* throw it away */
+		werrstr("ReadPNG: unknown color space %d", colorspace);
+		return nil;
+	}
+	pnginit();
+	array = malloc(2*sizeof(*array));
+	if(array==nil)
+		return nil;
+	errstr(buf, sizeof buf);	/* throw it away */
+	r = readslave(b);
+	array[0] = r;
+	array[1] = nil;
+	return array;
+}
+
+Rawimage**
+readpng(int fd, int colorspace)
+{
+	Rawimage** a;
+	Biobuf b;
+
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+	a = Breadpng(&b, colorspace);
+	Bterm(&b);
+	return a;
+}
diff --git a/src/cmd/jpg/readppm.c b/src/cmd/jpg/readppm.c
new file mode 100644
index 0000000..28b7f4e
--- /dev/null
+++ b/src/cmd/jpg/readppm.c
@@ -0,0 +1,238 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <ctype.h>
+#include "imagefile.h"
+
+Rawimage *readppm(Biobuf*, Rawimage*);
+
+/*
+ * fetch a non-comment character.
+ */
+static
+int
+Bgetch(Biobuf *b)
+{
+	int c;
+
+	for(;;) {
+		c = Bgetc(b);
+		if(c == '#') {
+			while((c = Bgetc(b)) != Beof && c != '\n')
+				;
+		}
+		return c;
+	}		
+}
+
+/*
+ * fetch a nonnegative decimal integer.
+ */
+static
+int
+Bgetint(Biobuf *b)
+{
+	int c;
+	int i;
+
+	while((c = Bgetch(b)) != Beof && !isdigit(c))
+		;
+	if(c == Beof)
+		return -1;
+
+	i = 0;
+	do { 
+		i = i*10 + (c-'0');
+	} while((c = Bgetch(b)) != Beof && isdigit(c));
+
+	return i;
+}
+
+static
+int
+Bgetdecimalbit(Biobuf *b)
+{
+	int c;
+	while((c = Bgetch(b)) != Beof && c != '0' && c != '1')
+		;
+	if(c == Beof)
+		return -1;
+	return c == '1';
+}
+
+static int bitc, nbit;
+
+static
+int
+Bgetbit(Biobuf *b)
+{
+	if(nbit == 0) {
+		nbit = 8;
+		bitc = Bgetc(b);
+		if(bitc == -1)
+			return -1;
+	}
+	nbit--;
+	return (bitc >> (nbit-1)) & 0x1;
+}
+
+static
+void
+Bflushbit(Biobuf *b)
+{
+	USED(b);
+	nbit = 0;
+}
+
+
+Rawimage**
+readpixmap(int fd, int colorspace)
+{
+	Rawimage **array, *a;
+	Biobuf b;
+	char buf[ERRMAX];
+	int i;
+	char *e;
+
+	USED(colorspace);
+	if(Binit(&b, fd, OREAD) < 0)
+		return nil;
+
+	werrstr("");
+	e = "out of memory";
+	if((array = malloc(sizeof *array)) == nil)
+		goto Error;
+	if((array[0] = malloc(sizeof *array[0])) == nil)
+		goto Error;
+	memset(array[0], 0, sizeof *array[0]);
+
+	for(i=0; i<3; i++)
+		array[0]->chans[i] = nil;
+
+	e = "bad file format";
+	switch(Bgetc(&b)) {
+	case 'P':
+		Bungetc(&b);
+		a = readppm(&b, array[0]);
+		break;
+	default:
+		a = nil;
+		break;
+	}
+	if(a == nil)
+		goto Error;
+	array[0] = a;
+
+	return array;
+
+Error:
+	if(array)
+		free(array[0]);
+	free(array);
+
+	errstr(buf, sizeof buf);
+	if(buf[0] == 0)
+		strcpy(buf, e);
+	errstr(buf, sizeof buf);
+
+	return nil;
+}
+
+typedef struct Pix	Pix;
+struct Pix {
+	char magic;
+	int	maxcol;
+	int	(*fetch)(Biobuf*);
+	int	nchan;
+	int	chandesc;
+	int	invert;
+	void	(*flush)(Biobuf*);
+};
+
+static Pix pix[] = {
+	{ '1', 1, Bgetdecimalbit, 1, CY, 1, nil },	/* portable bitmap */
+	{ '4', 1, Bgetbit, 1, CY, 1, Bflushbit },	/* raw portable bitmap */
+	{ '2', 0, Bgetint, 1, CY, 0, nil },	/* portable greymap */
+	{ '5', 0, Bgetc, 1, CY, 0, nil },	/* raw portable greymap */
+	{ '3', 0, Bgetint, 3, CRGB, 0, nil },	/* portable pixmap */
+	{ '6', 0, Bgetc, 3, CRGB, 0, nil },	/* raw portable pixmap */
+	{ 0 },
+};
+
+Rawimage*
+readppm(Biobuf *b, Rawimage *a)
+{
+	int i, ch, wid, ht, r, c;
+	int maxcol, nchan, invert;
+	int (*fetch)(Biobuf*);
+	uchar *rgb[3];
+	char buf[ERRMAX];
+	char *e;
+	Pix *p;
+
+	e = "bad file format";
+	if(Bgetc(b) != 'P')
+		goto Error;
+
+	c = Bgetc(b);
+	for(p=pix; p->magic; p++)
+		if(p->magic == c)
+			break;
+	if(p->magic == 0)
+		goto Error;
+
+
+	wid = Bgetint(b);
+	ht = Bgetint(b);
+	if(wid <= 0 || ht <= 0)
+		goto Error;
+	a->r = Rect(0,0,wid,ht);
+
+	maxcol = p->maxcol;
+	if(maxcol == 0) {
+		maxcol = Bgetint(b);
+		if(maxcol <= 0)
+			goto Error;
+	}
+
+	e = "out of memory";
+	for(i=0; i<p->nchan; i++)
+		if((rgb[i] = a->chans[i] = malloc(wid*ht)) == nil)
+			goto Error;
+	a->nchans = p->nchan;
+	a->chanlen = wid*ht;
+	a->chandesc = p->chandesc;
+
+	e = "error reading file";
+
+	fetch = p->fetch;
+	nchan = p->nchan;
+	invert = p->invert;
+	for(r=0; r<ht; r++) {
+		for(c=0; c<wid; c++) {
+			for(i=0; i<nchan; i++) {
+				if((ch = (*fetch)(b)) < 0)
+					goto Error;
+				if(invert)
+					ch = maxcol - ch;
+				*rgb[i]++ = (ch * 255)/maxcol;
+			}
+		}
+		if(p->flush)
+			(*p->flush)(b);
+	}
+
+	return a;
+
+Error:
+	errstr(buf, sizeof buf);
+	if(buf[0] == 0)
+		strcpy(buf, e);
+	errstr(buf, sizeof buf);
+
+	for(i=0; i<3; i++)
+		free(a->chans[i]);
+	free(a->cmap);
+	return nil;
+}
diff --git a/src/cmd/jpg/readyuv.c b/src/cmd/jpg/readyuv.c
new file mode 100644
index 0000000..eae2764
--- /dev/null
+++ b/src/cmd/jpg/readyuv.c
@@ -0,0 +1,190 @@
+/* readyuv.c - read an Abekas A66 style image file.   Steve Simon, 2003 */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <ctype.h>
+#include "imagefile.h"
+
+/*
+ * ITU/CCIR Rec601 states:
+ *
+ * R = y + 1.402 * Cr
+ * B = Y + 1.77305 * Cb
+ * G = Y - 0.72414 * Cr - 0.34414 * Cb
+ *
+ *	using 8 bit traffic
+ * Y = 16 + 219 * Y
+ * Cr = 128 + 224 * Cr
+ * Cb = 128 + 224 * Cb
+ * 	or, if 10bit is used
+ * Y = 64 + 876 * Y
+ * Cr = 512 + 896 * Cr
+ * Cb = 512 + 896 * Cb
+ */
+
+enum {
+	PAL = 576, NTSC = 486 };
+
+
+static int lsbtab[] = { 6, 4, 2, 0};
+
+static int 
+clip(int x)
+{
+	x >>= 18;
+
+	if (x > 255)
+		return 0xff;
+	if (x <= 0)
+		return 0;
+	return x;
+}
+
+
+Rawimage**
+Breadyuv(Biobuf *bp, int colourspace)
+{
+	Dir * d;
+	Rawimage * a, **array;
+	char	*e, ebuf[128];
+	ushort * mux, *end, *frm;
+	uchar buf[720 * 2], *r, *g, *b;
+	int	y1, y2, cb, cr, sz, c, l, w, base, bits, lines;
+
+	frm = 0;
+	if (colourspace != CYCbCr) {
+		errstr(ebuf, sizeof ebuf);	/* throw it away */
+		werrstr("ReadYUV: unknown colour space %d", colourspace);
+		return nil;
+	}
+
+	if ((a = calloc(sizeof(Rawimage), 1)) == nil)
+		sysfatal("no memory");
+
+	if ((array = calloc(sizeof(Rawimage * ), 2)) == nil)
+		sysfatal("no memory");
+	array[0] = a;
+	array[1] = nil;
+
+	if ((d = dirfstat(Bfildes(bp))) != nil) {
+		sz = d->length;
+		free(d);
+	} else {
+		fprint(2, "cannot stat input, assuming 720x576x10bit\n");
+		sz = 720 * PAL * 2L + (720 * PAL / 2L);
+	}
+
+	switch (sz) {
+	case 720 * PAL * 2:				// 625 x 8bit
+		bits = 8;
+		lines = PAL;
+		break;
+	case 720 * NTSC * 2:				// 525 x 8bit
+		bits = 8;
+		lines = NTSC;
+		break;
+	case 720 * PAL * 2 + (720 * PAL / 2) :		// 625 x 10bit
+			bits = 10;
+		lines = PAL;
+		break;
+	case 720 * NTSC * 2 + (720 * NTSC / 2) :	// 525 x 10bit
+			bits = 10;
+		lines = NTSC;
+		break;
+	default:
+		e = "unknown file size";
+		goto Error;
+	}
+
+	//	print("bits=%d pixels=%d lines=%d\n", bits, 720, lines);
+	//
+	a->nchans = 3;
+	a->chandesc = CRGB;
+	a->chanlen = 720 * lines;
+	a->r = Rect(0, 0, 720, lines);
+
+	e = "no memory";
+	if ((frm = malloc(720 * 2 * lines * sizeof(ushort))) == nil)
+		goto Error;
+
+	for (c = 0; c  < 3; c++)
+		if ((a->chans[c] = malloc(720 * lines)) == nil)
+			goto Error;
+
+	e = "read file";
+	for (l = 0; l < lines; l++) {
+		if (Bread(bp, buf, 720 * 2) == -1)
+			goto Error;
+
+		base = l * 720 * 2;
+		for (w = 0; w < 720 * 2; w++)
+			frm[base + w] = ((ushort)buf[w]) << 2;
+	}
+
+
+	if (bits == 10)
+		for (l = 0; l < lines; l++) {
+			if (Bread(bp, buf, 720 / 2) == -1)
+				goto Error;
+
+
+			base = l * 720 * 2;
+			for (w = 0; w < 720 * 2; w++)
+				frm[base + w] |= buf[w / 4] >> lsbtab[w % 4];
+		}
+
+	mux = frm;
+	end = frm + 720 * lines * 2;
+	r = a->chans[0];
+	g = a->chans[1];
+	b = a->chans[2];
+
+	while (mux < end) {
+		cb = *mux++ - 512;
+		y1 = (*mux++ - 64) * 76310;
+		cr = *mux++ - 512;
+		y2 = (*mux++ - 64) * 76310;
+
+		*r++ = clip((104635 * cr) + y1);
+		*g++ = clip((-25690 * cb + -53294 * cr) + y1);
+		*b++ = clip((132278 * cb) + y1);
+
+		*r++ = clip((104635 * cr) + y2);
+		*g++ = clip((-25690 * cb + -53294 * cr) + y2);
+		*b++ = clip((132278 * cb) + y2);
+	}
+	free(frm);
+	return array;
+
+Error:
+
+	errstr(ebuf, sizeof ebuf);
+	if (ebuf[0] == 0)
+		strcpy(ebuf, e);
+	errstr(ebuf, sizeof ebuf);
+
+	for (c = 0; c < 3; c++)
+		free(a->chans[c]);
+	free(a->cmap);
+	free(array[0]);
+	free(array);
+	free(frm);
+	return nil;
+}
+
+
+Rawimage**
+readyuv(int fd, int colorspace)
+{
+	Rawimage * *a;
+	Biobuf b;
+
+	if (Binit(&b, fd, OREAD) < 0)
+		return nil;
+	a = Breadyuv(&b, colorspace);
+	Bterm(&b);
+	return a;
+}
+
+
diff --git a/src/cmd/jpg/rgbrgbv.c b/src/cmd/jpg/rgbrgbv.c
new file mode 100644
index 0000000..bc55947
--- /dev/null
+++ b/src/cmd/jpg/rgbrgbv.c
@@ -0,0 +1,69 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+/*
+ * This version of closest() is now (feb 20, 2001) installed as rgb2cmap in libdraw
+ */
+
+int
+closest(int cr, int cg, int cb)
+{
+	int i, r, g, b, sq;
+	ulong rgb;
+	int best, bestsq;
+
+	best = 0;
+	bestsq = 0x7FFFFFFF;
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		r = (rgb>>16) & 0xFF;
+		g = (rgb>>8) & 0xFF;
+		b = (rgb>>0) & 0xFF;
+		sq = (r-cr)*(r-cr)+(g-cg)*(g-cg)+(b-cb)*(b-cb);
+		if(sq < bestsq){
+			bestsq = sq;
+			best = i;
+		}
+	}
+	return best;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, rgb;
+	int r, g, b;
+	uchar close[16*16*16];
+
+	/* rgbmap */
+	print("uint rgbmap[256] = {\n");
+	for(i=0; i<256; i++){
+		if(i%8 == 0)
+			print("\t");
+		rgb = cmap2rgb(i);
+		r = (rgb>>16) & 0xFF;
+		g = (rgb>>8) & 0xFF;
+		b = (rgb>>0) & 0xFF;
+		print("0x%.6ulX, ", (r<<16) | (g<<8) | b);
+		if(i%8 == 7)
+			print("\n");
+	}
+	print("};\n\n");
+
+	/* closestrgb */
+	print("uchar closestrgb[16*16*16] = {\n");
+	for(r=0; r<256; r+=16)
+	for(g=0; g<256; g+=16)
+	for(b=0; b<256; b+=16)
+		close[(b/16)+16*((g/16)+16*(r/16))] = closest(r+8, g+8, b+8);
+	for(i=0; i<16*16*16; i++){
+		if(i%16 == 0)
+			print("\t");
+		print("%d,", close[i]);
+		if(i%16 == 15)
+			print("\n");
+	}
+	print("};\n\n");
+	exits(nil);
+}
diff --git a/src/cmd/jpg/rgbycc.c b/src/cmd/jpg/rgbycc.c
new file mode 100644
index 0000000..3654a2d
--- /dev/null
+++ b/src/cmd/jpg/rgbycc.c
@@ -0,0 +1,120 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+
+float c1 = 1.402;
+float c2 = 0.34414;
+float c3 = 0.71414;
+float c4 = 1.772;
+
+int
+closest(int Y, int Cb, int Cr)
+{
+	double r, g, b;
+	double diff, min;
+	int rgb, R, G, B, v, i;
+	int y1, cb1, cr1;
+
+	Cb -= 128;
+	Cr -= 128;
+	r = Y+c1*Cr;
+	g = Y-c2*Cb-c3*Cr;
+	b = Y+c4*Cb;
+
+//print("YCbCr: %d %d %d, RGB: %g %g %g\n", Y, Cb, Cr, r, g, b);
+
+	min = 1000000.;
+	v = 1000;
+	for(i=0; i<256; i++){
+		rgb =  cmap2rgb(i);
+		R = (rgb >> 16) & 0xFF;
+		G = (rgb >> 8) & 0xFF;
+		B = (rgb >> 0) & 0xFF;
+		diff = (R-r)*(R-r) + (G-g)*(G-g) + (B-b)*(B-b);
+		y1 = 0.5870*G + 0.114*B + 0.299*R;
+		cb1 = (B-y1)/1.772;
+		cr1 = (R-y1)/1.402;
+		if(diff < min){
+//			if(Y==0 && y1!=0)
+//				continue;
+			if(Y==256-16 && y1<256-16)
+				continue;
+//			if(Cb==0 && cb1!=0)
+//				continue;
+			if(Cb==256-16 && cb1<256-16)
+				continue;
+//			if(Cr==0 && cr1!=0)
+//				continue;
+			if(Cr==256-16 && cr1<256-16)
+				continue;
+//print("%d %d %d\n", R, G, B);
+			min = diff;
+			v = i;
+		}
+	}
+	if(v > 255)
+		abort();
+	return v;
+}
+
+void
+main(int argc, char *argv[])
+{
+	int i, rgb;
+	int r, g, b;
+	double Y, Cr, Cb;
+	int y, cb, cr;
+	uchar close[16*16*16];
+
+//print("%d\n", closest(atoi(argv[1]), atoi(argv[2]), atoi(argv[3])));
+//exits("X");
+
+	/* ycbcrmap */
+	print("uint ycbcrmap[256] = {\n");
+	for(i=0; i<256; i++){
+		if(i%8 == 0)
+			print("\t");
+		rgb = cmap2rgb(i);
+		r = (rgb>>16) & 0xFF;
+		g = (rgb>>8) & 0xFF;
+		b = (rgb>>0) & 0xFF;
+		Y = 0.5870*g + 0.114*b + 0.299*r;
+		Cr = (r-Y)/1.402 + 128.;
+		Cb = (b-Y)/1.772 + 128.;
+		if(Y<0. || Y>=256. || Cr<0. || Cr>=256. || Cb<0. || Cb>=256.)
+			print("bad at %d: %d %d %d; %g %g %g\n", i, r, g, b, Y, Cb, Cr);
+		r = Y;
+		g = Cb;
+		b = Cr;
+		print("0x%.6ulX, ", (r<<16) | (g<<8) | b);
+		if(i%8 == 7)
+			print("\n");
+	}
+	print("};\n\n");
+
+	/* closestycbcr */
+	print("uchar closestycbcr[16*16*16] = {\n");
+	for(y=0; y<256; y+=16)
+	for(cb=0; cb<256; cb+=16)
+	for(cr=0; cr<256; cr+=16)
+		close[(cr/16)+16*((cb/16)+16*(y/16))] = closest(y, cb, cr);
+if(0){
+	/*weird: set white for nearly white */
+	for(cb=128-32; cb<=128+32; cb+=16)
+	for(cr=128-32; cr<=128+32; cr+=16)
+		close[(cr/16)+16*((cb/16)+16*(255/16))] = 0;
+	/*weird: set black for nearly black */
+	for(cb=128-32; cb<=128+32; cb+=16)
+	for(cr=128-32; cr<=128+32; cr+=16)
+		close[(cr/16)+16*((cb/16)+16*(0/16))] = 255;
+}
+	for(i=0; i<16*16*16; i++){
+		if(i%16 == 0)
+			print("\t");
+		print("%d,", close[i]);
+		if(i%16 == 15)
+			print("\n");
+	}
+	print("};\n\n");
+	exits(nil);
+}
diff --git a/src/cmd/jpg/togif.c b/src/cmd/jpg/togif.c
new file mode 100644
index 0000000..3533688
--- /dev/null
+++ b/src/cmd/jpg/togif.c
@@ -0,0 +1,147 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <ctype.h>
+#include <bio.h>
+#include "imagefile.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: togif [-l loopcount] [-c 'comment'] [-d Δt (ms)] [-t transparency-index] [file ... [-d Δt] file ...]\n");
+	exits("usage");
+}
+
+#define	UNSET (-12345678)
+
+void
+main(int argc, char *argv[])
+{
+	Biobuf bout;
+	Memimage *i, *ni;
+	int fd, j, dt, trans, loop;
+	char buf[256];
+	char *err, *comment, *s;
+
+	comment = nil;
+	dt = -1;
+	trans = -1;
+	loop = UNSET;
+	ARGBEGIN{
+	case 'l':
+		s = ARGF();
+		if(s==nil || (!isdigit(s[0]) && s[0]!='-'))
+			usage();
+		loop = atoi(s);
+		break;
+	case 'c':
+		comment = ARGF();
+		if(comment == nil)
+			usage();
+		break;
+	case 'd':
+		s = ARGF();
+		if(s==nil || !isdigit(s[0]))
+			usage();
+		dt = atoi(s);
+		break;
+	case 't':
+		s = ARGF();
+		if(s==nil || !isdigit(s[0]))
+			usage();
+		trans = atoi(s);
+		if(trans > 255)
+			usage();
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(Binit(&bout, 1, OWRITE) < 0)
+		sysfatal("Binit failed: %r");
+
+	memimageinit();
+
+	err = nil;
+
+	if(argc == 0){
+		i = readmemimage(0);
+		if(i == nil)
+			sysfatal("reading input: %r");
+		ni = memonechan(i);
+		if(ni == nil)
+			sysfatal("converting image to RGBV: %r");
+		if(i != ni){
+			freememimage(i);
+			i = ni;
+		}
+		err = memstartgif(&bout, i, -1);
+		if(err == nil){
+			if(comment)
+				err = memwritegif(&bout, i, comment, dt, trans);
+			else{
+				snprint(buf, sizeof buf, "Converted by Plan 9 from <stdin>");
+				err = memwritegif(&bout, i, buf, dt, trans);
+			}
+		}
+	}else{
+		if(loop == UNSET){
+			if(argc == 1)
+				loop = -1;	/* no loop for single image */
+			else
+				loop = 0;	/* the default case: 0 means infinite loop */
+		}
+		for(j=0; j<argc; j++){
+			if(argv[j][0] == '-' && argv[j][1]=='d'){
+				/* time change */
+				if(argv[j][2] == '\0'){
+					s = argv[++j];
+					if(j == argc)
+						usage();
+				}else
+					s = &argv[j][2];
+				if(!isdigit(s[0]))
+					usage();
+				dt = atoi(s);
+				if(j == argc-1)	/* last argument must be file */
+					usage();
+				continue;
+			}
+			fd = open(argv[j], OREAD);
+			if(fd < 0)
+				sysfatal("can't open %s: %r", argv[j]);
+			i = readmemimage(fd);
+			if(i == nil)
+				sysfatal("can't readimage %s: %r", argv[j]);
+			close(fd);
+			ni = memonechan(i);
+			if(ni == nil)
+				sysfatal("converting image to RGBV: %r");
+			if(i != ni){
+				freememimage(i);
+				i = ni;
+			}
+			if(j == 0){
+				err = memstartgif(&bout, i, loop);
+				if(err != nil)
+					break;
+			}
+			if(comment)
+				err = memwritegif(&bout, i, comment, dt, trans);
+			else{
+				snprint(buf, sizeof buf, "Converted by Plan 9 from %s", argv[j]);
+				err = memwritegif(&bout, i, buf, dt, trans);
+			}
+			if(err != nil)
+				break;
+			freememimage(i);
+			comment = nil;
+		}
+	}
+	memendgif(&bout);
+
+	if(err != nil)
+		fprint(2, "togif: %s\n", err);
+	exits(err);
+}
diff --git a/src/cmd/jpg/toico.c b/src/cmd/jpg/toico.c
new file mode 100644
index 0000000..059e018
--- /dev/null
+++ b/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 */
+	initdraw(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);
+}
diff --git a/src/cmd/jpg/topng.c b/src/cmd/jpg/topng.c
new file mode 100644
index 0000000..09c0061
--- /dev/null
+++ b/src/cmd/jpg/topng.c
@@ -0,0 +1,70 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <ctype.h>
+#include <bio.h>
+#include <flate.h>
+#include "imagefile.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: topng [-c 'comment'] [-g 'gamma'] [file]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Biobuf bout;
+	Memimage *i;
+	int fd;
+	char *err, *filename;
+	ImageInfo II;
+
+	ARGBEGIN{
+	case 'c':
+		II.comment = ARGF();
+		if(II.comment == nil)
+			usage();
+		II.fields_set |= II_COMMENT;
+		break;
+	case 'g':
+		II.gamma = atof(ARGF());
+		if(II.gamma == 0.)
+			usage();
+		II.fields_set |= II_GAMMA;
+		break;
+	case 't':
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(Binit(&bout, 1, OWRITE) < 0)
+		sysfatal("Binit failed: %r");
+	memimageinit();
+
+	if(argc == 0){
+		fd = 0;
+		filename = "<stdin>";
+	}else{
+		fd = open(argv[0], OREAD);
+		if(fd < 0)
+			sysfatal("can't open %s: %r", argv[0]);
+		filename = argv[0];
+	}
+
+	i = readmemimage(fd);
+	if(i == nil)
+		sysfatal("can't readimage %s: %r", filename);
+	close(fd);
+
+	err = memwritepng(&bout, i, &II);
+	freememimage(i);
+
+	if(err != nil)
+		fprint(2, "topng: %s\n", err);
+	exits(err);
+}
diff --git a/src/cmd/jpg/toppm.c b/src/cmd/jpg/toppm.c
new file mode 100644
index 0000000..e309465
--- /dev/null
+++ b/src/cmd/jpg/toppm.c
@@ -0,0 +1,90 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <ctype.h>
+#include <bio.h>
+#include "imagefile.h"
+
+void
+usage(void)
+{
+	fprint(2, "usage: toppm [-c 'comment'] [file]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Biobuf bout;
+	Memimage *i, *ni;
+	int fd;
+	char buf[256];
+	char *err, *comment;
+
+	comment = nil;
+	ARGBEGIN{
+	case 'c':
+		comment = ARGF();
+		if(comment == nil)
+			usage();
+		if(strchr(comment, '\n') != nil){
+			fprint(2, "ppm: comment cannot contain newlines\n");
+			usage();
+		}
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc > 1)
+		usage();
+
+	if(Binit(&bout, 1, OWRITE) < 0)
+		sysfatal("Binit failed: %r");
+
+	memimageinit();
+
+	err = nil;
+
+	if(argc == 0){
+		i = readmemimage(0);
+		if(i == nil)
+			sysfatal("reading input: %r");
+		ni = memmultichan(i);
+		if(ni == nil)
+			sysfatal("converting image to RGBV: %r");
+		if(i != ni){
+			freememimage(i);
+			i = ni;
+		}
+		if(err == nil)
+			err = memwriteppm(&bout, i, comment);
+	}else{
+		fd = open(argv[0], OREAD);
+		if(fd < 0)
+			sysfatal("can't open %s: %r", argv[0]);
+		i = readmemimage(fd);
+		if(i == nil)
+			sysfatal("can't readimage %s: %r", argv[0]);
+		close(fd);
+		ni = memmultichan(i);
+		if(ni == nil)
+			sysfatal("converting image to RGBV: %r");
+		if(i != ni){
+			freememimage(i);
+			i = ni;
+		}
+		if(comment)
+			err = memwriteppm(&bout, i, comment);
+		else{
+			snprint(buf, sizeof buf, "Converted by Plan 9 from %s", argv[0]);
+			err = memwriteppm(&bout, i, buf);
+		}
+		freememimage(i);
+	}
+
+	if(err != nil)
+		fprint(2, "toppm: %s\n", err);
+	exits(err);
+}
diff --git a/src/cmd/jpg/torgbv.c b/src/cmd/jpg/torgbv.c
new file mode 100644
index 0000000..e83f82c
--- /dev/null
+++ b/src/cmd/jpg/torgbv.c
@@ -0,0 +1,299 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+
+#include "rgbv.h"
+#include "ycbcr.h"
+
+#define	CLAMPOFF 128
+
+static	int	clamp[CLAMPOFF+256+CLAMPOFF];
+static	int	inited;
+
+void*
+_remaperror(char *fmt, ...)
+{
+	va_list arg;
+	char buf[256];
+
+	va_start(arg, fmt);
+	vseprint(buf, buf+sizeof buf, fmt, arg);
+	va_end(arg);
+
+	werrstr(buf);
+	return nil;
+}
+
+Rawimage*
+torgbv(Rawimage *i, int errdiff)
+{
+	int j, k, rgb, x, y, er, eg, eb, col, t;
+	int r, g, b, r1, g1, b1;
+	int *ered, *egrn, *eblu, *rp, *gp, *bp;
+	uint *map3;
+	uchar *closest;
+	Rawimage *im;
+	int dx, dy;
+	char err[ERRMAX];
+	uchar *cmap, *cm, *in, *out, *inp, *outp, cmap1[3*256], map[256], *rpic, *bpic, *gpic;
+
+	err[0] = '\0';
+	errstr(err, sizeof err);	/* throw it away */
+	im = malloc(sizeof(Rawimage));
+	if(im == nil)
+		return nil;
+	memset(im, 0, sizeof(Rawimage));
+	im->chans[0] = malloc(i->chanlen);
+	if(im->chans[0] == nil){
+		free(im);
+		return nil;
+	}
+	im->r = i->r;
+	im->nchans = 1;
+	im->chandesc = CRGBV;
+	im->chanlen = i->chanlen;
+
+	dx = i->r.max.x-i->r.min.x;
+	dy = i->r.max.y-i->r.min.y;
+	cmap = i->cmap;
+
+	if(inited == 0){
+		inited = 1;
+		for(j=0; j<CLAMPOFF; j++)
+			clamp[j] = 0;
+		for(j=0; j<256; j++)
+			clamp[CLAMPOFF+j] = (j>>4);
+		for(j=0; j<CLAMPOFF; j++)
+			clamp[CLAMPOFF+256+j] = (255>>4);
+	}
+
+	in = i->chans[0];
+	inp = in;
+	out = im->chans[0];
+	outp = out;
+
+	ered = malloc((dx+1)*sizeof(int));
+	egrn = malloc((dx+1)*sizeof(int));
+	eblu = malloc((dx+1)*sizeof(int));
+	if(ered==nil || egrn==nil || eblu==nil){
+		free(im->chans[0]);
+		free(im);
+		free(ered);
+		free(egrn);
+		free(eblu);
+		return _remaperror("remap: malloc failed: %r");
+	}
+	memset(ered, 0, (dx+1)*sizeof(int));
+	memset(egrn, 0, (dx+1)*sizeof(int));
+	memset(eblu, 0, (dx+1)*sizeof(int));
+
+	switch(i->chandesc){
+	default:
+		return _remaperror("remap: can't recognize channel type %d", i->chandesc);
+	case CRGB1:
+		if(cmap == nil)
+			return _remaperror("remap: image has no color map");
+		if(i->nchans != 1)
+			return _remaperror("remap: can't handle nchans %d", i->nchans);
+		for(j=1; j<=8; j++)
+			if(i->cmaplen == 3*(1<<j))
+				break;
+		if(j > 8)
+			return _remaperror("remap: can't do colormap size 3*%d", i->cmaplen/3);
+		if(i->cmaplen != 3*256){
+			/* to avoid a range check in inner loop below, make a full-size cmap */
+			memmove(cmap1, cmap, i->cmaplen);
+			cmap = cmap1;
+		}
+		if(errdiff == 0){
+			k = 0;
+			for(j=0; j<256; j++){
+				r = cmap[k]>>4;
+				g = cmap[k+1]>>4;
+				b = cmap[k+2]>>4;
+				k += 3;
+				map[j] = closestrgb[b+16*(g+16*r)];
+			}
+			for(j=0; j<i->chanlen; j++)
+				out[j] = map[in[j]];
+		}else{
+			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */
+			for(y=0; y<dy; y++){
+				er = 0;
+				eg = 0;
+				eb = 0;
+				rp = ered;
+				gp = egrn;
+				bp = eblu;
+				for(x=0; x<dx; x++){
+					cm = &cmap[3 * *inp++];
+					r = cm[0] +*rp;
+					g = cm[1] +*gp;
+					b = cm[2] +*bp;
+
+					/* sanity checks are new */
+					if(r >= 256+CLAMPOFF)
+						r = 0;
+					if(g >= 256+CLAMPOFF)
+						g = 0;
+					if(b >= 256+CLAMPOFF)
+						b = 0;
+					r1 = clamp[r+CLAMPOFF];
+					g1 = clamp[g+CLAMPOFF];
+					b1 = clamp[b+CLAMPOFF];
+					if(r1 >= 16 || g1 >= 16 || b1 >= 16)
+						col = 0;
+					else
+						col = closestrgb[b1+16*(g1+16*r1)];
+					*outp++ = col;
+
+					rgb = rgbmap[col];
+					r -= (rgb>>16) & 0xFF;
+					t = (3*r)>>4;
+					*rp++ = t+er;
+					*rp += t;
+					er = r-3*t;
+
+					g -= (rgb>>8) & 0xFF;
+					t = (3*g)>>4;
+					*gp++ = t+eg;
+					*gp += t;
+					eg = g-3*t;
+
+					b -= rgb & 0xFF;
+					t = (3*b)>>4;
+					*bp++ = t+eb;
+					*bp += t;
+					eb = b-3*t;
+				}
+			}
+		}
+		break;
+
+	case CYCbCr:
+		closest = closestycbcr;
+		map3 = ycbcrmap;
+		goto Threecolor;
+
+	case CRGB:
+		closest = closestrgb;
+		map3 = rgbmap;
+
+	Threecolor:
+		if(i->nchans != 3)
+			return _remaperror("remap: RGB image has %d channels", i->nchans);
+		rpic = i->chans[0];
+		gpic = i->chans[1];
+		bpic = i->chans[2];
+		if(errdiff == 0){
+			for(j=0; j<i->chanlen; j++){
+				r = rpic[j]>>4;
+				g = gpic[j]>>4;
+				b = bpic[j]>>4;
+				out[j] = closest[b+16*(g+16*r)];
+			}
+		}else{
+			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */
+			for(y=0; y<dy; y++){
+				er = 0;
+				eg = 0;
+				eb = 0;
+				rp = ered;
+				gp = egrn;
+				bp = eblu;
+				for(x=0; x<dx; x++){
+					r = *rpic++ + *rp;
+					g = *gpic++ + *gp;
+					b = *bpic++ + *bp;
+					/*
+					 * Errors can be uncorrectable if converting from YCbCr,
+					 * since we can't guarantee that an extremal value of one of
+					 * the components selects a color with an extremal value.
+					 * If we don't, the errors accumulate without bound.  This
+					 * doesn't happen in RGB because the closest table can guarantee
+					 * a color on the edge of the gamut, producing a zero error in
+					 * that component.  For the rotation YCbCr space, there may be
+					 * no color that can guarantee zero error at the edge.
+					 * Therefore we must clamp explicitly rather than by assuming
+					 * an upper error bound of CLAMPOFF.  The performance difference
+					 * is miniscule anyway.
+					 */
+					if(r < 0)
+						r = 0;
+					else if(r > 255)
+						r = 255;
+					if(g < 0)
+						g = 0;
+					else if(g > 255)
+						g = 255;
+					if(b < 0)
+						b = 0;
+					else if(b > 255)
+						b = 255;
+					r1 = r>>4;
+					g1 = g>>4;
+					b1 = b>>4;
+					col = closest[b1+16*(g1+16*r1)];
+					*outp++ = col;
+
+					rgb = map3[col];
+					r -= (rgb>>16) & 0xFF;
+					t = (3*r)>>4;
+					*rp++ = t+er;
+					*rp += t;
+					er = r-3*t;
+
+					g -= (rgb>>8) & 0xFF;
+					t = (3*g)>>4;
+					*gp++ = t+eg;
+					*gp += t;
+					eg = g-3*t;
+
+					b -= rgb & 0xFF;
+					t = (3*b)>>4;
+					*bp++ = t+eb;
+					*bp += t;
+					eb = b-3*t;
+				}
+			}
+		}
+		break;
+
+	case CY:
+		if(i->nchans != 1)
+			return _remaperror("remap: Y image has %d chans", i->nchans);
+		rpic = i->chans[0];
+		if(errdiff == 0){
+			for(j=0; j<i->chanlen; j++){
+				r = rpic[j]>>4;
+				*outp++ = closestrgb[r+16*(r+16*r)];
+			}
+		}else{
+			/* modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 */
+			for(y=0; y<dy; y++){
+				er = 0;
+				rp = ered;
+				for(x=0; x<dx; x++){
+					r = *inp++ + *rp;
+					r1 = clamp[r+CLAMPOFF];
+					col = closestrgb[r1+16*(r1+16*r1)];
+					*outp++ = col;
+
+					rgb = rgbmap[col];
+					r -= (rgb>>16) & 0xFF;
+					t = (3*r)>>4;
+					*rp++ = t+er;
+					*rp += t;
+					er = r-3*t;
+				}
+			}
+		}
+		break;
+	}
+	free(ered);
+	free(egrn);
+	free(eblu);
+	return im;
+}
diff --git a/src/cmd/jpg/totruecolor.c b/src/cmd/jpg/totruecolor.c
new file mode 100644
index 0000000..0da5d9a
--- /dev/null
+++ b/src/cmd/jpg/totruecolor.c
@@ -0,0 +1,163 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+
+enum {
+	c1 = 2871,	/* 1.402 * 2048 */
+	c2 = 705,		/* 0.34414 * 2048 */
+	c3 = 1463,	/* 0.71414 * 2048 */
+	c4 = 3629,	/* 1.772 * 2048 */
+};
+
+Rawimage*
+totruecolor(Rawimage *i, int chandesc)
+{
+	int j, k;
+	Rawimage *im;
+	char err[ERRMAX];
+	uchar *rp, *gp, *bp, *cmap, *inp, *outp, cmap1[3*256];
+	int r, g, b, Y, Cr, Cb;
+
+	if(chandesc!=CY && chandesc!=CRGB24)
+		return _remaperror("remap: can't convert to chandesc %d", chandesc);
+
+	err[0] = '\0';
+	errstr(err, sizeof err);	/* throw it away */
+	im = malloc(sizeof(Rawimage));
+	if(im == nil)
+		return nil;
+	memset(im, 0, sizeof(Rawimage));
+	if(chandesc == CY)
+		im->chanlen = i->chanlen;
+	else
+		im->chanlen = 3*i->chanlen;
+	im->chandesc = chandesc;
+	im->chans[0] = malloc(im->chanlen);
+	if(im->chans[0] == nil){
+		free(im);
+		return nil;
+	}
+	im->r = i->r;
+	im->nchans = 1;
+
+	cmap = i->cmap;
+
+	outp = im->chans[0];
+
+	switch(i->chandesc){
+	default:
+		return _remaperror("remap: can't recognize channel type %d", i->chandesc);
+	case CY:
+		if(i->nchans != 1)
+			return _remaperror("remap: Y image has %d chans", i->nchans);
+		if(chandesc == CY){
+			memmove(im->chans[0], i->chans[0], i->chanlen);
+			break;
+		}
+		/* convert to three color */
+		inp = i->chans[0];
+		for(j=0; j<i->chanlen; j++){
+			k = *inp++;
+			*outp++ = k;
+			*outp++ = k;
+			*outp++ = k;
+		}
+		break;
+
+	case CRGB1:
+		if(cmap == nil)
+			return _remaperror("remap: image has no color map");
+		if(i->nchans != 1)
+			return _remaperror("remap: can't handle nchans %d", i->nchans);
+		for(j=1; j<=8; j++)
+			if(i->cmaplen == 3*(1<<j))
+				break;
+		if(j > 8)
+			return _remaperror("remap: can't do colormap size 3*%d", i->cmaplen/3);
+		if(i->cmaplen != 3*256){
+			/* to avoid a range check in loop below, make a full-size cmap */
+			memmove(cmap1, cmap, i->cmaplen);
+			cmap = cmap1;
+		}
+		inp = i->chans[0];
+		if(chandesc == CY){
+			for(j=0; j<i->chanlen; j++){
+				k = *inp++;
+				r = cmap[3*k+2];
+				g = cmap[3*k+1];
+				b = cmap[3*k+0];
+				r = (2125*r + 7154*g + 721*b)/10000;	/* Poynton page 84 */
+				*outp++ = r;
+			}
+		}else{
+			for(j=0; j<i->chanlen; j++){
+				k = *inp++;
+				*outp++ = cmap[3*k+2];
+				*outp++ = cmap[3*k+1];
+				*outp++ = cmap[3*k+0];
+			}
+		}
+		break;
+
+	case CRGB:
+		if(i->nchans != 3)
+			return _remaperror("remap: can't handle nchans %d", i->nchans);
+		rp = i->chans[0];
+		gp = i->chans[1];
+		bp = i->chans[2];
+		if(chandesc == CY){
+			for(j=0; j<i->chanlen; j++){
+				r = *bp++;
+				g = *gp++;
+				b = *rp++;
+				r = (2125*r + 7154*g + 721*b)/10000;	/* Poynton page 84 */
+				*outp++ = r;
+			}
+		}else
+			for(j=0; j<i->chanlen; j++){
+				*outp++ = *bp++;
+				*outp++ = *gp++;
+				*outp++ = *rp++;
+			}
+		break;
+
+	case CYCbCr:
+		if(i->nchans != 3)
+			return _remaperror("remap: can't handle nchans %d", i->nchans);
+		rp = i->chans[0];
+		gp = i->chans[1];
+		bp = i->chans[2];
+		for(j=0; j<i->chanlen; j++){
+			Y = *rp++ << 11;
+			Cb = *gp++ - 128;
+			Cr = *bp++ - 128;
+			r = (Y+c1*Cr) >> 11;
+			g = (Y-c2*Cb-c3*Cr) >> 11;
+			b = (Y+c4*Cb) >> 11;
+			if(r < 0)
+				r = 0;
+			if(r > 255)
+				r = 255;
+			if(g < 0)
+				g = 0;
+			if(g > 255)
+				g = 255;
+			if(b < 0)
+				b = 0;
+			if(b > 255)
+				b = 255;
+			if(chandesc == CY){
+				r = (2125*r + 7154*g + 721*b)/10000;
+				*outp++ = r;
+			}else{
+				*outp++ = b;
+				*outp++ = g;
+				*outp++ = r;
+			}
+		}
+		break;
+	}
+	return im;
+}
diff --git a/src/cmd/jpg/writegif.c b/src/cmd/jpg/writegif.c
new file mode 100644
index 0000000..16402ef
--- /dev/null
+++ b/src/cmd/jpg/writegif.c
@@ -0,0 +1,568 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+#include "imagefile.h"
+
+enum
+{
+	Nhash	= 4001,
+	Nbuf		= 300,
+};
+
+typedef struct Entry Entry;
+typedef struct IO IO;
+
+
+struct Entry
+{
+	int		index;
+	int		prefix;
+	int		exten;
+	Entry	*next;
+};
+
+struct IO
+{
+	Biobuf	*fd;
+	uchar	buf[Nbuf];
+	int		i;
+	int		nbits;	/* bits in right side of shift register */
+	int		sreg;		/* shift register */
+};
+
+static Rectangle	mainrect;
+static Entry	tbl[4096];
+static uchar	*colormap[5];	/* one for each ldepth: GREY1 GREY2 GREY4 CMAP8=rgbv plus GREY8 */
+#define	GREYMAP	4
+static int		colormapsize[] = { 2, 4, 16, 256, 256 };	/* 2 for zero is an odd property of GIF */
+
+static void		writeheader(Biobuf*, Rectangle, int, ulong, int);
+static void		writedescriptor(Biobuf*, Rectangle);
+static char*	writedata(Biobuf*, Image*, Memimage*);
+static void		writecomment(Biobuf *fd, char*);
+static void		writegraphiccontrol(Biobuf *fd, int, int);
+static void*	gifmalloc(ulong);
+static void		encode(Biobuf*, Rectangle, int, uchar*, uint);
+
+static
+char*
+startgif0(Biobuf *fd, ulong chan, Rectangle r, int depth, int loopcount)
+{
+	int i;
+
+	for(i=0; i<nelem(tbl); i++)
+		tbl[i] = (Entry){i, -1, i, nil};
+
+	switch(chan){
+	case GREY1:
+	case GREY2:
+	case GREY4:
+	case CMAP8:
+	case GREY8:
+		break;
+	default:
+		return "WriteGIF: can't handle channel type";
+	}
+
+	mainrect = r;
+	writeheader(fd, r, depth, chan, loopcount);
+	return nil;
+}
+
+char*
+startgif(Biobuf *fd, Image *image, int loopcount)
+{
+	return startgif0(fd, image->chan, image->r, image->depth, loopcount);
+}
+
+char*
+memstartgif(Biobuf *fd, Memimage *memimage, int loopcount)
+{
+	return startgif0(fd, memimage->chan, memimage->r, memimage->depth, loopcount);
+}
+
+static
+char*
+writegif0(Biobuf *fd, Image *image, Memimage *memimage, ulong chan, Rectangle r, char *comment, int dt, int trans)
+{
+	char *err;
+
+	switch(chan){
+	case GREY1:
+	case GREY2:
+	case GREY4:
+	case CMAP8:
+	case GREY8:
+		break;
+	default:
+		return "WriteGIF: can't handle channel type";
+	}
+
+	writecomment(fd, comment);
+	writegraphiccontrol(fd, dt, trans);
+	writedescriptor(fd, r);
+
+	err = writedata(fd, image, memimage);
+	if(err != nil)
+		return err;
+
+	return nil;
+}
+
+char*
+writegif(Biobuf *fd, Image *image, char *comment, int dt, int trans)
+{
+	return writegif0(fd, image, nil, image->chan, image->r, comment, dt, trans);
+}
+
+char*
+memwritegif(Biobuf *fd, Memimage *memimage, char *comment, int dt, int trans)
+{
+	return writegif0(fd, nil, memimage, memimage->chan, memimage->r, comment, dt, trans);
+}
+
+/*
+ * Write little-endian 16-bit integer
+ */
+static
+void
+put2(Biobuf *fd, int i)
+{
+	Bputc(fd, i);
+	Bputc(fd, i>>8);
+}
+
+/*
+ * Get color map for all ldepths, in format suitable for writing out
+ */
+static
+void
+getcolormap(void)
+{
+	int i, col;
+	ulong rgb;
+	uchar *c;
+
+	if(colormap[0] != nil)
+		return;
+	for(i=0; i<nelem(colormap); i++)
+		colormap[i] = gifmalloc(3* colormapsize[i]);
+	c = colormap[GREYMAP];	/* GREY8 */
+	for(i=0; i<256; i++){
+		c[3*i+0] = i;	/* red */
+		c[3*i+1] = i;	/* green */
+		c[3*i+2] = i;	/* blue */
+	}
+	c = colormap[3];	/* RGBV */
+	for(i=0; i<256; i++){
+		rgb = cmap2rgb(i);
+		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
+		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
+		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
+	}
+	c = colormap[2];	/* GREY4 */
+	for(i=0; i<16; i++){
+		col = (i<<4)|i;
+		rgb = cmap2rgb(col);
+		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
+		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
+		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
+	}
+	c = colormap[1];	/* GREY2 */
+	for(i=0; i<4; i++){
+		col = (i<<6)|(i<<4)|(i<<2)|i;
+		rgb = cmap2rgb(col);
+		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
+		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
+		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
+	}
+	c = colormap[0];	/* GREY1 */
+	for(i=0; i<2; i++){
+		if(i == 0)
+			col = 0;
+		else
+			col = 0xFF;
+		rgb = cmap2rgb(col);
+		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
+		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
+		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
+	}
+}
+
+/*
+ * Write header, logical screen descriptor, and color map
+ */
+static
+void
+writeheader(Biobuf *fd, Rectangle r, int depth, ulong chan, int loopcount)
+{
+	/* Header */
+	Bprint(fd, "%s", "GIF89a");
+
+	/*  Logical Screen Descriptor */
+	put2(fd, Dx(r));
+	put2(fd, Dy(r));
+
+	/* Color table present, 4 bits per color (for RGBV best case), size of color map */
+	Bputc(fd, (1<<7)|(3<<4)|(depth-1));	/* not right for GREY8, but GIF doesn't let us specify enough bits */
+	Bputc(fd, 0xFF);	/* white background (doesn't matter anyway) */
+	Bputc(fd, 0);	/* pixel aspect ratio - unused */
+
+	/* Global Color Table */
+	getcolormap();
+	if(chan == GREY8)
+		depth = GREYMAP;
+	else
+		depth = drawlog2[depth];
+	Bwrite(fd, colormap[depth], 3*colormapsize[depth]);
+
+	if(loopcount >= 0){	/* hard-to-discover way to force cycled animation */
+		/* Application Extension with (1 loopcountlo loopcounthi) as data */
+		Bputc(fd, 0x21);
+		Bputc(fd, 0xFF);
+		Bputc(fd, 11);
+		Bwrite(fd, "NETSCAPE2.0", 11);
+		Bputc(fd, 3);
+		Bputc(fd, 1);
+		put2(fd, loopcount);
+		Bputc(fd, 0);
+	}
+}
+
+/*
+ * Write optional comment block
+ */
+static
+void
+writecomment(Biobuf *fd, char *comment)
+{
+	int n;
+
+	if(comment==nil || comment[0]=='\0')
+		return;
+
+	/* Comment extension and label */
+	Bputc(fd, 0x21);
+	Bputc(fd, 0xFE);
+
+	/* Comment data */
+	n = strlen(comment);
+	if(n > 255)
+		n = 255;
+	Bputc(fd, n);
+	Bwrite(fd, comment, n);
+
+	/* Block terminator */
+	Bputc(fd, 0x00);
+}
+
+/*
+ * Write optional control block (sets Delay Time)
+ */
+static
+void
+writegraphiccontrol(Biobuf *fd, int dt, int trans)
+{
+	if(dt < 0 && trans < 0)
+		return;
+
+	/* Comment extension and label and block size*/
+	Bputc(fd, 0x21);
+	Bputc(fd, 0xF9);
+	Bputc(fd, 0x04);
+
+	/* Disposal method and other flags (none) */
+	if(trans >= 0)
+		Bputc(fd, 0x01);
+	else
+		Bputc(fd, 0x00);
+	
+	/* Delay time, in centisec (argument is millisec for sanity) */
+	if(dt < 0)
+		dt = 0;
+	else if(dt < 10)
+		dt = 1;
+	else
+		dt = (dt+5)/10;
+	put2(fd, dt);
+
+	/* Transparency index */
+	if(trans < 0)
+		trans = 0;
+	Bputc(fd, trans);
+
+	/* Block terminator */
+	Bputc(fd, 0x00);
+}
+
+/*
+ * Write image descriptor
+ */
+static
+void
+writedescriptor(Biobuf *fd, Rectangle r)
+{
+	/* Image Separator */
+	Bputc(fd, 0x2C);
+
+	/* Left, top, width, height */
+	put2(fd, r.min.x-mainrect.min.x);
+	put2(fd, r.min.y-mainrect.min.y);
+	put2(fd, Dx(r));
+	put2(fd, Dy(r));
+	/* no special processing */
+	Bputc(fd, 0);
+}
+
+/*
+ * Write data
+ */
+static
+char*
+writedata(Biobuf *fd, Image *image, Memimage *memimage)
+{
+	char *err;
+	uchar *data;
+	int ndata, depth;
+	Rectangle r;
+
+	if(memimage != nil){
+		r = memimage->r;
+		depth = memimage->depth;
+	}else{
+		r = image->r;
+		depth = image->depth;
+	}
+
+	/* LZW Minimum code size */
+	if(depth == 1)
+		Bputc(fd, 2);
+	else
+		Bputc(fd, depth);
+
+	/* 
+	 * Read image data into memory
+	 * potentially one extra byte on each end of each scan line
+	 */
+	ndata = Dy(r)*(2+(Dx(r)>>(3-drawlog2[depth])));
+	data = gifmalloc(ndata);
+	if(memimage != nil)
+		ndata = unloadmemimage(memimage, r, data, ndata);
+	else
+		ndata = unloadimage(image, r, data, ndata);
+	if(ndata < 0){
+		err = gifmalloc(ERRMAX);
+		snprint(err, ERRMAX, "WriteGIF: %r");
+		free(data);
+		return err;
+	}
+
+	/* Encode and emit the data */
+	encode(fd, r, depth, data, ndata);
+	free(data);
+
+	/*  Block Terminator */
+	Bputc(fd, 0);
+	return nil;
+}
+
+/*
+ * Write trailer
+ */
+void
+endgif(Biobuf *fd)
+{
+	Bputc(fd, 0x3B);
+	Bflush(fd);
+}
+
+void
+memendgif(Biobuf *fd)
+{
+	endgif(fd);
+}
+
+/*
+ * Put n bits of c into output at io.buf[i];
+ */
+static
+void
+output(IO *io, int c, int n)
+{
+	if(c < 0){
+		if(io->nbits != 0)
+			io->buf[io->i++] = io->sreg;
+		Bputc(io->fd, io->i);
+		Bwrite(io->fd, io->buf, io->i);
+		io->nbits = 0;
+		return;
+	}
+
+	if(io->nbits+n >= 31){
+		fprint(2, "panic: WriteGIF sr overflow\n");
+		exits("WriteGIF panic");
+	}
+	io->sreg |= c<<io->nbits;
+	io->nbits += n;
+
+	while(io->nbits >= 8){
+		io->buf[io->i++] = io->sreg;
+		io->sreg >>= 8;
+		io->nbits -= 8;
+	}
+
+	if(io->i >= 255){
+		Bputc(io->fd, 255);
+		Bwrite(io->fd, io->buf, 255);
+		memmove(io->buf, io->buf+255, io->i-255);
+		io->i -= 255;
+	}
+}
+
+/*
+ * LZW encoder
+ */
+static
+void
+encode(Biobuf *fd, Rectangle r, int depth, uchar *data, uint ndata)
+{
+	int i, c, h, csize, prefix, first, sreg, nbits, bitsperpixel;
+	int CTM, EOD, codesize, ld0, datai, x, ld, pm;
+	int nentry, maxentry, early;
+	Entry *e, *oe;
+	IO *io;
+	Entry **hash;
+
+	first = 1;
+	ld = drawlog2[depth];
+	/* ldepth 0 must generate codesize 2 with values 0 and 1 (see the spec.) */
+	ld0 = ld;
+	if(ld0 == 0)
+		ld0 = 1;
+	codesize = (1<<ld0);
+	CTM = 1<<codesize;
+	EOD = CTM+1;
+
+	io = gifmalloc(sizeof(IO));
+	io->fd = fd;
+	sreg = 0;
+	nbits = 0;
+	bitsperpixel = 1<<ld;
+	pm = (1<<bitsperpixel)-1;
+
+	datai = 0;
+	x = r.min.x;
+	hash = gifmalloc(Nhash*sizeof(Entry*));
+
+Init:
+	memset(hash, 0, Nhash*sizeof(Entry*));
+	csize = codesize+1;
+	nentry = EOD+1;
+	maxentry = (1<<csize);
+	for(i = 0; i<nentry; i++){
+		e = &tbl[i];
+		h = (e->prefix<<24) | (e->exten<<8);
+		h %= Nhash;
+		if(h < 0)
+			h += Nhash;
+		e->next = hash[h];
+		hash[h] = e;
+	}
+	prefix = -1;
+	if(first)
+		output(io, CTM, csize);
+	first = 0;
+
+	/*
+	 * Scan over pixels.  Because of partially filled bytes on ends of scan lines,
+	 * which must be ignored in the data stream passed to GIF, this is more
+	 * complex than we'd like.
+	 */
+Next:
+	for(;;){
+		if(ld != 3){
+			/* beginning of scan line is difficult; prime the shift register */
+			if(x == r.min.x){
+				if(datai == ndata)
+					break;
+				sreg = data[datai++];
+				nbits = 8-((x&(7>>ld))<<ld);
+			}
+			x++;
+			if(x == r.max.x)
+				x = r.min.x;
+		}
+		if(nbits == 0){
+			if(datai == ndata)
+				break;
+			sreg = data[datai++];
+			nbits = 8;
+		}
+		nbits -= bitsperpixel;
+		c = sreg>>nbits & pm;
+		h = prefix<<24 | c<<8;
+		h %= Nhash;
+		if(h < 0)
+			h += Nhash;
+		oe = nil;
+		for(e = hash[h]; e!=nil; e=e->next){
+			if(e->prefix == prefix && e->exten == c){
+				if(oe != nil){
+					oe->next = e->next;
+					e->next = hash[h];
+					hash[h] = e;
+				}
+				prefix = e->index;
+				goto Next;
+			}
+			oe = e;
+		}
+
+		output(io, prefix, csize);
+		early = 0; /* peculiar tiff feature here for reference */
+		if(nentry == maxentry-early){
+			if(csize == 12){
+				nbits += bitsperpixel;	/* unget pixel */
+				x--;
+				if(ld != 3 && x == r.min.x)
+					datai--;
+				output(io, CTM, csize);
+				goto Init;
+			}
+			csize++;
+			maxentry = (1<<csize);
+		}
+
+		e = &tbl[nentry];
+		e->prefix = prefix;
+		e->exten = c;
+		e->next = hash[h];
+		hash[h] = e;
+
+		prefix = c;
+		nentry++;
+	}
+
+	output(io, prefix, csize);
+	output(io, EOD, csize);
+	output(io, -1, csize);
+	free(io);
+	free(hash);
+}
+
+static
+void*
+gifmalloc(ulong sz)
+{
+	void *v;
+	v = malloc(sz);
+	if(v == nil) {
+		fprint(2, "WriteGIF: out of memory allocating %ld\n", sz);
+abort();
+		exits("mem");
+	}
+	memset(v, 0, sz);
+	return v;
+}
diff --git a/src/cmd/jpg/writepng.c b/src/cmd/jpg/writepng.c
new file mode 100644
index 0000000..44bfe10
--- /dev/null
+++ b/src/cmd/jpg/writepng.c
@@ -0,0 +1,220 @@
+// based on PNG 1.2 specification, July 1999  (see also rfc2083)
+// Alpha is not supported yet because of lack of industry acceptance and
+// because Plan9 Image uses premultiplied alpha, so png can't be lossless.
+// Only 24bit color supported, because 8bit may as well use GIF.
+
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <ctype.h>
+#include <bio.h>
+#include <flate.h>
+#include "imagefile.h"
+
+enum{	IDATSIZE = 	20000,
+	FilterNone =	0,
+};
+
+typedef struct ZlibR{
+	uchar *data;
+	int width;
+	int nrow, ncol;
+	int row, col;	// next pixel to send
+} ZlibR;
+
+typedef struct ZlibW{
+	Biobuf *bo;
+	uchar *buf;
+	uchar *b;	// next place to write
+	uchar *e;	// past end of buf
+} ZlibW;
+
+static ulong *crctab;
+static uchar PNGmagic[] = {137,80,78,71,13,10,26,10};
+
+static void
+put4(uchar *a, ulong v)
+{
+	a[0] = v>>24;
+	a[1] = v>>16;
+	a[2] = v>>8;
+	a[3] = v;
+}
+
+static void
+chunk(Biobuf *bo, char *type, uchar *d, int n)
+{
+	uchar buf[4];
+	ulong crc = 0;
+
+	if(strlen(type) != 4)
+		return;
+	put4(buf, n);
+	Bwrite(bo, buf, 4);
+	Bwrite(bo, type, 4);
+	Bwrite(bo, d, n);
+	crc = blockcrc(crctab, crc, type, 4);
+	crc = blockcrc(crctab, crc, d, n);
+	put4(buf, crc);
+	Bwrite(bo, buf, 4);
+}
+
+static int
+zread(void *va, void *buf, int n)
+{
+	ZlibR *z = va;
+	int nrow = z->nrow;
+	int ncol = z->ncol;
+	uchar *b = buf, *e = b+n, *img;
+	int i, pixels;  // number of pixels in row that can be sent now
+
+	while(b+3 <= e){ // loop over image rows
+		if(z->row >= nrow)
+			break;
+		if(z->col==0)
+			*b++ = FilterNone;
+		pixels = (e-b)/3;
+		if(pixels > ncol - z->col)
+			pixels = ncol - z->col;
+		img = z->data + z->width * z->row + 3 * z->col;
+
+		// Plan 9 image format is BGR?!!!
+		// memmove(b, img, 3*pixels);
+		// b += 3*pixels;
+		for(i=0; i<pixels; i++, img += 3){
+			*b++ = img[2];
+			*b++ = img[1];
+			*b++ = img[0];
+		}
+
+		z->col += pixels;
+		if(z->col >= ncol){
+			z->col = 0;
+			z->row++;
+		}
+	}
+	return b - (uchar*)buf;
+}
+
+static void
+IDAT(ZlibW *z)
+{
+	chunk(z->bo, "IDAT", z->buf, z->b - z->buf);
+	z->b = z->buf;
+}
+
+static int
+zwrite(void *va, void *buf, int n)
+{
+	ZlibW *z = va;
+	uchar *b = buf, *e = b+n;
+	int m;
+
+	while(b < e){ // loop over IDAT chunks
+		m = z->e - z->b;
+		if(m > e - b)
+			m = e - b;
+		memmove(z->b, b, m);
+		z->b += m;
+		b += m;
+		if(z->b >= z->e)
+			IDAT(z);
+	}
+	return n;
+}
+
+static Memimage*
+memRGB(Memimage *i)
+{
+	Memimage *ni;
+
+	if(i->chan == RGB24)
+		return i;
+
+	ni = allocmemimage(i->r, RGB24);
+	if(ni == nil)
+		return ni;
+	memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S);
+	return ni;
+}
+
+char*
+memwritepng(Biobuf *bo, Memimage *r, ImageInfo *II)
+{
+	uchar buf[200], *h;
+	ulong vgamma;
+	int err, n;
+	ZlibR zr;
+	ZlibW zw;
+	int nrow = r->r.max.y - r->r.min.y;
+	int ncol = r->r.max.x - r->r.min.x;
+	Tm *tm;
+	Memimage *rgb;
+
+	rgb = memRGB(r);
+	if(rgb == nil)
+		return "allocmemimage nil";
+	crctab = mkcrctab(0xedb88320);
+	if(crctab == nil)
+		sysfatal("mkcrctab error");
+	deflateinit();
+
+	Bwrite(bo, PNGmagic, sizeof PNGmagic);
+	// IHDR chunk
+	h = buf;
+	put4(h, ncol); h += 4;
+	put4(h, nrow); h += 4;
+	*h++ = 8; // bit depth = 24 bit per pixel
+	*h++ = 2; // color type = rgb
+	*h++ = 0; // compression method = deflate
+	*h++ = 0; // filter method
+	*h++ = 0; // interlace method = no interlace
+	chunk(bo, "IHDR", buf, h-buf);
+
+	tm = gmtime(time(0));
+	h = buf;
+	*h++ = (tm->year + 1900)>>8;
+	*h++ = (tm->year + 1900)&0xff;
+	*h++ = tm->mon + 1;
+	*h++ = tm->mday;
+	*h++ = tm->hour;
+	*h++ = tm->min;
+	*h++ = tm->sec;
+	chunk(bo, "tIME", buf, h-buf);
+	
+	if(II->fields_set & II_GAMMA){
+		vgamma = II->gamma*100000;
+		put4(buf, vgamma);
+		chunk(bo, "gAMA", buf, 4);
+	}
+
+	if(II->fields_set & II_COMMENT){
+		strncpy((char*)buf, "Comment", sizeof buf);
+		n = strlen((char*)buf)+1; // leave null between Comment and text
+		strncpy((char*)(buf+n), II->comment, sizeof buf - n);
+		chunk(bo, "tEXt", buf, n+strlen((char*)buf+n));
+	}
+
+	// image chunks
+	zr.nrow = nrow;
+	zr.ncol = ncol;
+	zr.width = rgb->width * sizeof(ulong);
+	zr.data = rgb->data->bdata;
+	zr.row = zr.col = 0;
+	zw.bo = bo;
+	zw.buf = malloc(IDATSIZE);
+	zw.b = zw.buf;
+	zw.e = zw.b + IDATSIZE;
+	err = deflatezlib(&zw, zwrite, &zr, zread, 6, 0);
+	if(zw.b > zw.buf)
+		IDAT(&zw);
+	free(zw.buf);
+	if(err)
+		sysfatal("deflatezlib %s\n", flateerr(err));
+	chunk(bo, "IEND", nil, 0);
+
+	if(r != rgb)
+		freememimage(rgb);
+	return nil;
+}
diff --git a/src/cmd/jpg/writeppm.c b/src/cmd/jpg/writeppm.c
new file mode 100644
index 0000000..c837865
--- /dev/null
+++ b/src/cmd/jpg/writeppm.c
@@ -0,0 +1,164 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+#include "imagefile.h"
+
+#define	MAXLINE	70
+
+/*
+ * Write data
+ */
+static
+char*
+writedata(Biobuf *fd, Image *image, Memimage *memimage)
+{
+	char *err;
+	uchar *data;
+	int i, x, y, ndata, depth, col, pix, xmask, pmask;
+	ulong chan;
+	Rectangle r;
+
+	if(memimage != nil){
+		r = memimage->r;
+		depth = memimage->depth;
+		chan = memimage->chan;
+	}else{
+		r = image->r;
+		depth = image->depth;
+		chan = image->chan;
+	}
+
+	/* 
+	 * Read image data into memory
+	 * potentially one extra byte on each end of each scan line
+	 */
+	ndata = Dy(r)*(2+Dx(r)*depth/8);
+	data = malloc(ndata);
+	if(data == nil)
+		return "WritePPM: malloc failed";
+	if(memimage != nil)
+		ndata = unloadmemimage(memimage, r, data, ndata);
+	else
+		ndata = unloadimage(image, r, data, ndata);
+	if(ndata < 0){
+		err = malloc(ERRMAX);
+		if(err == nil)
+			return "WritePPM: malloc failed";
+		snprint(err, ERRMAX, "WriteGIF: %r");
+		free(data);
+		return err;
+	}
+
+	/* Encode and emit the data */
+	col = 0;
+	switch(chan){
+	case GREY1:
+	case GREY2:
+	case GREY4:
+		pmask = (1<<depth)-1;
+		xmask = 7>>drawlog2[depth];
+		for(y=r.min.y; y<r.max.y; y++){
+			i = (y-r.min.y)*bytesperline(r, depth);
+			for(x=r.min.x; x<r.max.x; x++){
+				pix = (data[i]>>depth*((xmask-x)&xmask))&pmask;
+				if(((x+1)&xmask) == 0)
+					i++;
+				col += Bprint(fd, "%d ", pix);
+				if(col >= MAXLINE-(2+1)){
+					Bprint(fd, "\n");
+					col = 0;
+				}else
+					col += Bprint(fd, " ");
+			}
+		}
+		break;
+	case	GREY8:
+		for(i=0; i<ndata; i++){
+			col += Bprint(fd, "%d ", data[i]);
+			if(col >= MAXLINE-(4+1)){
+				Bprint(fd, "\n");
+				col = 0;
+			}else
+				col += Bprint(fd, " ");
+		}
+		break;
+	case RGB24:
+		for(i=0; i<ndata; i+=3){
+			col += Bprint(fd, "%d %d %d", data[i+2], data[i+1], data[i]);
+			if(col >= MAXLINE-(4+4+4+1)){
+				Bprint(fd, "\n");
+				col = 0;
+			}else
+				col += Bprint(fd, " ");
+		}
+		break;
+	default:
+		return "WritePPM: can't handle channel type";
+	}
+
+	return nil;
+}
+
+static
+char*
+writeppm0(Biobuf *fd, Image *image, Memimage *memimage, Rectangle r, int chan, char *comment)
+{
+	char *err;
+
+	switch(chan){
+	case GREY1:
+		Bprint(fd, "P1\n");
+		break;
+	case GREY2:
+	case GREY4:
+	case	GREY8:
+		Bprint(fd, "P2\n");
+		break;
+	case RGB24:
+		Bprint(fd, "P3\n");
+		break;
+	default:
+		return "WritePPM: can't handle channel type";
+	}
+
+	if(comment!=nil && comment[0]!='\0'){
+		Bprint(fd, "# %s", comment);
+		if(comment[strlen(comment)-1] != '\n')
+			Bprint(fd, "\n");
+	}
+	Bprint(fd, "%d %d\n", Dx(r), Dy(r));
+
+	/* maximum pixel value */
+	switch(chan){
+	case GREY2:
+		Bprint(fd, "%d\n", 3);
+		break;
+	case GREY4:
+		Bprint(fd, "%d\n", 15);
+		break;
+	case	GREY8:
+	case RGB24:
+		Bprint(fd, "%d\n", 255);
+		break;
+	}
+
+	err = writedata(fd, image, memimage);
+
+	Bprint(fd, "\n");
+	Bflush(fd);
+	return err;
+}
+
+char*
+writeppm(Biobuf *fd, Image *image, char *comment)
+{
+	return writeppm0(fd, image, nil, image->r, image->chan, comment);
+}
+
+char*
+memwriteppm(Biobuf *fd, Memimage *memimage, char *comment)
+{
+	return writeppm0(fd, nil, memimage, memimage->r, memimage->chan, comment);
+}
diff --git a/src/cmd/jpg/writerawimage.c b/src/cmd/jpg/writerawimage.c
new file mode 100644
index 0000000..26e0cd1
--- /dev/null
+++ b/src/cmd/jpg/writerawimage.c
@@ -0,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "imagefile.h"
+
+/*
+ * Hacked version for writing from Rawimage to file.
+ * Assumes 8 bits per component.
+ */
+
+#define	HSHIFT	3	/* HSHIFT==5 runs slightly faster, but hash table is 64x bigger */
+#define	NHASH	(1<<(HSHIFT*NMATCH))
+#define	HMASK	(NHASH-1)
+#define	hupdate(h, c)	((((h)<<HSHIFT)^(c))&HMASK)
+typedef struct Hlist Hlist;
+struct Hlist{
+	uchar *s;
+	Hlist *next, *prev;
+};
+
+int
+writerawimage(int fd, Rawimage *i)
+{
+	uchar *outbuf, *outp, *eout;		/* encoded data, pointer, end */
+	uchar *loutp;				/* start of encoded line */
+	Hlist *hash;				/* heads of hash chains of past strings */
+	Hlist *chain, *hp;			/* hash chain members, pointer */
+	Hlist *cp;				/* next Hlist to fall out of window */
+	int h;					/* hash value */
+	uchar *line, *eline;			/* input line, end pointer */
+	uchar *data, *edata;			/* input buffer, end pointer */
+	ulong n;				/* length of input buffer */
+	int bpl;				/* input line length */
+	int offs, runlen;			/* offset, length of consumed data */
+	uchar dumpbuf[NDUMP];			/* dump accumulator */
+	int ndump;				/* length of dump accumulator */
+	int ncblock;				/* size of buffer */
+	Rectangle r;
+	uchar *p, *q, *s, *es, *t;
+	char hdr[11+5*12+1], buf[16];
+	ulong desc;
+
+	r = i->r;
+	switch(i->chandesc){
+	default:
+		werrstr("can't handle chandesc %d", i->chandesc);
+		return -1;
+	case CY:
+		bpl = Dx(r);
+		desc = GREY8;
+		break;
+	case CYA16:
+		bpl = 2*Dx(r);
+		desc = CHAN2(CGrey, 8, CAlpha, 8);
+		break;
+	case CRGBV:
+		bpl = Dx(r);
+		desc = CMAP8;
+		break;
+	case CRGBVA16:
+		bpl = 2*Dx(r);
+		desc = CHAN2(CMap, 8, CAlpha, 8);
+		break;
+	case CRGB24:
+		bpl = 3*Dx(r);
+		desc = RGB24;
+		break;
+	case CRGBA32:
+		bpl = 4*Dx(r);
+		desc = RGBA32;
+		break;
+	}
+	ncblock = _compblocksize(r, bpl/Dx(r));
+	outbuf = malloc(ncblock);
+	hash = malloc(NHASH*sizeof(Hlist));
+	chain = malloc(NMEM*sizeof(Hlist));
+	if(outbuf == 0 || hash == 0 || chain == 0){
+	ErrOut:
+		free(outbuf);
+		free(hash);
+		free(chain);
+		return -1;
+	}
+	n = Dy(r)*bpl;
+	data = i->chans[0];
+	sprint(hdr, "compressed\n%11s %11d %11d %11d %11d ",
+		chantostr(buf, desc), r.min.x, r.min.y, r.max.x, r.max.y);
+	if(write(fd, hdr, 11+5*12) != 11+5*12){
+		werrstr("i/o error writing header");
+		goto ErrOut;
+	}
+	edata = data+n;
+	eout = outbuf+ncblock;
+	line = data;
+	r.max.y = r.min.y;
+	while(line != edata){
+		memset(hash, 0, NHASH*sizeof(Hlist));
+		memset(chain, 0, NMEM*sizeof(Hlist));
+		cp = chain;
+		h = 0;
+		outp = outbuf;
+		for(n = 0; n != NMATCH; n++)
+			h = hupdate(h, line[n]);
+		loutp = outbuf;
+		while(line != edata){
+			ndump = 0;
+			eline = line+bpl;
+			for(p = line; p != eline; ){
+				if(eline-p < NRUN)
+					es = eline;
+				else
+					es = p+NRUN;
+				q = 0;
+				runlen = 0;
+				for(hp = hash[h].next; hp; hp = hp->next){
+					s = p + runlen;
+					if(s >= es)
+						continue;
+					t = hp->s + runlen;
+					for(; s >= p; s--)
+						if(*s != *t--)
+							goto matchloop;
+					t += runlen+2;
+					s += runlen+2;
+					for(; s < es; s++)
+						if(*s != *t++)
+							break;
+					n = s-p;
+					if(n > runlen){
+						runlen = n;
+						q = hp->s;
+						if(n == NRUN)
+							break;
+					}
+			matchloop: ;
+				}
+				if(runlen < NMATCH){
+					if(ndump == NDUMP){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					dumpbuf[ndump++] = *p;
+					runlen = 1;
+				}
+				else{
+					if(ndump != 0){
+						if(eout-outp < ndump+1)
+							goto Bfull;
+						*outp++ = ndump-1+128;
+						memmove(outp, dumpbuf, ndump);
+						outp += ndump;
+						ndump = 0;
+					}
+					offs = p-q-1;
+					if(eout-outp < 2)
+						goto Bfull;
+					*outp++ = ((runlen-NMATCH)<<2) + (offs>>8);
+					*outp++ = offs&255;
+				}
+				for(q = p+runlen; p != q; p++){
+					if(cp->prev)
+						cp->prev->next = 0;
+					cp->next = hash[h].next;
+					cp->prev = &hash[h];
+					if(cp->next)
+						cp->next->prev = cp;
+					cp->prev->next = cp;
+					cp->s = p;
+					if(++cp == &chain[NMEM])
+						cp = chain;
+					if(edata-p > NMATCH)
+						h = hupdate(h, p[NMATCH]);
+				}
+			}
+			if(ndump != 0){
+				if(eout-outp < ndump+1)
+					goto Bfull;
+				*outp++ = ndump-1+128;
+				memmove(outp, dumpbuf, ndump);
+				outp += ndump;
+			}
+			line = eline;
+			loutp = outp;
+			r.max.y++;
+		}
+	Bfull:
+		if(loutp == outbuf){
+			werrstr("compressor out of sync");
+			goto ErrOut;
+		}
+		n = loutp-outbuf;
+		sprint(hdr, "%11d %11ld ", r.max.y, n);
+		write(fd, hdr, 2*12);
+		write(fd, outbuf, n);
+		r.min.y = r.max.y;
+	}
+	free(outbuf);
+	free(hash);
+	free(chain);
+	return 0;
+}
diff --git a/src/cmd/jpg/yuv.c b/src/cmd/jpg/yuv.c
new file mode 100644
index 0000000..929ccf3
--- /dev/null
+++ b/src/cmd/jpg/yuv.c
@@ -0,0 +1,211 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+#include "imagefile.h"
+
+int		cflag = 0;
+int		dflag = 0;
+int		eflag = 0;
+int		nineflag = 0;
+int		threeflag = 0;
+int		output = 0;
+ulong	outchan = CMAP8;
+int		defaultcolor = 1;
+Image	*image;
+
+enum{
+	Border	= 2,
+	Edge		= 5
+};
+
+char	*show(int, char*);
+
+Rawimage** readyuv(int fd, int colorspace);
+
+void
+eresized(int new)
+{
+	Rectangle r;
+
+	if(new && getwindow(display, Refnone) < 0){
+		fprint(2, "yuv: can't reattach to window\n");
+		exits("resize");
+	}
+	if(image == nil)
+		return;
+	r = insetrect(screen->clipr, Edge+Border);
+	r.max.x = r.min.x+Dx(image->r);
+	r.max.y = r.min.y+Dy(image->r);
+	border(screen, r, -Border, nil, ZP);
+	draw(screen, r, image, nil, image->r.min);
+	flushimage(display, 1);
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, i;
+	char *err;
+
+	ARGBEGIN{
+	case '3':		/* produce encoded, compressed, three-color bitmap file; no display by default */
+		threeflag++;
+		/* fall through */
+	case 't':		/* produce encoded, compressed, true-color bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		defaultcolor = 0;
+		outchan = RGB24;
+		break;
+	case 'c':		/* produce encoded, compressed, bitmap file; no display by default */
+		cflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	case 'd':		/* suppress display of image */
+		dflag++;
+		break;
+	case 'e':		/* disable floyd-steinberg error diffusion */
+		eflag++;
+		break;
+	case 'k':		/* force black and white */
+		defaultcolor = 0;
+		outchan = GREY8;
+		break;
+	case 'v':		/* force RGBV */
+		defaultcolor = 0;
+		outchan = CMAP8;
+		break;
+	case '9':		/* produce plan 9, uncompressed, bitmap file; no display by default */
+		nineflag++;
+		dflag++;
+		output++;
+		if(defaultcolor)
+			outchan = CMAP8;
+		break;
+	default:
+		fprint(2, "usage: yuv -39cdektv  [file.yuv ...]\n");
+		exits("usage");
+	}ARGEND;
+
+	err = nil;
+	if(argc == 0)
+		err = show(0, "<stdin>");
+	else{
+		for(i=0; i<argc; i++){
+			fd = open(argv[i], OREAD);
+			if(fd < 0){
+				fprint(2, "yuv: can't open %s: %r\n", argv[i]);
+				err = "open";
+			}else{
+				err = show(fd, argv[i]);
+				close(fd);
+			}
+			if((nineflag || cflag) && argc>1 && err==nil){
+				fprint(2, "yuv: exiting after one file\n");
+				break;
+			}
+		}
+	}
+	exits(err);
+}
+
+int
+init(void)
+{
+	static int inited;
+
+	if(inited == 0){
+		if(initdraw(0, 0, 0) < 0){
+			fprint(2, "yuv: initdraw failed: %r");
+			return -1;
+		}
+		einit(Ekeyboard|Emouse);
+		inited++;
+	}
+	return 1;
+}
+
+char*
+show(int fd, char *name)
+{
+	Rawimage **array, *r, *c;
+	Image *i;
+	int j, ch;
+	char buf[32];
+
+	array = readyuv(fd, CYCbCr);
+	if(array == nil || array[0]==nil){
+		fprint(2, "yuv: decode %s failed: %r\n", name);
+		return "decode";
+	}
+	if(!dflag){
+		if(init() < 0)
+			return "initdraw";
+		if(defaultcolor && screen->depth>8)
+			outchan = RGB24;
+	}
+	r = array[0];
+	if(outchan == CMAP8)
+		c = torgbv(r, !eflag);
+	else{
+		if(outchan==GREY8 || (r->chandesc==CY && threeflag==0))
+			c = totruecolor(r, CY);
+		else
+			c = totruecolor(r, CRGB24);
+	}
+	if(c == nil){
+		fprint(2, "yuv: converting %s to local format failed: %r\n", name);
+		return "torgbv";
+	}
+	if(!dflag){
+		if(r->chandesc == CY)
+			i = allocimage(display, c->r, GREY8, 0, 0);
+		else
+			i = allocimage(display, c->r, outchan, 0, 0);
+		if(i == nil){
+			fprint(2, "yuv: allocimage %s failed: %r\n", name);
+			return "allocimage";
+		}
+		if(loadimage(i, i->r, c->chans[0], c->chanlen) < 0){
+			fprint(2, "yuv: loadimage %s failed: %r\n", name);
+			return "loadimage";
+		}
+		image = i;
+		eresized(0);
+		if((ch=ekbd())=='q' || ch==0x7F || ch==0x04)
+			exits(nil);
+		draw(screen, screen->clipr, display->white, nil, ZP);
+		image = nil;
+		freeimage(i);
+	}
+	if(nineflag){
+		chantostr(buf, outchan);
+		print("%11s %11d %11d %11d %11d ", buf,
+			c->r.min.x, c->r.min.y, c->r.max.x, c->r.max.y);
+		if(write(1, c->chans[0], c->chanlen) != c->chanlen){
+			fprint(2, "yuv: %s: write error %r\n", name);
+			return "write";
+		}
+	}else if(cflag){
+		if(writerawimage(1, c) < 0){
+			fprint(2, "yuv: %s: write error: %r\n", name);
+			return "write";
+		}
+	}
+	for(j=0; j<r->nchans; j++)
+		free(r->chans[j]);
+	free(r->cmap);
+	free(r);
+	free(array);
+	if(c){
+		free(c->chans[0]);
+		free(c);
+	}
+	return nil;
+}
diff --git a/src/cmd/map/index.c b/src/cmd/map/index.c
new file mode 100644
index 0000000..0ff75f5
--- /dev/null
+++ b/src/cmd/map/index.c
@@ -0,0 +1,88 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static proj Yaitoff(double p0, double p1){USED(p0); USED(p1); return aitoff();}
+static proj Yalbers(double p0,double p1){USED(p0); USED(p1); return albers(p0,p1);}
+static proj Yazequalarea(double p0, double p1){USED(p0); USED(p1); return azequalarea();}
+static proj Yazequidistant(double p0, double p1){USED(p0); USED(p1); return azequidistant();}
+static proj Ybicentric(double p0,double p1){USED(p0); USED(p1); return bicentric(p0);}
+static proj Ybonne(double p0,double p1){USED(p0); USED(p1); return bonne(p0);}
+static proj Yconic(double p0,double p1){USED(p0); USED(p1); return conic(p0);}
+static proj Ycylequalarea(double p0,double p1){USED(p0); USED(p1); return cylequalarea(p0);}
+static proj Ycylindrical(double p0, double p1){USED(p0); USED(p1); return cylindrical();}
+static proj Yelliptic(double p0,double p1){USED(p0); USED(p1); return elliptic(p0);}
+static proj Yfisheye(double p0,double p1){USED(p0); USED(p1); return fisheye(p0);}
+static proj Ygall(double p0,double p1){USED(p0); USED(p1); return gall(p0);}
+static proj Ygilbert(double p0, double p1){USED(p0); USED(p1); return gilbert();}
+static proj Yglobular(double p0, double p1){USED(p0); USED(p1); return globular();}
+static proj Ygnomonic(double p0, double p1){USED(p0); USED(p1); return gnomonic();}
+static proj Yguyou(double p0, double p1){USED(p0); USED(p1); return guyou();}
+static proj Yharrison(double p0,double p1){USED(p0); USED(p1); return harrison(p0,p1);}
+static proj Yhex(double p0, double p1){USED(p0); USED(p1); return hex();}
+static proj Yhoming(double p0,double p1){USED(p0); USED(p1); return homing(p0);}
+static proj Ylagrange(double p0, double p1){USED(p0); USED(p1); return lagrange();}
+static proj Ylambert(double p0,double p1){USED(p0); USED(p1); return lambert(p0,p1);}
+static proj Ylaue(double p0, double p1){USED(p0); USED(p1); return laue();}
+static proj Ylune(double p0,double p1){USED(p0); USED(p1); return lune(p0,p1);}
+static proj Ymecca(double p0, double p1){USED(p0); USED(p1); return mecca(p0);}
+static proj Ymercator(double p0, double p1){USED(p0); USED(p1); return mercator();}
+static proj Ymollweide(double p0, double p1){USED(p0); USED(p1); return mollweide();}
+static proj Ynewyorker(double p0,double p1){USED(p0); USED(p1); return newyorker(p0);}
+static proj Yorthographic(double p0, double p1){USED(p0); USED(p1); return orthographic();}
+static proj Yperspective(double p0,double p1){USED(p0); USED(p1); return perspective(p0);}
+static proj Ypolyconic(double p0, double p1){USED(p0); USED(p1); return polyconic();}
+static proj Yrectangular(double p0,double p1){USED(p0); USED(p1); return rectangular(p0);}
+static proj Ysimpleconic(double p0,double p1){USED(p0); USED(p1); return simpleconic(p0,p1);}
+static proj Ysinusoidal(double p0, double p1){USED(p0); USED(p1); return sinusoidal();}
+static proj Ysp_albers(double p0,double p1){USED(p0); USED(p1); return sp_albers(p0,p1);}
+static proj Ysp_mercator(double p0, double p1){USED(p0); USED(p1); return sp_mercator();}
+static proj Ysquare(double p0, double p1){USED(p0); USED(p1); return square();}
+static proj Ystereographic(double p0, double p1){USED(p0); USED(p1); return stereographic();}
+static proj Ytetra(double p0, double p1){USED(p0); USED(p1); return tetra();}
+static proj Ytrapezoidal(double p0,double p1){USED(p0); USED(p1); return trapezoidal(p0,p1);}
+static proj Yvandergrinten(double p0, double p1){USED(p0); USED(p1); return vandergrinten();}
+
+struct index index[] = {
+	{"aitoff", Yaitoff, 0, picut, 0, 0, 0},
+	{"albers", Yalbers, 2, picut, 3, 0, 0},
+	{"azequalarea", Yazequalarea, 0, nocut, 1, 0, 0},
+	{"azequidistant", Yazequidistant, 0, nocut, 1, 0, 0},
+	{"bicentric", Ybicentric, 1, nocut, 0, 0, 0},
+	{"bonne", Ybonne, 1, picut, 0, 0, 0},
+	{"conic", Yconic, 1, picut, 0, 0, 0},
+	{"cylequalarea", Ycylequalarea, 1, picut, 3, 0, 0},
+	{"cylindrical", Ycylindrical, 0, picut, 0, 0, 0},
+	{"elliptic", Yelliptic, 1, picut, 0, 0, 0},
+	{"fisheye", Yfisheye, 1, nocut, 0, 0, 0},
+	{"gall", Ygall, 1, picut, 3, 0, 0},
+	{"gilbert", Ygilbert, 0, picut, 0, 0, 0},
+	{"globular", Yglobular, 0, picut, 0, 0, 0},
+	{"gnomonic", Ygnomonic, 0, nocut, 0, 0, plimb},
+	{"guyou", Yguyou, 0, guycut, 0, 0, 0},
+	{"harrison", Yharrison, 2, nocut, 0, 0, plimb},
+	{"hex", Yhex, 0, hexcut, 0, 0, 0},
+	{"homing", Yhoming, 1, nocut, 3, 0, hlimb},
+	{"lagrange", Ylagrange,0,picut,0, 0, 0},
+	{"lambert", Ylambert, 2, picut, 0, 0, 0},
+	{"laue", Ylaue, 0, nocut, 0, 0, 0},
+	{"lune", Ylune, 2, nocut, 0, 0, 0},
+	{"mecca", Ymecca, 1, picut, 3, 0, mlimb},
+	{"mercator", Ymercator, 0, picut, 3, 0, 0},
+	{"mollweide", Ymollweide, 0, picut, 0, 0, 0},
+	{"newyorker", Ynewyorker, 1, nocut, 0, 0, 0},
+	{"orthographic", Yorthographic, 0, nocut, 0, 0, olimb},
+	{"perspective", Yperspective, 1, nocut, 0, 0, plimb},
+	{"polyconic", Ypolyconic, 0, picut, 0, 0, 0},
+	{"rectangular", Yrectangular, 1, picut, 3, 0, 0},
+	{"simpleconic", Ysimpleconic, 2, picut, 3, 0, 0},
+	{"sinusoidal", Ysinusoidal, 0, picut, 0, 0, 0},
+	{"sp_albers", Ysp_albers, 2, picut, 3, 1, 0},
+	{"sp_mercator", Ysp_mercator, 0, picut, 0, 1, 0},
+	{"square", Ysquare, 0, picut, 0, 0, 0},
+	{"stereographic", Ystereographic, 0, nocut, 0, 0, 0},
+	{"tetra", Ytetra, 0, tetracut, 0, 0, 0},
+	{"trapezoidal", Ytrapezoidal, 2, picut, 3, 0, 0},
+	{"vandergrinten", Yvandergrinten, 0, picut, 0, 0, 0},
+	0
+};
diff --git a/src/cmd/map/iplot.h b/src/cmd/map/iplot.h
new file mode 100644
index 0000000..be472ff
--- /dev/null
+++ b/src/cmd/map/iplot.h
@@ -0,0 +1,51 @@
+/* Plotting functions for v8 and v9 systems */
+/* This file is an alternative to plot.h */
+
+/* open the plotting output */
+#define openpl()  print("o\n")
+
+/* close the plotting output */
+#define closepl()  print("cl\n")
+
+/* make sure the page or screen is clear */
+#define erase() print("e\n")
+
+/* plot a point at _x,_y, which becomes current */
+#define point(_x,_y)  print("poi %d %d\n", _x,_y)
+
+/* coordinates to be assigned to lower left and upper right
+   corners of (square) plotting area */
+#define range(_x,_y,_X,_Y)  print("ra %d %d %d %d\n", _x,_y,_X,_Y)
+
+/* place text, first letter at current point, which does not change */
+#define text(_s)  {if(*(_s) == ' ')print("t \"%s\"\n",_s); else print("t %s\n", _s); }
+
+/* draw line from current point to _x,_y, which becomes current */
+#define vec(_x,_y)  print("v %d %d\n", _x,_y)
+
+/* _x,_y becomes current point */
+#define move(_x, _y)  print("m %d %d\n", _x, _y)
+
+/* specify style for drawing lines */
+
+#define SOLID "solid"
+#define DOTTED "dotted"
+#define DASHED "dashed"
+#define DOTDASH "dotdash"
+
+#define pen(_s)  print("pe %s\n", _s)
+
+#define BLACK "z"
+#define RED "r"
+#define YELLOW "y"
+#define GREEN "g"
+#define BLUE "b"
+#define CYAN "c"
+#define MAGENTA "m"
+#define WHITE "w"
+
+#define colorcode(_s) ((strcmp(_s,"black")==0)?BLACK:_s)
+
+#define colorx(_s) print("co %s\n", _s);	/* funny name is all ken's fault */
+
+#define comment(s,f)
diff --git a/src/cmd/map/libmap/aitoff.c b/src/cmd/map/libmap/aitoff.c
new file mode 100644
index 0000000..83b777f
--- /dev/null
+++ b/src/cmd/map/libmap/aitoff.c
@@ -0,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+#define Xaitwist Xaitpole.nlat
+static struct place Xaitpole;
+
+static int
+Xaitoff(struct place *place, double *x, double *y)
+{
+	struct place p;
+	copyplace(place,&p);
+	p.wlon.l /= 2.;
+	sincos(&p.wlon);
+	norm(&p,&Xaitpole,&Xaitwist);
+	Xazequalarea(&p,x,y);
+	*x *= 2.;
+	return(1);
+}
+
+proj
+aitoff(void)
+{
+	latlon(0.,0.,&Xaitpole);
+	return(Xaitoff);
+}
diff --git a/src/cmd/map/libmap/albers.c b/src/cmd/map/libmap/albers.c
new file mode 100644
index 0000000..56e3e9f
--- /dev/null
+++ b/src/cmd/map/libmap/albers.c
@@ -0,0 +1,117 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+/* For Albers formulas see Deetz and Adams "Elements of Map Projection", */
+/* USGS Special Publication No. 68, GPO 1921 */
+
+static double r0sq, r1sq, d2, n, den, sinb1, sinb2;
+static struct coord plat1, plat2;
+static int southpole;
+
+static double num(double s)
+{
+	if(d2==0)
+		return(1);
+	s = d2*s*s;
+	return(1+s*(2./3+s*(3./5+s*(4./7+s*5./9))));
+}
+
+/* Albers projection for a spheroid, good only when N pole is fixed */
+
+static int
+Xspalbers(struct place *place, double *x, double *y)
+{
+	double r = sqrt(r0sq-2*(1-d2)*place->nlat.s*num(place->nlat.s)/n);
+	double t = n*place->wlon.l;
+	*y = r*cos(t);
+	*x = -r*sin(t);
+	if(!southpole)
+		*y = -*y;
+	else
+		*x = -*x;
+	return(1);
+}
+
+/* lat1, lat2: std parallels; e2: squared eccentricity */
+
+static proj albinit(double lat1, double lat2, double e2)
+{
+	double r1;
+	double t;
+	for(;;) {
+		if(lat1 < -90)
+			lat1 = -180 - lat1;
+		if(lat2 > 90)
+			lat2 = 180 - lat2;
+		if(lat1 <= lat2)
+			break;
+		t = lat1; lat1 = lat2; lat2 = t;
+	}
+	if(lat2-lat1 < 1) {
+		if(lat1 > 89)
+			return(azequalarea());
+		return(0);
+	}
+	if(fabs(lat2+lat1) < 1)
+		return(cylequalarea(lat1));
+	d2 = e2;
+	den = num(1.);
+	deg2rad(lat1,&plat1);
+	deg2rad(lat2,&plat2);
+	sinb1 = plat1.s*num(plat1.s)/den;
+	sinb2 = plat2.s*num(plat2.s)/den;
+	n = (plat1.c*plat1.c/(1-e2*plat1.s*plat1.s) -
+	    plat2.c*plat2.c/(1-e2*plat2.s*plat2.s)) /
+	    (2*(1-e2)*den*(sinb2-sinb1));
+	r1 = plat1.c/(n*sqrt(1-e2*plat1.s*plat1.s));
+	r1sq = r1*r1;
+	r0sq = r1sq + 2*(1-e2)*den*sinb1/n;
+	southpole = lat1<0 && plat2.c>plat1.c;
+	return(Xspalbers);
+}
+
+proj
+sp_albers(double lat1, double lat2)
+{
+	return(albinit(lat1,lat2,EC2));
+}
+
+proj
+albers(double lat1, double lat2)
+{
+	return(albinit(lat1,lat2,0.));
+}
+
+static double scale = 1;
+static double twist = 0;
+
+void
+albscale(double x, double y, double lat, double lon)
+{
+	struct place place;
+	double alat, alon, x1,y1;
+	scale = 1;
+	twist = 0;
+	invalb(x,y,&alat,&alon);
+	twist = lon - alon;
+	deg2rad(lat,&place.nlat);
+	deg2rad(lon,&place.wlon);
+	Xspalbers(&place,&x1,&y1);
+	scale = sqrt((x1*x1+y1*y1)/(x*x+y*y));
+}
+
+void
+invalb(double x, double y, double *lat, double *lon)
+{
+	int i;
+	double sinb_den, sinp;
+	x *= scale;
+	y *= scale;
+	*lon = atan2(-x,fabs(y))/(RAD*n) + twist;
+	sinb_den = (r0sq - x*x - y*y)*n/(2*(1-d2));
+	sinp = sinb_den;
+	for(i=0; i<5; i++)
+		sinp = sinb_den/num(sinp);
+	*lat = asin(sinp)/RAD;
+}
diff --git a/src/cmd/map/libmap/azequalarea.c b/src/cmd/map/libmap/azequalarea.c
new file mode 100644
index 0000000..6bae893
--- /dev/null
+++ b/src/cmd/map/libmap/azequalarea.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xazequalarea(struct place *place, double *x, double *y)
+{
+	double r;
+	r = sqrt(1. - place->nlat.s);
+	*x = - r * place->wlon.s;
+	*y = - r * place->wlon.c;
+	return(1);
+}
+
+proj
+azequalarea(void)
+{
+	return(Xazequalarea);
+}
diff --git a/src/cmd/map/libmap/azequidist.c b/src/cmd/map/libmap/azequidist.c
new file mode 100644
index 0000000..d26d33d
--- /dev/null
+++ b/src/cmd/map/libmap/azequidist.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xazequidistant(struct place *place, double *x, double *y)
+{
+	double colat;
+	colat = PI/2 - place->nlat.l;
+	*x = -colat * place->wlon.s;
+	*y = -colat * place->wlon.c;
+	return(1);
+}
+
+proj
+azequidistant(void)
+{
+	return(Xazequidistant);
+}
diff --git a/src/cmd/map/libmap/bicentric.c b/src/cmd/map/libmap/bicentric.c
new file mode 100644
index 0000000..33fd8d1
--- /dev/null
+++ b/src/cmd/map/libmap/bicentric.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord center;
+
+static int
+Xbicentric(struct place *place, double *x, double *y)
+{
+	if(place->wlon.c<=.01||place->nlat.c<=.01)
+		return(-1);
+	*x = -center.c*place->wlon.s/place->wlon.c;
+	*y = place->nlat.s/(place->nlat.c*place->wlon.c);
+	return(*x**x+*y**y<=9);
+}
+
+proj
+bicentric(double l)
+{
+	l = fabs(l);
+	if(l>89)
+		return(0);
+	deg2rad(l,&center);
+	return(Xbicentric);
+}
diff --git a/src/cmd/map/libmap/bonne.c b/src/cmd/map/libmap/bonne.c
new file mode 100644
index 0000000..858f0d6
--- /dev/null
+++ b/src/cmd/map/libmap/bonne.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord stdpar;
+static double r0;
+
+static int
+Xbonne(struct place *place, double *x, double *y)
+{
+	double r, alpha;
+	r = r0 - place->nlat.l;
+	if(r<.001)
+		if(fabs(stdpar.c)<1e-10)
+			alpha = place->wlon.l;
+		else if(fabs(place->nlat.c)==0)
+			alpha = 0;
+		else 
+			alpha = place->wlon.l/(1+
+				stdpar.c*stdpar.c*stdpar.c/place->nlat.c/3);
+	else
+		alpha = place->wlon.l * place->nlat.c / r;
+	*x = - r*sin(alpha);
+	*y = - r*cos(alpha);
+	return(1);
+}
+
+proj
+bonne(double par)
+{
+	if(fabs(par*RAD) < .01)
+		return(Xsinusoidal);
+	deg2rad(par, &stdpar);
+	r0 = stdpar.c/stdpar.s + stdpar.l;
+	return(Xbonne);
+}
diff --git a/src/cmd/map/libmap/ccubrt.c b/src/cmd/map/libmap/ccubrt.c
new file mode 100644
index 0000000..8a7af56
--- /dev/null
+++ b/src/cmd/map/libmap/ccubrt.c
@@ -0,0 +1,13 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+void
+ccubrt(double zr, double zi, double *wr, double *wi)
+{
+	double r, theta;
+	theta = atan2(zi,zr);
+	r = cubrt(hypot(zr,zi));
+	*wr = r*cos(theta/3);
+	*wi = r*sin(theta/3);
+}
diff --git a/src/cmd/map/libmap/complex.c b/src/cmd/map/libmap/complex.c
new file mode 100644
index 0000000..b3099fd
--- /dev/null
+++ b/src/cmd/map/libmap/complex.c
@@ -0,0 +1,85 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+/*complex divide, defensive against overflow from
+ *	* and /, but not from + and -
+ *	assumes underflow yields 0.0
+ *	uses identities:
+ *	(a + bi)/(c + di) = ((a + bd/c) + (b - ad/c)i)/(c + dd/c)
+ *	(a + bi)/(c + di) = (b - ai)/(d - ci)
+*/
+void
+cdiv(double a, double b, double c, double d, double *u, double *v)
+{
+	double r,t;
+	if(fabs(c)<fabs(d)) {
+		t = -c; c = d; d = t;
+		t = -a; a = b; b = t;
+	}
+	r = d/c;
+	t = c + r*d;
+	*u = (a + r*b)/t;
+	*v = (b - r*a)/t;
+}
+
+void
+cmul(double c1, double c2, double d1, double d2, double *e1, double *e2)
+{
+	*e1 = c1*d1 - c2*d2;
+	*e2 = c1*d2 + c2*d1;
+}
+
+void
+csq(double c1, double c2, double *e1, double *e2)
+{
+	*e1 = c1*c1 - c2*c2;
+	*e2 = c1*c2*2;
+}
+
+/* complex square root
+ *	assumes underflow yields 0.0
+ *	uses these identities:
+ *	sqrt(x+_iy) = sqrt(r(cos(t)+_isin(t))
+ *	           = sqrt(r)(cos(t/2)+_isin(t/2))
+ *	cos(t/2) = sin(t)/2sin(t/2) = sqrt((1+cos(t)/2)
+ *	sin(t/2) = sin(t)/2cos(t/2) = sqrt((1-cos(t)/2)
+*/
+void
+csqrt(double c1, double c2, double *e1, double *e2)
+{
+	double r,s;
+	double x,y;
+	x = fabs(c1);
+	y = fabs(c2);
+	if(x>=y) {
+		if(x==0) {
+			*e1 = *e2 = 0;
+			return;
+		}
+		r = x;
+		s = y/x;
+	} else {
+		r = y;
+		s = x/y;
+	}
+	r *= sqrt(1+ s*s);
+	if(c1>0) {
+		*e1 = sqrt((r+c1)/2);
+		*e2 = c2/(2* *e1);
+	} else {
+		*e2 = sqrt((r-c1)/2);
+		if(c2<0)
+			*e2 = -*e2;
+		*e1 = c2/(2* *e2);
+	}
+}
+
+
+void cpow(double c1, double c2, double *d1, double *d2, double pwr)
+{
+	double theta = pwr*atan2(c2,c1);
+	double r = pow(hypot(c1,c2), pwr);
+	*d1 = r*cos(theta);
+	*d2 = r*sin(theta);
+}
diff --git a/src/cmd/map/libmap/conic.c b/src/cmd/map/libmap/conic.c
new file mode 100644
index 0000000..ba4430c
--- /dev/null
+++ b/src/cmd/map/libmap/conic.c
@@ -0,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord stdpar;
+
+static int
+Xconic(struct place *place, double *x, double *y)
+{
+	double r;
+	if(fabs(place->nlat.l-stdpar.l) > 80.*RAD)
+		return(-1);
+	r = stdpar.c/stdpar.s - tan(place->nlat.l - stdpar.l);
+	*x = - r*sin(place->wlon.l * stdpar.s);
+	*y = - r*cos(place->wlon.l * stdpar.s);
+	if(r>3) return(0);
+	return(1);
+}
+
+proj
+conic(double par)
+{
+	if(fabs(par) <.1)
+		return(Xcylindrical);
+	deg2rad(par, &stdpar);
+	return(Xconic);
+}
diff --git a/src/cmd/map/libmap/cubrt.c b/src/cmd/map/libmap/cubrt.c
new file mode 100644
index 0000000..fd508d2
--- /dev/null
+++ b/src/cmd/map/libmap/cubrt.c
@@ -0,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+double
+cubrt(double a)
+{
+	double x,y,x1;
+	if(a==0) 
+		return(0.);
+	y = 1;
+	if(a<0) {
+		y = -y;
+		a = -a;
+	}
+	while(a<1) {
+		a *= 8;
+		y /= 2;
+	}
+	while(a>1) {
+		a /= 8;
+		y *= 2;
+	}
+	x = 1;
+	do {
+		x1 = x;
+		x = (2*x1+a/(x1*x1))/3;
+	} while(fabs(x-x1)>10.e-15);
+	return(x*y);
+}
diff --git a/src/cmd/map/libmap/cuts.c b/src/cmd/map/libmap/cuts.c
new file mode 100644
index 0000000..ad9d6dc
--- /dev/null
+++ b/src/cmd/map/libmap/cuts.c
@@ -0,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+extern void abort(void);
+
+/* these routines duplicate names found in map.c.  they are
+called from routines in hex.c, guyou.c, and tetra.c, which
+are in turn invoked directly from map.c.  this bad organization
+arises from data hiding; only these three files know stuff
+that's necessary for the proper handling of the unusual cuts
+involved in these projections.
+
+the calling routines are not advertised as part of the library,
+and the library duplicates should never get loaded, however they
+are included to make the libary self-standing.*/
+
+int
+picut(struct place *g, struct place *og, double *cutlon)
+{
+	g; og; cutlon;
+	abort();
+	return 0;
+}
+
+int
+ckcut(struct place *g1, struct place *g2, double lon)
+{
+	g1; g2; lon;
+	abort();
+	return 0;
+}
+
+double
+reduce(double x)
+{
+	x;
+	abort();
+	return 0;
+}
diff --git a/src/cmd/map/libmap/cylequalarea.c b/src/cmd/map/libmap/cylequalarea.c
new file mode 100644
index 0000000..3cf222f
--- /dev/null
+++ b/src/cmd/map/libmap/cylequalarea.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double a;
+
+static int
+Xcylequalarea(struct place *place, double *x, double *y)
+{
+	*x = - place->wlon.l * a;
+	*y = place->nlat.s;
+	return(1);
+}
+
+proj
+cylequalarea(double par)
+{
+	struct coord stdp0;
+	if(par > 89.0)
+		return(0);
+	deg2rad(par, &stdp0);
+	a = stdp0.c*stdp0.c;
+	return(Xcylequalarea);
+}
diff --git a/src/cmd/map/libmap/cylindrical.c b/src/cmd/map/libmap/cylindrical.c
new file mode 100644
index 0000000..4d01bc2
--- /dev/null
+++ b/src/cmd/map/libmap/cylindrical.c
@@ -0,0 +1,19 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xcylindrical(struct place *place, double *x, double *y)
+{
+	if(fabs(place->nlat.l) > 80.*RAD)
+		return(-1);
+	*x = - place->wlon.l;
+	*y = place->nlat.s / place->nlat.c;
+	return(1);
+}
+
+proj
+cylindrical(void)
+{
+	return(Xcylindrical);
+}
diff --git a/src/cmd/map/libmap/elco2.c b/src/cmd/map/libmap/elco2.c
new file mode 100644
index 0000000..b4c9bbf
--- /dev/null
+++ b/src/cmd/map/libmap/elco2.c
@@ -0,0 +1,132 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+/* elliptic integral routine, R.Bulirsch,
+ *	Numerische Mathematik 7(1965) 78-90
+ *	calculate integral from 0 to x+iy of
+ *	(a+b*t^2)/((1+t^2)*sqrt((1+t^2)*(1+kc^2*t^2)))
+ *	yields about D valid figures, where CC=10e-D
+ *	for a*b>=0, except at branchpoints x=0,y=+-i,+-i/kc;
+ *	there the accuracy may be reduced.
+ *	fails for kc=0 or x<0
+ *	return(1) for success, return(0) for fail
+ *
+ *	special case a=b=1 is equivalent to
+ *	standard elliptic integral of first kind
+ *	from 0 to atan(x+iy) of
+ *	1/sqrt(1-k^2*(sin(t))^2) where k^2=1-kc^2
+*/
+
+#define ROOTINF 10.e18
+#define CC 1.e-6
+
+int
+elco2(double x, double y, double kc, double a, double b, double *u, double *v)
+{
+	double c,d,dn1,dn2,e,e1,e2,f,f1,f2,h,k,m,m1,m2,sy;
+	double d1[13],d2[13];
+	int i,l;
+	if(kc==0||x<0)
+		return(0);
+	sy = y>0? 1: y==0? 0: -1;
+	y = fabs(y);
+	csq(x,y,&c,&e2);
+	d = kc*kc;
+	k = 1-d;
+	e1 = 1+c;
+	cdiv2(1+d*c,d*e2,e1,e2,&f1,&f2);
+	f2 = -k*x*y*2/f2;
+	csqr(f1,f2,&dn1,&dn2);
+	if(f1<0) {
+		f1 = dn1;
+		dn1 = -dn2;
+		dn2 = -f1;
+	}
+	if(k<0) {
+		dn1 = fabs(dn1);
+		dn2 = fabs(dn2);
+	}
+	c = 1+dn1;
+	cmul(e1,e2,c,dn2,&f1,&f2);
+	cdiv(x,y,f1,f2,&d1[0],&d2[0]);
+	h = a-b;
+	d = f = m = 1;
+	kc = fabs(kc);
+	e = a;
+	a += b;
+	l = 4;
+	for(i=1;;i++) {
+		m1 = (kc+m)/2;
+		m2 = m1*m1;
+		k *= f/(m2*4);
+		b += e*kc;
+		e = a;
+		cdiv2(kc+m*dn1,m*dn2,c,dn2,&f1,&f2);
+		csqr(f1/m1,k*dn2*2/f2,&dn1,&dn2);
+		cmul(dn1,dn2,x,y,&f1,&f2);
+		x = fabs(f1);
+		y = fabs(f2);
+		a += b/m1;
+		l *= 2;
+		c = 1 +dn1;
+		d *= k/2;
+		cmul(x,y,x,y,&e1,&e2);
+		k *= k;
+
+		cmul(c,dn2,1+e1*m2,e2*m2,&f1,&f2);
+		cdiv(d*x,d*y,f1,f2,&d1[i],&d2[i]);
+		if(k<=CC) 
+			break;
+		kc = sqrt(m*kc);
+		f = m2;
+		m = m1;
+	}
+	f1 = f2 = 0;
+	for(;i>=0;i--) {
+		f1 += d1[i];
+		f2 += d2[i];
+	}
+	x *= m1;
+	y *= m1;
+	cdiv2(1-y,x,1+y,-x,&e1,&e2);
+	e2 = x*2/e2;
+	d = a/(m1*l);
+	*u = atan2(e2,e1);
+	if(*u<0)
+		*u += PI;
+	a = d*sy/2;
+	*u = d*(*u) + f1*h;
+	*v = (-1-log(e1*e1+e2*e2))*a + f2*h*sy + a;
+	return(1);
+}
+
+void
+cdiv2(double c1, double c2, double d1, double d2, double *e1, double *e2)
+{
+	double t;
+	if(fabs(d2)>fabs(d1)) {
+		t = d1, d1 = d2, d2 = t;
+		t = c1, c1 = c2, c2 = t;
+	}
+	if(fabs(d1)>ROOTINF)
+		*e2 = ROOTINF*ROOTINF;
+	else
+		*e2 = d1*d1 + d2*d2;
+	t = d2/d1;
+	*e1 = (c1+t*c2)/(d1+t*d2); /* (c1*d1+c2*d2)/(d1*d1+d2*d2) */
+}
+
+/* complex square root of |x|+iy */
+void
+csqr(double c1, double c2, double *e1, double *e2)
+{
+	double r2;
+	r2 = c1*c1 + c2*c2;
+	if(r2<=0) {
+		*e1 = *e2 = 0;
+		return;
+	}
+	*e1 = sqrt((sqrt(r2) + fabs(c1))/2);
+	*e2 = c2/(*e1*2);
+}
diff --git a/src/cmd/map/libmap/elliptic.c b/src/cmd/map/libmap/elliptic.c
new file mode 100644
index 0000000..3f3b1d5
--- /dev/null
+++ b/src/cmd/map/libmap/elliptic.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+struct coord center;
+
+static int
+Xelliptic(struct place *place, double *x, double *y)
+{
+	double r1,r2;
+	r1 = acos(place->nlat.c*(place->wlon.c*center.c
+		- place->wlon.s*center.s));
+	r2 = acos(place->nlat.c*(place->wlon.c*center.c
+		+ place->wlon.s*center.s));
+	*x = -(r1*r1 - r2*r2)/(4*center.l);
+	*y = (r1*r1+r2*r2)/2 - (center.l*center.l+*x**x);
+	if(*y < 0)
+		*y = 0;
+	*y = sqrt(*y);
+	if(place->nlat.l<0)
+		*y = -*y;
+	return(1);
+}
+
+proj
+elliptic(double l)
+{
+	l = fabs(l);
+	if(l>89)
+		return(0);
+	if(l<1)
+		return(Xazequidistant);
+	deg2rad(l,&center);
+	return(Xelliptic);
+}
diff --git a/src/cmd/map/libmap/fisheye.c b/src/cmd/map/libmap/fisheye.c
new file mode 100644
index 0000000..412d65e
--- /dev/null
+++ b/src/cmd/map/libmap/fisheye.c
@@ -0,0 +1,26 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+/* refractive fisheye, not logarithmic */
+
+static double n;
+
+static int
+Xfisheye(struct place *place, double *x, double *y)
+{
+	double r;
+	double u = sin(PI/4-place->nlat.l/2)/n;
+	if(fabs(u) > .97)
+		return -1;
+	r = tan(asin(u));
+	*x = -r*place->wlon.s;
+	*y = -r*place->wlon.c;
+	return 1;
+}
+
+proj
+fisheye(double par)
+{
+	n = par;
+	return n<.1? 0: Xfisheye;
+}
diff --git a/src/cmd/map/libmap/gall.c b/src/cmd/map/libmap/gall.c
new file mode 100644
index 0000000..84da651
--- /dev/null
+++ b/src/cmd/map/libmap/gall.c
@@ -0,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double scale;
+
+static int
+Xgall(struct place *place, double *x, double *y)
+{
+	/* two ways to compute tan(place->nlat.l/2) */
+	if(fabs(place->nlat.s)<.1)
+		*y = sin(place->nlat.l/2)/cos(place->nlat.l/2);
+	else
+		*y = (1-place->nlat.c)/place->nlat.s;
+	*x = -scale*place->wlon.l;
+	return 1;
+}
+
+proj
+gall(double par)
+{
+	double coshalf;
+	if(fabs(par)>80)
+		return 0;
+	par *= RAD;
+	coshalf = cos(par/2);
+	scale = cos(par)/(2*coshalf*coshalf);
+	return Xgall;
+}
diff --git a/src/cmd/map/libmap/gilbert.c b/src/cmd/map/libmap/gilbert.c
new file mode 100644
index 0000000..173ffcd
--- /dev/null
+++ b/src/cmd/map/libmap/gilbert.c
@@ -0,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xgilbert(struct place *p, double *x, double *y)
+{
+/* the interesting part - map the sphere onto a hemisphere */
+	struct place q;
+	q.nlat.s = tan(0.5*(p->nlat.l));
+	if(q.nlat.s > 1) q.nlat.s = 1;
+	if(q.nlat.s < -1) q.nlat.s = -1;
+	q.nlat.c = sqrt(1 - q.nlat.s*q.nlat.s);
+	q.wlon.l = p->wlon.l/2;
+	sincos(&q.wlon);
+/* the dull part: present the hemisphere orthogrpahically */
+	*y = q.nlat.s;
+	*x = -q.wlon.s*q.nlat.c;
+	return(1);
+}
+
+proj
+gilbert(void)
+{
+	return(Xgilbert);
+}
+
+/* derivation of the interesting part:
+   map the sphere onto the plane by stereographic projection;
+   map the plane onto a half plane by sqrt;
+   map the half plane back to the sphere by stereographic
+   projection
+
+   n,w are original lat and lon
+   r is stereographic radius
+   primes are transformed versions
+
+   r = cos(n)/(1+sin(n))
+   r' = sqrt(r) = cos(n')/(1+sin(n'))
+
+   r'^2 = (1-sin(n')^2)/(1+sin(n')^2) = cos(n)/(1+sin(n))
+
+   this is a linear equation for sin n', with solution
+
+   sin n' = (1+sin(n)-cos(n))/(1+sin(n)+cos(n))
+
+   use standard formula: tan x/2 = (1-cos x)/sin x = sin x/(1+cos x)
+   to show that the right side of the last equation is tan(n/2)
+*/
+
+
diff --git a/src/cmd/map/libmap/guyou.c b/src/cmd/map/libmap/guyou.c
new file mode 100644
index 0000000..b37736f
--- /dev/null
+++ b/src/cmd/map/libmap/guyou.c
@@ -0,0 +1,101 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct place gywhem, gyehem;
+static struct coord gytwist;
+static double gyconst, gykc, gyside;
+
+
+static void
+dosquare(double z1, double z2, double *x, double *y)
+{
+	double w1,w2;
+	w1 = z1 -1;
+	if(fabs(w1*w1+z2*z2)>.000001) {
+		cdiv(z1+1,z2,w1,z2,&w1,&w2);
+		w1 *= gyconst;
+		w2 *= gyconst;
+		if(w1<0)
+			w1 = 0;
+		elco2(w1,w2,gykc,1.,1.,x,y);
+	} else {
+		*x = gyside;
+		*y = 0;
+	}
+}
+
+int
+Xguyou(struct place *place, double *x, double *y)
+{
+	int ew;		/*which hemisphere*/
+	double z1,z2;
+	struct place pl;
+	ew = place->wlon.l<0;
+	copyplace(place,&pl);
+	norm(&pl,ew?&gyehem:&gywhem,&gytwist);
+	Xstereographic(&pl,&z1,&z2);
+	dosquare(z1/2,z2/2,x,y);
+	if(!ew)
+		*x -= gyside;
+	return(1);
+}
+
+proj
+guyou(void)
+{
+	double junk;
+	gykc = 1/(3+2*sqrt(2.));
+	gyconst = -(1+sqrt(2.));
+	elco2(-gyconst,0.,gykc,1.,1.,&gyside,&junk);
+	gyside *= 2;
+	latlon(0.,90.,&gywhem);
+	latlon(0.,-90.,&gyehem);
+	deg2rad(0.,&gytwist);
+	return(Xguyou);
+}
+
+int
+guycut(struct place *g, struct place *og, double *cutlon)
+{
+	int c;
+	c = picut(g,og,cutlon);
+	if(c!=1)
+		return(c);
+	*cutlon = 0.;
+	if(g->nlat.c<.7071||og->nlat.c<.7071)
+		return(ckcut(g,og,0.));
+	return(1);
+}
+
+static int
+Xsquare(struct place *place, double *x, double *y)
+{
+	double z1,z2;
+	double r, theta;
+	struct place p;
+	copyplace(place,&p);
+	if(place->nlat.l<0) {
+		p.nlat.l = -p.nlat.l;
+		p.nlat.s = -p.nlat.s;
+	}
+	if(p.nlat.l<FUZZ && fabs(p.wlon.l)>PI-FUZZ){
+		*y = -gyside/2;
+		*x = p.wlon.l>0?0:gyside;
+		return(1);
+	}
+	Xstereographic(&p,&z1,&z2);
+	r = sqrt(sqrt(hypot(z1,z2)/2));
+	theta = atan2(z1,-z2)/4;
+	dosquare(r*sin(theta),-r*cos(theta),x,y);
+	if(place->nlat.l<0)
+		*y = -gyside - *y;
+	return(1);
+}
+
+proj
+square(void)
+{
+	guyou();
+	return(Xsquare);
+}
diff --git a/src/cmd/map/libmap/harrison.c b/src/cmd/map/libmap/harrison.c
new file mode 100644
index 0000000..6b6003c
--- /dev/null
+++ b/src/cmd/map/libmap/harrison.c
@@ -0,0 +1,40 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double v3,u2,u3,a,b; /*v=view,p=obj,u=unit.y*/
+
+static int
+Xharrison(struct place *place, double *x, double *y)
+{
+	double p1 = -place->nlat.c*place->wlon.s;
+	double p2 = -place->nlat.c*place->wlon.c;
+	double p3 = place->nlat.s;
+	double d = b + u3*p2 - u2*p3;
+	double t;
+	if(d < .01)
+		return -1;
+	t = a/d;
+	if(v3*place->nlat.s < 1.)
+		return -1;
+	*y = t*p2*u2 + (v3-t*(v3-p3))*u3;
+	*x = t*p1;
+	if(t < 0)
+		return 0;
+	if(*x * *x + *y * *y > 16)
+		return -1;
+	return 1;
+}
+
+proj
+harrison(double r, double alpha)
+{
+	u2 = cos(alpha*RAD);
+	u3 = sin(alpha*RAD);
+	v3 = r;
+	b = r*u2;
+	a = 1 + b;
+	if(r<1.001 || a<sqrt(r*r-1))
+		return 0;
+	return Xharrison;
+}
diff --git a/src/cmd/map/libmap/hex.c b/src/cmd/map/libmap/hex.c
new file mode 100644
index 0000000..851f138
--- /dev/null
+++ b/src/cmd/map/libmap/hex.c
@@ -0,0 +1,122 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+#define BIG 1.e15
+#define HFUZZ .0001
+
+static double hcut[3] ;
+static double kr[3] = { .5, -1., .5 };
+static double ki[3] = { -1., 0., 1. }; 	/*to multiply by sqrt(3)/2*/
+static double cr[3];
+static double ci[3];
+static struct place hem;
+static struct coord twist;
+static double  rootroot3, hkc;
+static double w2;
+static double rootk;
+
+static void
+reflect(int i, double wr, double wi, double *x, double *y)
+{
+	double pr,pi,l;
+	pr = cr[i]-wr;
+	pi = ci[i]-wi;
+	l = 2*(kr[i]*pr + ki[i]*pi);
+	*x = wr + l*kr[i];
+	*y = wi + l*ki[i];
+}
+
+static int
+Xhex(struct place *place, double *x, double *y)
+{
+	int ns;
+	int i;
+	double zr,zi;
+	double sr,si,tr,ti,ur,ui,vr,vi,yr,yi;
+	struct place p;
+	copyplace(place,&p);
+	ns = place->nlat.l >= 0;
+	if(!ns) {
+		p.nlat.l = -p.nlat.l;
+		p.nlat.s = -p.nlat.s;
+	}
+	if(p.nlat.l<HFUZZ) {
+		for(i=0;i<3;i++)
+			if(fabs(reduce(p.wlon.l-hcut[i]))<HFUZZ) {
+				if(i==2) {
+					*x = 2*cr[0] - cr[1];
+					*y = 0;
+				} else {
+					*x = cr[1];
+					*y = 2*ci[2*i];
+				}
+				return(1);
+			}
+		p.nlat.l = HFUZZ;
+		sincos(&p.nlat);
+	}
+	norm(&p,&hem,&twist);
+	Xstereographic(&p,&zr,&zi);
+	zr /= 2;
+	zi /= 2;
+	cdiv(1-zr,-zi,1+zr,zi,&sr,&si);
+	csq(sr,si,&tr,&ti);
+	ccubrt(1+3*tr,3*ti,&ur,&ui);
+	csqrt(ur-1,ui,&vr,&vi);
+	cdiv(rootroot3+vr,vi,rootroot3-vr,-vi,&yr,&yi);
+	yr /= rootk;
+	yi /= rootk;
+	elco2(fabs(yr),yi,hkc,1.,1.,x,y);
+	if(yr < 0)
+		*x = w2 - *x;
+	if(!ns) reflect(hcut[0]>place->wlon.l?0:
+			hcut[1]>=place->wlon.l?1:
+			2,*x,*y,x,y);
+	return(1);
+}
+
+proj
+hex(void)
+{
+	int i;
+	double t;
+	double root3;
+	double c,d;
+	struct place p;
+	hcut[2] = PI;
+	hcut[1] = hcut[2]/3;
+	hcut[0] = -hcut[1];
+	root3 = sqrt(3.);
+	rootroot3 = sqrt(root3);
+	t = 15 -8*root3;
+	hkc = t*(1-sqrt(1-1/(t*t)));
+	elco2(BIG,0.,hkc,1.,1.,&w2,&t);
+	w2 *= 2;
+	rootk = sqrt(hkc);
+	latlon(90.,90.,&hem);
+	latlon(90.,0.,&p);
+	Xhex(&p,&c,&t);
+	latlon(0.,0.,&p);
+	Xhex(&p,&d,&t);
+	for(i=0;i<3;i++) {
+		ki[i] *= root3/2;
+		cr[i] = c + (c-d)*kr[i];
+		ci[i] = (c-d)*ki[i];
+	}
+	deg2rad(0.,&twist);
+	return(Xhex);
+}
+
+int
+hexcut(struct place *g, struct place *og, double *cutlon)
+{
+	int t,i;
+	if(g->nlat.l>=-HFUZZ&&og->nlat.l>=-HFUZZ)
+		return(1);
+	for(i=0;i<3;i++) {
+		t = ckcut(g,og,*cutlon=hcut[i]);
+		if(t!=1) return(t);
+	}
+	return(1);
+}
diff --git a/src/cmd/map/libmap/homing.c b/src/cmd/map/libmap/homing.c
new file mode 100644
index 0000000..366f69f
--- /dev/null
+++ b/src/cmd/map/libmap/homing.c
@@ -0,0 +1,121 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord p0;		/* standard parallel */
+
+int first;
+
+static double
+trigclamp(double x)
+{
+	return x>1? 1: x<-1? -1: x;
+}
+
+static struct coord az;		/* azimuth of p0 seen from place */
+static struct coord rad;	/* angular dist from place to p0 */
+
+static int
+azimuth(struct place *place)
+{
+	if(place->nlat.c < FUZZ) {
+		az.l = PI/2 + place->nlat.l - place->wlon.l;
+		sincos(&az);
+		rad.l = fabs(place->nlat.l - p0.l);
+		if(rad.l > PI)
+			rad.l = 2*PI - rad.l;
+		sincos(&rad);
+		return 1;
+	}
+	rad.c = trigclamp(p0.s*place->nlat.s +	/* law of cosines */
+		p0.c*place->nlat.c*place->wlon.c);
+	rad.s = sqrt(1 - rad.c*rad.c);
+	if(fabs(rad.s) < .001) {
+		az.s = 0;
+		az.c = 1;
+	} else {
+		az.s = trigclamp(p0.c*place->wlon.s/rad.s); /* sines */
+		az.c = trigclamp((p0.s - rad.c*place->nlat.s)
+				/(rad.s*place->nlat.c));
+	}
+	rad.l = atan2(rad.s, rad.c);
+	return 1;
+}
+
+static int
+Xmecca(struct place *place, double *x, double *y)
+{
+	if(!azimuth(place))
+		return 0;
+	*x = -place->wlon.l;
+	*y = fabs(az.s)<.02? -az.c*rad.s/p0.c: *x*az.c/az.s;
+	return fabs(*y)>2? -1:
+	       rad.c<0? 0:
+	       1;
+}
+
+proj
+mecca(double par)
+{
+	first = 1;
+	if(fabs(par)>80.)
+		return(0);
+	deg2rad(par,&p0);
+	return(Xmecca);
+}
+
+static int
+Xhoming(struct place *place, double *x, double *y)
+{
+	if(!azimuth(place))
+		return 0;
+	*x = -rad.l*az.s;
+	*y = -rad.l*az.c;
+	return place->wlon.c<0? 0: 1;
+}
+
+proj
+homing(double par)
+{
+	first = 1;
+	if(fabs(par)>80.)
+		return(0);
+	deg2rad(par,&p0);
+	return(Xhoming);
+}
+
+int
+hlimb(double *lat, double *lon, double res)
+{
+	if(first) {
+		*lon = -90;
+		*lat = -90;
+		first = 0;
+		return 0;
+	}
+	*lat += res;
+	if(*lat <= 90) 
+		return 1;
+	if(*lon == 90)
+		return -1;
+	*lon = 90;
+	*lat = -90;
+	return 0;
+}
+
+int
+mlimb(double *lat, double *lon, double res)
+{
+	int ret = !first;
+	if(fabs(p0.s) < .01)
+		return -1;
+	if(first) {
+		*lon = -180;
+		first = 0;
+	} else
+		*lon += res;
+	if(*lon > 180)
+		return -1;
+	*lat = atan(-cos(*lon*RAD)/p0.s*p0.c)/RAD;
+	return ret;
+}
diff --git a/src/cmd/map/libmap/lagrange.c b/src/cmd/map/libmap/lagrange.c
new file mode 100644
index 0000000..02dd29e
--- /dev/null
+++ b/src/cmd/map/libmap/lagrange.c
@@ -0,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static int
+Xlagrange(struct place *place, double *x, double *y)
+{
+	double z1,z2;
+	double w1,w2,t1,t2;
+	struct place p;
+	copyplace(place,&p);
+	if(place->nlat.l<0) {
+		p.nlat.l = -p.nlat.l;
+		p.nlat.s = -p.nlat.s;
+	}
+	Xstereographic(&p,&z1,&z2);
+	csqrt(-z2/2,z1/2,&w1,&w2);
+	cdiv(w1-1,w2,w1+1,w2,&t1,&t2);
+	*y = -t1;
+	*x = t2;
+	if(place->nlat.l<0)
+		*y = -*y;
+	return(1);
+}
+
+proj
+lagrange(void)
+{
+	return(Xlagrange);
+}
diff --git a/src/cmd/map/libmap/lambert.c b/src/cmd/map/libmap/lambert.c
new file mode 100644
index 0000000..6b688aa
--- /dev/null
+++ b/src/cmd/map/libmap/lambert.c
@@ -0,0 +1,46 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord stdp0, stdp1;
+static double k;
+
+static int
+Xlambert(struct place *place, double *x, double *y)
+{
+	double r;
+	if(place->nlat.l < -80.*RAD)
+		return(-1);
+	if(place->nlat.l > 89.*RAD)
+		r = 0;	/* slovenly */
+	else
+		r = stdp0.c*exp(0.5*k*log(
+		   (1+stdp0.s)*(1-place->nlat.s)/((1-stdp0.s)*(1+place->nlat.s))));
+	if(stdp1.l<0.)
+		r = -r;
+	*x = - r*sin(k * place->wlon.l);
+	*y = - r*cos(k * place->wlon.l);
+	return(1);
+}
+
+proj
+lambert(double par0, double par1)
+{
+	double temp;
+	if(fabs(par0)>fabs(par1)){
+		temp = par0;
+		par0 = par1;
+		par1 = temp;
+	}
+	deg2rad(par0, &stdp0);
+	deg2rad(par1, &stdp1);
+	if(fabs(par1+par0)<.1) 
+		return(mercator());
+	if(fabs(par1-par0)<.1)
+		return(perspective(-1.));
+	if(fabs(par0)>89.5||fabs(par1)>89.5)
+		return(0);
+	k = 2*log(stdp1.c/stdp0.c)/log(
+		(1+stdp0.s)*(1-stdp1.s)/((1-stdp0.s)*(1+stdp1.s)));
+	return(Xlambert);
+}
diff --git a/src/cmd/map/libmap/laue.c b/src/cmd/map/libmap/laue.c
new file mode 100644
index 0000000..06c5f3a
--- /dev/null
+++ b/src/cmd/map/libmap/laue.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+
+static int
+Xlaue(struct place *place, double *x, double *y)
+{
+	double r;
+	if(place->nlat.l<PI/4+FUZZ)
+		return(-1);
+	r = tan(PI-2*place->nlat.l);
+	if(r>3)
+		return(-1);
+	*x = - r * place->wlon.s;
+	*y = - r * place->wlon.c;
+	return(1);
+}
+
+proj
+laue(void)
+{
+	return(Xlaue);
+}
diff --git a/src/cmd/map/libmap/lune.c b/src/cmd/map/libmap/lune.c
new file mode 100644
index 0000000..dc58c6f
--- /dev/null
+++ b/src/cmd/map/libmap/lune.c
@@ -0,0 +1,62 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int Xstereographic(struct place *place, double *x, double *y);
+
+static struct place eastpole;
+static struct place westpole;
+static double eastx, easty;
+static double westx, westy;
+static double scale;
+static double pwr;
+
+/* conformal map w = ((1+z)^A - (1-z)^A)/((1+z)^A + (1-z)^A),
+   where A<1, maps unit circle onto a convex lune with x= +-1
+   mapping to vertices of angle A*PI at w = +-1 */
+
+/* there are cuts from E and W poles to S pole,
+   in absence of a cut routine, error is returned for
+   points outside a polar cap through E and W poles */
+
+static int Xlune(struct place *place, double *x, double *y)
+{
+	double stereox, stereoy;
+	double z1x, z1y, z2x, z2y;
+	double w1x, w1y, w2x, w2y;
+	double numx, numy, denx, deny;
+	if(place->nlat.l < eastpole.nlat.l-FUZZ)
+		return	-1;
+	Xstereographic(place, &stereox, &stereoy);
+	stereox *= scale;
+	stereoy *= scale;
+	z1x = 1 + stereox;
+	z1y = stereoy;
+	z2x = 1 - stereox;
+	z2y = -stereoy;
+	cpow(z1x,z1y,&w1x,&w1y,pwr);
+	cpow(z2x,z2y,&w2x,&w2y,pwr);
+	numx = w1x - w2x;
+	numy = w1y - w2y;
+	denx = w1x + w2x;
+	deny = w1y + w2y;
+	cdiv(numx, numy, denx, deny, x, y);
+	return 1;
+}	
+
+proj
+lune(double lat, double theta)
+{
+	deg2rad(lat, &eastpole.nlat);
+	deg2rad(-90.,&eastpole.wlon);
+	deg2rad(lat, &westpole.nlat);
+	deg2rad(90. ,&westpole.wlon);
+	Xstereographic(&eastpole, &eastx, &easty);
+	Xstereographic(&westpole, &westx, &westy);
+	if(fabs(easty)>FUZZ || fabs(westy)>FUZZ ||
+	   fabs(eastx+westx)>FUZZ)
+		abort();
+	scale = 1/eastx;
+	pwr = theta/180;
+	return Xlune;
+}
diff --git a/src/cmd/map/libmap/mercator.c b/src/cmd/map/libmap/mercator.c
new file mode 100644
index 0000000..0ab6365
--- /dev/null
+++ b/src/cmd/map/libmap/mercator.c
@@ -0,0 +1,36 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static int
+Xmercator(struct place *place, double *x, double *y)
+{
+	if(fabs(place->nlat.l) > 80.*RAD)
+		return(-1);
+	*x = -place->wlon.l;
+	*y = 0.5*log((1+place->nlat.s)/(1-place->nlat.s));
+	return(1);
+}
+
+proj
+mercator(void)
+{
+	return(Xmercator);
+}
+
+static double ecc = ECC;
+
+static int
+Xspmercator(struct place *place, double *x, double *y)
+{
+	if(Xmercator(place,x,y) < 0)
+		return(-1);
+	*y += 0.5*ecc*log((1-ecc*place->nlat.s)/(1+ecc*place->nlat.s));
+	return(1);
+}
+
+proj
+sp_mercator(void)
+{
+	return(Xspmercator);
+}
diff --git a/src/cmd/map/libmap/mkfile b/src/cmd/map/libmap/mkfile
new file mode 100644
index 0000000..e903124
--- /dev/null
+++ b/src/cmd/map/libmap/mkfile
@@ -0,0 +1,50 @@
+<$PLAN9/src/mkhdr
+
+LIB=libmap.a
+OFILES=aitoff.$O\
+	albers.$O\
+	azequalarea.$O\
+	azequidist.$O\
+	bicentric.$O\
+	bonne.$O\
+	ccubrt.$O\
+	complex.$O\
+	conic.$O\
+	cubrt.$O\
+	cylequalarea.$O\
+	cylindrical.$O\
+	elco2.$O\
+	elliptic.$O\
+	fisheye.$O\
+	gall.$O\
+	gilbert.$O\
+	guyou.$O\
+	harrison.$O\
+	hex.$O\
+	homing.$O\
+	lagrange.$O\
+	lambert.$O\
+	laue.$O\
+	lune.$O\
+	mercator.$O\
+	mollweide.$O\
+	newyorker.$O\
+	orthographic.$O\
+	perspective.$O\
+	polyconic.$O\
+	rectangular.$O\
+	simpleconic.$O\
+	sinusoidal.$O\
+	tetra.$O\
+	trapezoidal.$O\
+	twocirc.$O\
+	zcoord.$O\
+
+HFILES=../map.h\
+
+<$PLAN9/src/mklib
+CFLAGS=$CFLAGS -I..
+
+nuke:V:
+	mk clean
+	rm -f libmap.a[$OS]
diff --git a/src/cmd/map/libmap/mollweide.c b/src/cmd/map/libmap/mollweide.c
new file mode 100644
index 0000000..3284c49
--- /dev/null
+++ b/src/cmd/map/libmap/mollweide.c
@@ -0,0 +1,25 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static int
+Xmollweide(struct place *place, double *x, double *y)
+{
+	double z;
+	double w;
+	z = place->nlat.l;
+	if(fabs(z)<89.9*RAD)
+		do {	/*newton for 2z+sin2z=pi*sin(lat)*/
+			w = (2*z+sin(2*z)-PI*place->nlat.s)/(2+2*cos(2*z));
+			z -= w;
+		} while(fabs(w)>=.00001);
+	*y = sin(z);
+	*x = - (2/PI)*cos(z)*place->wlon.l;
+	return(1);
+}
+
+proj
+mollweide(void)
+{
+	return(Xmollweide);
+}
diff --git a/src/cmd/map/libmap/newyorker.c b/src/cmd/map/libmap/newyorker.c
new file mode 100644
index 0000000..370e3b3
--- /dev/null
+++ b/src/cmd/map/libmap/newyorker.c
@@ -0,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double a;
+
+static int
+Xnewyorker(struct place *place, double *x, double *y)
+{
+	double r = PI/2 - place->nlat.l;
+	double s;
+	if(r<.001)	/* cheat to plot center */
+		s = 0;
+	else if(r<a)
+		return -1;
+	else
+		s = log(r/a);
+	*x = -s * place->wlon.s;
+	*y = -s * place->wlon.c;
+	return(1);
+}
+
+proj
+newyorker(double a0)
+{
+	a = a0*RAD;
+	return(Xnewyorker);
+}
diff --git a/src/cmd/map/libmap/orthographic.c b/src/cmd/map/libmap/orthographic.c
new file mode 100644
index 0000000..7aac5b1
--- /dev/null
+++ b/src/cmd/map/libmap/orthographic.c
@@ -0,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+
+int
+Xorthographic(struct place *place, double *x, double *y)
+{
+	*x = - place->nlat.c * place->wlon.s;
+	*y = - place->nlat.c * place->wlon.c;
+	return(place->nlat.l<0.? 0 : 1);
+}
+
+proj
+orthographic(void)
+{
+	return(Xorthographic);
+}
+
+int
+olimb(double *lat, double *lon, double res)
+{
+	static int first  = 1;
+	if(first) {
+		*lat = 0;
+		*lon = -180;
+		first = 0;
+		return 0;
+	}
+	*lon += res;
+	if(*lon <= 180)
+		return 1;
+	first = 1;
+	return -1;
+}
diff --git a/src/cmd/map/libmap/perspective.c b/src/cmd/map/libmap/perspective.c
new file mode 100644
index 0000000..7ce6b0d
--- /dev/null
+++ b/src/cmd/map/libmap/perspective.c
@@ -0,0 +1,84 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+#define ORTHRAD 1000
+static double viewpt;
+
+static int
+Xperspective(struct place *place, double *x, double *y)
+{
+	double r;
+	if(viewpt<=1+FUZZ && fabs(place->nlat.s<=viewpt+.01))
+		return(-1);
+	r = place->nlat.c*(viewpt - 1.)/(viewpt - place->nlat.s);
+	*x = - r*place->wlon.s;
+	*y = - r*place->wlon.c;
+	if(r>4.)
+		return(-1);
+	if(fabs(viewpt)>1 && place->nlat.s<1/viewpt ||
+	   fabs(viewpt)<=1 && place->nlat.s<viewpt)
+			return 0;
+	return(1);
+}
+
+proj
+perspective(double radius)
+{
+	viewpt = radius;
+	if(viewpt >= ORTHRAD)
+		return(Xorthographic);
+	if(fabs(viewpt-1.)<.0001)
+		return(0);
+	return(Xperspective);
+}
+
+	/* called from various conformal projections,
+           but not from stereographic itself */
+int
+Xstereographic(struct place *place, double *x, double *y)
+{
+	double v = viewpt;
+	int retval;
+	viewpt = -1;
+	retval = Xperspective(place, x, y);
+	viewpt = v;
+	return retval;
+}
+
+proj
+stereographic(void)
+{
+	viewpt = -1.;
+	return(Xperspective);
+}
+
+proj
+gnomonic(void)
+{
+	viewpt = 0.;
+	return(Xperspective);
+}
+
+int
+plimb(double *lat, double *lon, double res)
+{
+	static int first = 1;
+	if(viewpt >= ORTHRAD)
+		return olimb(lat, lon, res);
+	if(first) {
+		first = 0;
+		*lon = -180;
+		if(fabs(viewpt) < .01)
+			*lat = 0;
+		else if(fabs(viewpt)<=1)
+			*lat = asin(viewpt)/RAD;
+		else
+			*lat = asin(1/viewpt)/RAD;
+	} else
+		*lon += res;
+	if(*lon <= 180)
+		return 1;
+	first = 1;
+	return -1;
+}
diff --git a/src/cmd/map/libmap/polyconic.c b/src/cmd/map/libmap/polyconic.c
new file mode 100644
index 0000000..a07c97e
--- /dev/null
+++ b/src/cmd/map/libmap/polyconic.c
@@ -0,0 +1,28 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xpolyconic(struct place *place, double *x, double *y)
+{
+	double r, alpha;
+	double lat2, lon2;
+	if(fabs(place->nlat.l) > .01) {
+		r = place->nlat.c / place->nlat.s;
+		alpha = place->wlon.l * place->nlat.s;
+		*y = place->nlat.l + r*(1 - cos(alpha));
+		*x = - r*sin(alpha);
+	} else {
+		lon2 = place->wlon.l * place->wlon.l;
+		lat2 = place->nlat.l * place->nlat.l;
+		*y = place->nlat.l * (1+(lon2/2)*(1-(8+lon2)*lat2/12));
+		*x = - place->wlon.l * (1-lat2*(3+lon2)/6);
+	}
+	return(1);
+}
+
+proj
+polyconic(void)
+{
+	return(Xpolyconic);
+}
diff --git a/src/cmd/map/libmap/rectangular.c b/src/cmd/map/libmap/rectangular.c
new file mode 100644
index 0000000..d4a86c9
--- /dev/null
+++ b/src/cmd/map/libmap/rectangular.c
@@ -0,0 +1,22 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double scale;
+
+static int
+Xrectangular(struct place *place, double *x, double *y)
+{
+	*x = -scale*place->wlon.l;
+	*y = place->nlat.l;
+	return(1);
+}
+
+proj
+rectangular(double par)
+{
+	scale = cos(par*RAD);
+	if(scale<.1)
+		return 0;
+	return(Xrectangular);
+}
diff --git a/src/cmd/map/libmap/simpleconic.c b/src/cmd/map/libmap/simpleconic.c
new file mode 100644
index 0000000..1ed1d1a
--- /dev/null
+++ b/src/cmd/map/libmap/simpleconic.c
@@ -0,0 +1,34 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double r0, a;
+
+static int
+Xsimpleconic(struct place *place, double *x, double *y)
+{
+	double r = r0 - place->nlat.l;
+	double t = a*place->wlon.l;
+	*x = -r*sin(t);
+	*y = -r*cos(t);
+	return 1;
+}
+
+proj
+simpleconic(double par0, double par1)
+{
+	struct coord lat0;
+	struct coord lat1;
+	deg2rad(par0,&lat0);
+	deg2rad(par1,&lat1);
+	if(fabs(lat0.l+lat1.l)<.01)
+		return rectangular(par0);
+	if(fabs(lat0.l-lat1.l)<.01) {
+		a = lat0.s/lat0.l;
+		r0 = lat0.c/lat0.s + lat0.l;
+	} else {
+		a = (lat1.c-lat0.c)/(lat0.l-lat1.l);
+		r0 = ((lat0.c+lat1.c)/a + lat1.l + lat0.l)/2;
+	}
+	return Xsimpleconic;
+}
diff --git a/src/cmd/map/libmap/sinusoidal.c b/src/cmd/map/libmap/sinusoidal.c
new file mode 100644
index 0000000..6a79706
--- /dev/null
+++ b/src/cmd/map/libmap/sinusoidal.c
@@ -0,0 +1,17 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+int
+Xsinusoidal(struct place *place, double *x, double *y)
+{
+	*x = - place->wlon.l * place->nlat.c;
+	*y = place->nlat.l;
+	return(1);
+}
+
+proj
+sinusoidal(void)
+{
+	return(Xsinusoidal);
+}
diff --git a/src/cmd/map/libmap/tetra.c b/src/cmd/map/libmap/tetra.c
new file mode 100644
index 0000000..6bdef49
--- /dev/null
+++ b/src/cmd/map/libmap/tetra.c
@@ -0,0 +1,206 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+/*
+ *	conformal map of earth onto tetrahedron
+ *	the stages of mapping are
+ *	(a) stereo projection of tetrahedral face onto
+ *	    isosceles curvilinear triangle with 3 120-degree
+ *	    angles and one straight side
+ *	(b) map of this triangle onto half plane cut along
+ *	    3 rays from the roots of unity to infinity
+ *		formula (z^4+2*3^.5*z^2-1)/(z^4-2*3^.5*z^2-1)
+ *	(c) do 3 times for  each sector of plane:
+ *	    map of |arg z|<=pi/6, cut along z>1 into
+ *	    triangle |arg z|<=pi/6, Re z<=const,
+ *	    with upper side of cut going into upper half of
+ *	    of vertical side of triangle and lowere into lower
+ *		formula int from 0 to z dz/sqrt(1-z^3)
+ *
+ *	int from u to 1 3^.25*du/sqrt(1-u^3) =
+		F(acos((rt3-1+u)/(rt3+1-u)),sqrt(1/2+rt3/4))
+ *	int from 1 to u 3^.25*du/sqrt(u^3-1) =
+ *		F(acos((rt3+1-u)/(rt3-1+u)),sqrt(1/2-rt3/4))
+ *	this latter formula extends analytically down to
+ *	u=0 and is the basis of this routine, with the
+ *	argument of complex elliptic integral elco2
+ *	being tan(acos...)
+ *	the formula F(pi-x,k) = 2*F(pi/2,k)-F(x,k) is
+ *	used to cross over into the region where Re(acos...)>pi/2
+ *		f0 and fpi are suitably scaled complete integrals
+*/
+
+#define TFUZZ 0.00001
+
+static struct place tpole[4];	/* point of tangency of tetrahedron face*/
+static double tpoleinit[4][2] = {
+	1.,	0.,
+	1.,	180.,
+	-1.,	90.,
+	-1.,	-90.
+};
+static struct tproj {
+	double tlat,tlon;	/* center of stereo projection*/
+	double ttwist;		/* rotatn before stereo*/
+	double trot;		/*rotate after projection*/
+	struct place projpl;	/*same as tlat,tlon*/
+	struct coord projtw;	/*same as ttwist*/
+	struct coord postrot;	/*same as trot*/
+} tproj[4][4] = {
+{/*00*/	{0.},
+ /*01*/	{90.,	0.,	90.,	-90.},
+ /*02*/	{0.,	45.,	-45.,	150.},
+ /*03*/	{0.,	-45.,	-135.,	30.}
+},
+{/*10*/	{90.,	0.,	-90.,	90.},
+ /*11*/ {0.},
+ /*12*/ {0.,	135.,	-135.,	-150.},
+ /*13*/	{0.,	-135.,	-45.,	-30.}
+},
+{/*20*/	{0.,	45.,	135.,	-30.},
+ /*21*/	{0.,	135.,	45.,	-150.},
+ /*22*/	{0.},
+ /*23*/	{-90.,	0.,	180.,	90.}
+},
+{/*30*/	{0.,	-45.,	45.,	-150.},
+ /*31*/ {0.,	-135.,	135.,	-30.},
+ /*32*/	{-90.,	0.,	0.,	90.},
+ /*33*/ {0.} 
+}};
+static double tx[4] = {	/*where to move facet after final rotation*/
+	0.,	0.,	-1.,	1.	/*-1,1 to be sqrt(3)*/
+};
+static double ty[4] = {
+	0.,	2.,	-1.,	-1.
+};
+static double root3;
+static double rt3inv;
+static double two_rt3;
+static double tkc,tk,tcon;
+static double f0r,f0i,fpir,fpii;
+
+static void
+twhichp(struct place *g, int *p, int *q)
+{
+	int i,j,k;
+	double cosdist[4];
+	struct place *tp;
+	for(i=0;i<4;i++) {
+		tp = &tpole[i];
+		cosdist[i] = g->nlat.s*tp->nlat.s +
+			  g->nlat.c*tp->nlat.c*(
+			  g->wlon.s*tp->wlon.s +
+			  g->wlon.c*tp->wlon.c);
+	}
+	j = 0;
+	for(i=1;i<4;i++)
+		if(cosdist[i] > cosdist[j])
+			j = i;
+	*p = j;
+	k = j==0?1:0;
+	for(i=0;i<4;i++)
+		if(i!=j&&cosdist[i]>cosdist[k])
+			k = i;
+	*q = k;
+}
+
+int
+Xtetra(struct place *place, double *x, double *y)
+{
+	int i,j;
+	struct place pl;
+	register struct tproj *tpp;
+	double vr, vi;
+	double br, bi;
+	double zr,zi,z2r,z2i,z4r,z4i,sr,si,tr,ti;
+	twhichp(place,&i,&j);
+	copyplace(place,&pl);
+	norm(&pl,&tproj[i][j].projpl,&tproj[i][j].projtw);
+	Xstereographic(&pl,&vr,&vi);
+	zr = vr/2;
+	zi = vi/2;
+	if(zr<=TFUZZ)
+		zr = TFUZZ;
+	csq(zr,zi,&z2r,&z2i);
+	csq(z2r,z2i,&z4r,&z4i);
+	z2r *= two_rt3;
+	z2i *= two_rt3;
+	cdiv(z4r+z2r-1,z4i+z2i,z4r-z2r-1,z4i-z2i,&sr,&si);
+	csqrt(sr-1,si,&tr,&ti);
+	cdiv(tcon*tr,tcon*ti,root3+1-sr,-si,&br,&bi);
+	if(br<0) {
+		br = -br;
+		bi = -bi;
+		if(!elco2(br,bi,tk,1.,1.,&vr,&vi))
+			return 0;
+		vr = fpir - vr;
+		vi = fpii - vi;
+	} else 
+		if(!elco2(br,bi,tk,1.,1.,&vr,&vi))
+			return 0;
+	if(si>=0) {
+		tr = f0r - vi;
+		ti = f0i + vr;
+	} else {
+		tr = f0r + vi;
+		ti = f0i - vr;
+	}
+	tpp = &tproj[i][j];
+	*x = tr*tpp->postrot.c +
+	     ti*tpp->postrot.s + tx[i];
+	*y = ti*tpp->postrot.c -
+	     tr*tpp->postrot.s + ty[i];
+	return(1);
+}
+
+int
+tetracut(struct place *g, struct place *og, double *cutlon)
+{
+	int i,j,k;
+	if((g->nlat.s<=-rt3inv&&og->nlat.s<=-rt3inv) && 
+	   (ckcut(g,og,*cutlon=0.)==2||ckcut(g,og,*cutlon=PI)==2))
+		return(2);
+	twhichp(g,&i,&k);
+	twhichp(og,&j,&k);
+	if(i==j||i==0||j==0)
+		return(1);
+	return(0);
+}
+
+proj
+tetra(void)
+{
+	int i;
+	int j;
+	register struct place *tp;
+	register struct tproj *tpp;
+	double t;
+	root3 = sqrt(3.);
+	rt3inv = 1/root3;
+	two_rt3 = 2*root3;
+	tkc = sqrt(.5-.25*root3);
+	tk = sqrt(.5+.25*root3);
+	tcon = 2*sqrt(root3);
+	elco2(tcon/(root3-1),0.,tkc,1.,1.,&f0r,&f0i);
+	elco2(1.e15,0.,tk,1.,1.,&fpir,&fpii);
+	fpir *= 2;
+	fpii *= 2;
+	for(i=0;i<4;i++) {
+		tx[i] *= f0r*root3;
+		ty[i] *= f0r;
+		tp = &tpole[i];
+		t = tp->nlat.s = tpoleinit[i][0]/root3;
+		tp->nlat.c = sqrt(1 - t*t);
+		tp->nlat.l = atan2(tp->nlat.s,tp->nlat.c);
+		deg2rad(tpoleinit[i][1],&tp->wlon);
+		for(j=0;j<4;j++) {
+			tpp = &tproj[i][j];
+			latlon(tpp->tlat,tpp->tlon,&tpp->projpl);
+			deg2rad(tpp->ttwist,&tpp->projtw);
+			deg2rad(tpp->trot,&tpp->postrot);
+		}
+	}
+	return(Xtetra);
+}
+
diff --git a/src/cmd/map/libmap/trapezoidal.c b/src/cmd/map/libmap/trapezoidal.c
new file mode 100644
index 0000000..977cf60
--- /dev/null
+++ b/src/cmd/map/libmap/trapezoidal.c
@@ -0,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static struct coord stdpar0, stdpar1;
+static double k;
+static double yeq;
+
+static int
+Xtrapezoidal(struct place *place, double *x, double *y)
+{
+	*y = yeq + place->nlat.l;
+	*x = *y*k*place->wlon.l;
+	return 1;
+}
+
+proj
+trapezoidal(double par0, double par1)
+{
+	if(fabs(fabs(par0)-fabs(par1))<.1)
+		return rectangular(par0);
+	deg2rad(par0,&stdpar0);
+	deg2rad(par1,&stdpar1);
+	if(fabs(par1-par0) < .1)
+		k = stdpar1.s;
+	else
+		k = (stdpar1.c-stdpar0.c)/(stdpar0.l-stdpar1.l);
+	yeq = -stdpar1.l - stdpar1.c/k;
+	return Xtrapezoidal;
+}
diff --git a/src/cmd/map/libmap/twocirc.c b/src/cmd/map/libmap/twocirc.c
new file mode 100644
index 0000000..0d0e48a
--- /dev/null
+++ b/src/cmd/map/libmap/twocirc.c
@@ -0,0 +1,80 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+static double
+quadratic(double a, double b, double c)
+{
+	double disc = b*b - 4*a*c;
+	return disc<0? 0: (-b-sqrt(disc))/(2*a);
+}
+
+/* for projections with meridians being circles centered
+on the x axis and parallels being circles centered on the y
+axis.  Find the intersection of the meridian thru (m,0), (90,0),
+with the parallel thru (0,p), (p1,p2) */
+
+static int
+twocircles(double m, double p, double p1, double p2, double *x, double *y)
+{
+	double a;	/* center of meridian circle, a>0 */
+	double b;	/* center of parallel circle, b>0 */
+	double t,bb;
+	if(m > 0) {
+		twocircles(-m,p,p1,p2,x,y);
+		*x = -*x;
+	} else if(p < 0) {
+		twocircles(m,-p,p1,-p2,x,y);
+		*y = -*y;
+	} else if(p < .01) {
+		*x = m;
+		t = m/p1;
+		*y = p + (p2-p)*t*t;
+	} else if(m > -.01) {
+		*y = p;
+		*x = m - m*p*p;
+	} else {
+		b = p>=1? 1: p>.99? 0.5*(p+1 + p1*p1/(1-p)):
+			0.5*(p*p-p1*p1-p2*p2)/(p-p2);
+		a = .5*(m - 1/m);
+		t = m*m-p*p+2*(b*p-a*m);
+		bb = b*b;
+		*x = quadratic(1+a*a/bb, -2*a + a*t/bb,
+			t*t/(4*bb) - m*m + 2*a*m);
+		*y = (*x*a+t/2)/b;
+	}
+	return 1;
+}		
+
+static int
+Xglobular(struct place *place, double *x, double *y)
+{
+	twocircles(-2*place->wlon.l/PI,
+		2*place->nlat.l/PI, place->nlat.c, place->nlat.s, x, y);
+	return 1;
+}	
+
+proj
+globular(void)
+{
+	return Xglobular;
+}
+
+static int
+Xvandergrinten(struct place *place, double *x, double *y)
+{
+	double t = 2*place->nlat.l/PI;
+	double abst = fabs(t);
+	double pval = abst>=1? 1: abst/(1+sqrt(1-t*t));
+	double p2 = 2*pval/(1+pval);
+	twocircles(-place->wlon.l/PI, pval, sqrt(1-p2*p2), p2, x, y);
+	if(t < 0) 
+		*y = -*y;
+	return 1;
+}
+
+proj
+vandergrinten(void)
+{
+	return Xvandergrinten;
+}
diff --git a/src/cmd/map/libmap/zcoord.c b/src/cmd/map/libmap/zcoord.c
new file mode 100644
index 0000000..7c3d3ad
--- /dev/null
+++ b/src/cmd/map/libmap/zcoord.c
@@ -0,0 +1,143 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "map.h"
+
+static double cirmod(double);
+
+static struct place pole;	/* map pole is tilted to here */
+static struct coord twist;	/* then twisted this much */
+static struct place ipole;	/* inverse transfrom */
+static struct coord itwist;
+
+void
+orient(double lat, double lon, double theta)
+{
+	lat = cirmod(lat);
+	if(lat>90.) {
+		lat = 180. - lat;
+		lon -= 180.;
+		theta -= 180.;
+	} else if(lat < -90.) {
+		lat = -180. - lat;
+		lon -= 180.;
+		theta -= 180;
+	}
+	latlon(lat,lon,&pole);
+	deg2rad(theta, &twist);
+	latlon(lat,180.-theta,&ipole);
+	deg2rad(180.-lon, &itwist);
+}
+
+void
+latlon(double lat, double lon, struct place *p)
+{
+	lat = cirmod(lat);
+	if(lat>90.) {
+		lat = 180. - lat;
+		lon -= 180.;
+	} else if(lat < -90.) {
+		lat = -180. - lat;
+		lon -= 180.;
+	}
+	deg2rad(lat,&p->nlat);
+	deg2rad(lon,&p->wlon);
+}
+
+void
+deg2rad(double theta, struct coord *coord)
+{
+	theta = cirmod(theta);
+	coord->l = theta*RAD;
+	if(theta==90) {
+		coord->s = 1;
+		coord->c = 0;
+	} else if(theta== -90) {
+		coord->s = -1;
+		coord->c = 0;
+	} else
+		sincos(coord);
+}
+
+static double
+cirmod(double theta)
+{
+	while(theta >= 180.)
+		theta -= 360;
+	while(theta<-180.)
+		theta += 360.;
+	return(theta);
+}
+
+void
+sincos(struct coord *coord)
+{
+	coord->s = sin(coord->l);
+	coord->c = cos(coord->l);
+}
+
+void
+normalize(struct place *gg)
+{
+	norm(gg,&pole,&twist);
+}
+
+void
+invert(struct place *g)
+{
+	norm(g,&ipole,&itwist);
+}
+
+void
+norm(struct place *gg, struct place *pp, struct coord *tw)
+{
+	register struct place *g;	/*geographic coords */
+	register struct place *p;	/* new pole in old coords*/
+	struct place m;			/* standard map coords*/
+	g = gg;
+	p = pp;
+	if(p->nlat.s == 1.) {
+		if(p->wlon.l+tw->l == 0.)
+			return;
+		g->wlon.l -= p->wlon.l+tw->l;
+	} else {
+		if(p->wlon.l != 0) {
+			g->wlon.l -= p->wlon.l;
+			sincos(&g->wlon);
+		}
+		m.nlat.s = p->nlat.s * g->nlat.s
+			+ p->nlat.c * g->nlat.c * g->wlon.c;
+		m.nlat.c = sqrt(1. - m.nlat.s * m.nlat.s);
+		m.nlat.l = atan2(m.nlat.s, m.nlat.c);
+		m.wlon.s = g->nlat.c * g->wlon.s;
+		m.wlon.c = p->nlat.c * g->nlat.s
+			- p->nlat.s * g->nlat.c * g->wlon.c;
+		m.wlon.l = atan2(m.wlon.s, - m.wlon.c)
+			- tw->l;
+		*g = m;
+	}
+	sincos(&g->wlon);
+	if(g->wlon.l>PI)
+		g->wlon.l -= 2*PI;
+	else if(g->wlon.l<-PI)
+		g->wlon.l += 2*PI;
+}
+
+double
+tan(double x)
+{
+	return(sin(x)/cos(x));
+}
+
+void
+printp(struct place *g)
+{
+printf("%.3f %.3f %.3f %.3f %.3f %.3f\n",
+g->nlat.l,g->nlat.s,g->nlat.c,g->wlon.l,g->wlon.s,g->wlon.c);
+}
+
+void
+copyplace(struct place *g1, struct place *g2)
+{
+	*g2 = *g1;
+}
diff --git a/src/cmd/map/map.c b/src/cmd/map/map.c
new file mode 100644
index 0000000..47109d4
--- /dev/null
+++ b/src/cmd/map/map.c
@@ -0,0 +1,1226 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "map.h"
+#include "iplot.h"
+
+#define NVERT 20	/* max number of vertices in a -v polygon */
+#define HALFWIDTH 8192	/* output scaled to fit in -HALFWIDTH,HALFWIDTH */
+#define LONGLINES (HALFWIDTH*4)	/* permissible segment lengths */
+#define SHORTLINES (HALFWIDTH/8)
+#define SCALERATIO 10	/* of abs to rel data (see map(5)) */
+#define RESOL 2.	/* coarsest resolution for tracing grid (degrees) */
+#define TWO_THRD 0.66666666666666667
+
+int normproj(double, double, double *, double *);
+int posproj(double, double, double *, double *);
+int picut(struct place *, struct place *, double *);
+double reduce(double);
+short getshort(FILE *);
+char *mapindex(char *);
+proj projection;
+
+
+static char *mapdir = "/lib/map";	/* default map directory */
+struct file {
+	char *name;
+	char *color;
+	char *style;
+};
+static struct file dfltfile = {
+	"world", BLACK, SOLID	/* default map */
+};
+static struct file *file = &dfltfile;	/* list of map files */
+static int nfile = 1;			/* length of list */
+static char *currcolor = BLACK;		/* current color */
+static char *gridcolor = BLACK;
+static char *bordcolor = BLACK;
+
+extern struct index index[];
+int halfwidth = HALFWIDTH;
+
+static int (*cut)(struct place *, struct place *, double *);
+static int (*limb)(double*, double*, double);
+static void dolimb(void);
+static int onlimb;
+static int poles;
+static double orientation[3] = { 90., 0., 0. };	/* -o option */
+static int oriented;	/* nonzero if -o option occurred */
+static int upright;		/* 1 if orientation[0]==90, -1 if -90, else 0*/
+static int delta = 1;	/* -d setting */
+static double limits[4] = {	/* -l parameters */
+	-90., 90., -180., 180.
+};
+static double klimits[4] = {	/* -k parameters */
+	-90., 90., -180., 180.
+};
+static int limcase;
+static double rlimits[4];	/* limits expressed in radians */
+static double lolat, hilat, lolon, hilon;
+static double window[4] = {	/* option -w */
+	-90., 90., -180., 180.
+};
+static int windowed;	/* nozero if option -w */
+static struct vert { double x, y; } v[NVERT+2];	/*clipping polygon*/
+static struct edge { double a, b, c; } e[NVERT]; /* coeffs for linear inequality */
+static int nvert;	/* number of vertices in clipping polygon */
+
+static double rwindow[4];	/* window, expressed in radians */
+static double params[2];		/* projection params */
+/* bounds on output values before scaling; found by coarse survey */
+static double xmin = 100.;
+static double xmax = -100.;
+static double ymin = 100.;
+static double ymax = -100.;
+static double xcent, ycent;
+static double xoff, yoff;
+double xrange, yrange;
+static int left = -HALFWIDTH;
+static int right = HALFWIDTH;
+static int bottom = -HALFWIDTH;
+static int top = HALFWIDTH;
+static int longlines = SHORTLINES; /* drop longer segments */
+static int shortlines = SHORTLINES;
+static int bflag = 1;	/* 0 for option -b */
+static int s1flag = 0;	/* 1 for option -s1 */
+static int s2flag = 0;	/* 1 for option -s2 */
+static int rflag = 0;	/* 1 for option -r */
+static int kflag = 0;	/* 1 if option -k occurred */
+static int xflag = 0;	/* 1 for option -x */
+       int vflag = 1;	/* -1 if option -v occurred */
+static double position[3];	/* option -p */
+static double center[3] = {0., 0., 0.};	/* option -c */
+static struct coord crot;		/* option -c */
+static double grid[3] = { 10., 10., RESOL };	/* option -g */
+static double dlat, dlon;	/* resolution for tracing grid in lat and lon */
+static double scaling;	/* to compute final integer output */
+static struct file *track;	/* options -t and -u */
+static int ntrack;		/* number of tracks present */
+static char *symbolfile;	/* option -y */
+
+void	clamp(double *px, double v);
+void	clipinit(void);
+double	diddle(struct place *, double, double);
+double	diddle(struct place *, double, double);
+void	dobounds(double, double, double, double, int);
+void	dogrid(double, double, double, double);
+int	duple(struct place *, double);
+double	fmax(double, double);
+double	fmin(double, double);
+void	getdata(char *);
+int	gridpt(double, double, int);
+int	inpoly(double, double);
+int	inwindow(struct place *);
+void	pathnames(void);
+int	pnorm(double);
+void	radbds(double *w, double *rw);
+void	revlon(struct place *, double);
+void	satellite(struct file *);
+int	seeable(double, double);
+void	windlim(void);
+void	realcut(void);
+
+int
+option(char *s) 
+{
+
+	if(s[0]=='-' && (s[1]<'0'||s[1]>'9'))
+		return(s[1]!='.'&&s[1]!=0);
+	else
+		return(0);
+}
+
+void
+conv(int k, struct coord *g)
+{
+	g->l = (0.0001/SCALERATIO)*k;
+	sincos(g);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int i,k;
+	char *s, *t, *style;
+	double x, y;
+	double lat, lon;
+	double *wlim;
+	double dd;
+	if(sizeof(short)!=2)
+		abort();	/* getshort() won't work */
+	s = getenv("MAP");
+	if(s)
+		file[0].name = s;
+	s = getenv("MAPDIR");
+	if(s)
+		mapdir = s;
+	if(argc<=1) 
+		error("usage: map projection params options");
+	for(k=0;index[k].name;k++) {
+		s = index[k].name;
+		t = argv[1];
+		while(*s == *t){
+			if(*s==0) goto found;
+			s++;
+			t++;
+		}
+	}
+	fprintf(stderr,"projections:\n");
+	for(i=0;index[i].name;i++)  {
+		fprintf(stderr,"%s",index[i].name);
+		for(k=0; k<index[i].npar; k++)
+			fprintf(stderr," p%d", k);
+		fprintf(stderr,"\n");
+	}
+	exits("error");
+found:
+	argv += 2;
+	argc -= 2;
+	cut = index[k].cut;
+	limb = index[k].limb;
+	poles = index[k].poles;
+	for(i=0;i<index[k].npar;i++) {
+		if(i>=argc||option(argv[i])) {
+			fprintf(stderr,"%s needs %d params\n",index[k].name,index[k].npar);
+			exits("error");
+		}
+		params[i] = atof(argv[i]);
+	}
+	argv += i;
+	argc -= i;
+	while(argc>0&&option(argv[0])) {
+		argc--;
+		argv++;
+		switch(argv[-1][1]) {
+		case 'm':
+			if(file == &dfltfile) {
+				file = 0;
+				nfile = 0;
+			}
+			while(argc && !option(*argv)) {
+				file = realloc(file,(nfile+1)*sizeof(*file));
+				file[nfile].name = *argv;
+				file[nfile].color = currcolor;
+				file[nfile].style = SOLID;
+				nfile++;
+				argv++;
+				argc--;
+			}
+			break;
+		case 'b':
+			bflag = 0;
+			for(nvert=0;nvert<NVERT&&argc>=2;nvert++) {
+				if(option(*argv))
+					break;
+				v[nvert].x = atof(*argv++);
+				argc--;
+				if(option(*argv))
+					break;
+				v[nvert].y = atof(*argv++);
+				argc--;
+			}
+			if(nvert>=NVERT)
+				error("too many clipping vertices");
+			break;
+		case 'g':
+			gridcolor = currcolor;
+			for(i=0;i<3&&argc>i&&!option(argv[i]);i++)
+				grid[i] = atof(argv[i]);
+			switch(i) {
+			case 0:
+				grid[0] = grid[1] = 0.;
+				break;
+			case 1:
+				grid[1] = grid[0];
+			}
+			argc -= i;
+			argv += i;
+			break;
+		case 't':
+			style = SOLID;
+			goto casetu;
+		case 'u':
+			style = DOTDASH;
+		casetu:
+			while(argc && !option(*argv)) {
+				track = realloc(track,(ntrack+1)*sizeof(*track));
+				track[ntrack].name = *argv;
+				track[ntrack].color = currcolor;
+				track[ntrack].style = style;
+				ntrack++;
+				argv++;
+				argc--;
+			}
+			break;
+		case 'r':
+			rflag++;
+			break;
+		case 's':
+			switch(argv[-1][2]) {
+			case '1':
+				s1flag++;
+				break;
+			case 0:		/* compatibility */
+			case '2':
+				s2flag++;
+			}
+			break;
+		case 'o':
+			for(i=0;i<3&&i<argc&&!option(argv[i]);i++)
+				orientation[i] = atof(argv[i]);
+			oriented++;
+			argv += i;
+			argc -= i;
+			break;
+		case 'l':
+			bordcolor = currcolor;
+			for(i=0;i<argc&&i<4&&!option(argv[i]);i++)
+				limits[i] = atof(argv[i]);
+			argv += i;
+			argc -= i;
+			break;
+		case 'k':
+			kflag++;
+			for(i=0;i<argc&&i<4&&!option(argv[i]);i++)
+				klimits[i] = atof(argv[i]);
+			argv += i;
+			argc -= i;
+			break;
+		case 'd':
+			if(argc>0&&!option(argv[0])) {
+				delta = atoi(argv[0]);
+				argv++;
+				argc--;
+			}
+			break;
+		case 'w':
+			bordcolor = currcolor;
+			windowed++;
+			for(i=0;i<argc&&i<4&&!option(argv[i]);i++)
+				window[i] = atof(argv[i]);
+			argv += i;
+			argc -= i;
+			break;
+		case 'c':
+			for(i=0;i<3&&argc>i&&!option(argv[i]);i++) 
+				center[i] = atof(argv[i]);
+			argc -= i;
+			argv += i;
+			break;
+		case 'p':
+			for(i=0;i<3&&argc>i&&!option(argv[i]);i++)
+				position[i] = atof(argv[i]);
+			argc -= i;
+			argv += i;
+			if(i!=3||position[2]<=0) 
+				error("incomplete positioning");
+			break;
+		case 'y':
+			if(argc>0&&!option(argv[0])) {
+				symbolfile = argv[0];
+				argc--;
+				argv++;
+			}
+			break;
+		case 'v':
+			if(index[k].limb == 0)
+				error("-v does not apply here");
+			vflag = -1;
+			break;
+		case 'x':
+			xflag = 1;
+			break;
+		case 'C':
+			if(argc && !option(*argv)) {
+				currcolor = colorcode(*argv);
+				argc--;
+				argv++;
+			}
+			break;
+		}
+	}
+	if(argc>0)
+		error("error in arguments");
+	pathnames();
+	clamp(&limits[0],-90.);
+	clamp(&limits[1],90.);
+	clamp(&klimits[0],-90.);
+	clamp(&klimits[1],90.);
+	clamp(&window[0],-90.);
+	clamp(&window[1],90.);
+	radbds(limits,rlimits);
+	limcase = limits[2]<-180.?0:
+		  limits[3]>180.?2:
+		  1;
+	if(
+		window[0]>=window[1]||
+		window[2]>=window[3]||
+		window[0]>90.||
+		window[1]<-90.||
+		window[2]>180.||
+		window[3]<-180.)
+		error("unreasonable window");
+	windlim();
+	radbds(window,rwindow);
+	upright = orientation[0]==90? 1: orientation[0]==-90? -1: 0;
+	if(index[k].spheroid && !upright)
+		error("can't tilt the spheroid");
+	if(limits[2]>limits[3])
+		limits[3] += 360;
+	if(!oriented)
+		orientation[2] = (limits[2]+limits[3])/2;
+	orient(orientation[0],orientation[1],orientation[2]);
+	projection = (*index[k].prog)(params[0],params[1]);
+	if(projection == 0)
+		error("unreasonable projection parameters");
+	clipinit();
+	grid[0] = fabs(grid[0]);
+	grid[1] = fabs(grid[1]);
+	if(!kflag)
+		for(i=0;i<4;i++)
+			klimits[i] = limits[i];
+	if(klimits[2]>klimits[3])
+		klimits[3] += 360;
+	lolat = limits[0];
+	hilat = limits[1];
+	lolon = limits[2];
+	hilon = limits[3];
+	if(lolon>=hilon||lolat>=hilat||lolat<-90.||hilat>90.)
+		error("unreasonable limits");
+	wlim = kflag? klimits: window;
+	dlat = fmin(hilat-lolat,wlim[1]-wlim[0])/16;
+	dlon = fmin(hilon-lolon,wlim[3]-wlim[2])/32;
+	dd = fmax(dlat,dlon);
+	while(grid[2]>fmin(dlat,dlon)/2)
+		grid[2] /= 2;
+	realcut();
+	if(nvert<=0) {
+		for(lat=klimits[0];lat<klimits[1]+dd-FUZZ;lat+=dd) {
+			if(lat>klimits[1])
+				lat = klimits[1];
+			for(lon=klimits[2];lon<klimits[3]+dd-FUZZ;lon+=dd) {
+				i = (kflag?posproj:normproj)
+					(lat,lon+(lon<klimits[3]?FUZZ:-FUZZ),
+				   &x,&y);
+				if(i*vflag <= 0)
+					continue;
+				if(x<xmin) xmin = x;
+				if(x>xmax) xmax = x;
+				if(y<ymin) ymin = y;
+				if(y>ymax) ymax = y;
+			}
+		}
+	} else {
+		for(i=0; i<nvert; i++) {
+			x = v[i].x;
+			y = v[i].y;
+			if(x<xmin) xmin = x;
+			if(x>xmax) xmax = x;
+			if(y<ymin) ymin = y;
+			if(y>ymax) ymax = y;
+		}
+	}
+	xrange = xmax - xmin;
+	yrange = ymax - ymin;
+	if(xrange<=0||yrange<=0)
+		error("map seems to be empty");
+	scaling = 2;	/*plotting area from -1 to 1*/
+	if(position[2]!=0) {
+		if(posproj(position[0]-.5,position[1],&xcent,&ycent)==0||
+		   posproj(position[0]+.5,position[1],&x,&y)==0)
+			error("unreasonable position");
+		scaling /= (position[2]*hypot(x-xcent,y-ycent));
+		if(posproj(position[0],position[1],&xcent,&ycent)==0)
+			error("unreasonable position");
+	} else {
+		scaling /= (xrange>yrange?xrange:yrange);
+		xcent = (xmin+xmax)/2;
+		ycent = (ymin+ymax)/2;
+	}
+	xoff = center[0]/scaling;
+	yoff = center[1]/scaling;
+	crot.l = center[2]*RAD;
+	sincos(&crot);
+	scaling *= HALFWIDTH*0.9;
+	if(symbolfile) 
+		getsyms(symbolfile);
+	if(!s2flag) {
+		openpl();
+		erase();
+	}
+	range(left,bottom,right,top);
+	comment("grid","");
+	colorx(gridcolor);
+	pen(DOTTED);
+	if(grid[0]>0.)
+		for(lat=ceil(lolat/grid[0])*grid[0];
+		    lat<=hilat;lat+=grid[0]) 
+			dogrid(lat,lat,lolon,hilon);
+	if(grid[1]>0.)
+		for(lon=ceil(lolon/grid[1])*grid[1];
+		    lon<=hilon;lon+=grid[1]) 
+			dogrid(lolat,hilat,lon,lon);
+	comment("border","");
+	colorx(bordcolor);
+	pen(SOLID);
+	if(bflag) {
+		dolimb();
+		dobounds(lolat,hilat,lolon,hilon,0);
+		dobounds(window[0],window[1],window[2],window[3],1);
+	}
+	lolat = floor(limits[0]/10)*10;
+	hilat = ceil(limits[1]/10)*10;
+	lolon = floor(limits[2]/10)*10;
+	hilon = ceil(limits[3]/10)*10;
+	if(lolon>hilon)
+		hilon += 360.;
+	/*do tracks first so as not to lose the standard input*/
+	for(i=0;i<ntrack;i++) {
+		longlines = LONGLINES;
+		satellite(&track[i]);
+		longlines = shortlines;
+	}
+	for(i=0;i<nfile;i++) {
+		comment("mapfile",file[i].name);
+		colorx(file[i].color);
+		pen(file[i].style);
+		getdata(file[i].name);
+	}
+	move(right,bottom);
+	if(!s1flag)
+		closepl();
+	return 0;
+}
+
+/* Out of perverseness (really to recover from a dubious,
+   but documented, convention) the returns from projection
+   functions (-1 unplottable, 0 wrong sheet, 1 good) are
+   recoded into -1 wrong sheet, 0 unplottable, 1 good. */
+
+int
+fixproj(struct place *g, double *x, double *y)
+{
+	int i = (*projection)(g,x,y);
+	return i<0? 0: i==0? -1: 1;
+}
+
+int
+normproj(double lat, double lon, double *x, double *y)
+{
+	int i;
+	struct place geog;
+	latlon(lat,lon,&geog);
+/*
+	printp(&geog);
+*/
+	normalize(&geog);
+	if(!inwindow(&geog))
+		return(-1);
+	i = fixproj(&geog,x,y);
+	if(rflag) 
+		*x = -*x;
+/*
+	printp(&geog);
+	fprintf(stderr,"%d %.3f %.3f\n",i,*x,*y);
+*/
+	return(i);
+}
+
+int
+posproj(double lat, double lon, double *x, double *y)
+{
+	int i;
+	struct place geog;
+	latlon(lat,lon,&geog);
+	normalize(&geog);
+	i = fixproj(&geog,x,y);
+	if(rflag) 
+		*x = -*x;
+	return(i);
+}
+
+int
+inwindow(struct place *geog)
+{
+	if(geog->nlat.l<rwindow[0]-FUZZ||
+	   geog->nlat.l>rwindow[1]+FUZZ||
+	   geog->wlon.l<rwindow[2]-FUZZ||
+	   geog->wlon.l>rwindow[3]+FUZZ)
+		return(0);
+	else return(1);
+}
+
+int
+inlimits(struct place *g)
+{
+	if(rlimits[0]-FUZZ>g->nlat.l||
+	   rlimits[1]+FUZZ<g->nlat.l)
+		return(0);
+	switch(limcase) {
+	case 0:
+		if(rlimits[2]+TWOPI-FUZZ>g->wlon.l&&
+		   rlimits[3]+FUZZ<g->wlon.l)
+			return(0);
+		break;
+	case 1:
+		if(rlimits[2]-FUZZ>g->wlon.l||
+		   rlimits[3]+FUZZ<g->wlon.l)
+			return(0);
+		break;
+	case 2:
+		if(rlimits[2]>g->wlon.l&&
+		   rlimits[3]-TWOPI+FUZZ<g->wlon.l)
+			return(0);
+		break;
+	}
+	return(1);
+}
+
+
+long patch[18][36];
+
+void
+getdata(char *mapfile)
+{
+	char *indexfile;
+	int kx,ky,c;
+	int k;
+	long b;
+	long *p;
+	int ip, jp;
+	int n;
+	struct place g;
+	int i, j;
+	double lat, lon;
+	int conn;
+	FILE *ifile, *xfile;
+
+	indexfile = mapindex(mapfile);
+	xfile = fopen(indexfile,"r");
+	if(xfile==NULL)
+		filerror("can't find map index", indexfile);
+	free(indexfile);
+	for(i=0,p=patch[0];i<18*36;i++,p++)
+		*p = 1;
+	while(!feof(xfile) && fscanf(xfile,"%d%d%ld",&i,&j,&b)==3)
+		patch[i+9][j+18] = b;
+	fclose(xfile);
+	ifile = fopen(mapfile,"r");
+	if(ifile==NULL)
+		filerror("can't find map data", mapfile);
+	for(lat=lolat;lat<hilat;lat+=10.)
+		for(lon=lolon;lon<hilon;lon+=10.) {
+			if(!seeable(lat,lon))
+				continue;
+			i = pnorm(lat);
+			j = pnorm(lon);
+			if((b=patch[i+9][j+18])&1)
+				continue;
+			fseek(ifile,b,0);
+			while((ip=getc(ifile))>=0&&(jp=getc(ifile))>=0){
+				if(ip!=(i&0377)||jp!=(j&0377))
+					break;
+				n = getshort(ifile);
+				conn = 0;
+				if(n > 0) {	/* absolute coordinates */
+					kx = ky = 0;	/* set */
+					for(k=0;k<n;k++){
+						kx = SCALERATIO*getshort(ifile);
+						ky = SCALERATIO*getshort(ifile);
+						if (((k%delta) != 0) && (k != (n-1)))
+							continue;
+						conv(kx,&g.nlat);
+						conv(ky,&g.wlon);
+						conn = plotpt(&g,conn);
+					}
+				} else {	/* differential, scaled by SCALERATI0 */
+					n = -n;
+					kx = SCALERATIO*getshort(ifile);
+					ky = SCALERATIO*getshort(ifile);
+					for(k=0; k<n; k++) {
+						c = getc(ifile);
+						if(c&0200) c|= ~0177;
+						kx += c;
+						c = getc(ifile);
+						if(c&0200) c|= ~0177;
+						ky += c;
+						if(k%delta!=0&&k!=n-1)
+							continue;
+						conv(kx,&g.nlat);
+						conv(ky,&g.wlon);
+						conn = plotpt(&g,conn);
+					}
+				}
+				if(k==1) {
+					conv(kx,&g.nlat);
+					conv(ky,&g.wlon);
+					plotpt(&g,conn);
+				}
+			}
+		}
+	fclose(ifile);
+}
+
+int
+seeable(double lat0, double lon0)
+{
+	double x, y;
+	double lat, lon;
+	for(lat=lat0;lat<=lat0+10;lat+=2*grid[2])
+		for(lon=lon0;lon<=lon0+10;lon+=2*grid[2])
+			if(normproj(lat,lon,&x,&y)*vflag>0)
+				return(1);
+	return(0);
+}
+
+void
+satellite(struct file *t)
+{
+	char sym[50];
+	char lbl[50];
+	double scale;
+	int conn;
+	double lat,lon;
+	struct place place;
+	static FILE *ifile;
+
+	if(ifile == nil)
+		ifile = stdin;
+	if(t->name[0]!='-'||t->name[1]!=0) {
+		fclose(ifile);
+		if((ifile=fopen(t->name,"r"))==NULL)
+			filerror("can't find track", t->name);
+	}
+	comment("track",t->name);
+	colorx(t->color);
+	pen(t->style);
+	for(;;) {
+		conn = 0;
+		while(!feof(ifile) && fscanf(ifile,"%lf%lf",&lat,&lon)==2){
+			latlon(lat,lon,&place);
+			if(fscanf(ifile,"%1s",lbl) == 1) {
+				if(strchr("+-.0123456789",*lbl)==0)
+					break;
+				ungetc(*lbl,ifile);
+			}
+			conn = plotpt(&place,conn);
+		}
+		if(feof(ifile))
+			return;
+		fscanf(ifile,"%[^\n]",lbl+1);
+		switch(*lbl) {
+		case '"':
+			if(plotpt(&place,conn))
+				text(lbl+1);
+			break;
+		case ':':
+		case '!':
+			if(sscanf(lbl+1,"%s %lf",sym,&scale) <= 1)
+				scale = 1;
+			if(plotpt(&place,conn?conn:-1)) {
+				int r = *lbl=='!'?0:rflag?-1:1;
+				pen(SOLID);
+				if(putsym(&place,sym,scale,r) == 0)
+					text(lbl);
+				pen(t->style);
+			}
+			break;
+		default:
+			if(plotpt(&place,conn))
+				text(lbl);
+			break;
+		}
+	}
+}
+
+int
+pnorm(double x)
+{
+	int i;
+	i = x/10.;
+	i %= 36;
+	if(i>=18) return(i-36);
+	if(i<-18) return(i+36);
+	return(i);
+}
+
+void
+error(char *s)
+{
+	fprintf(stderr,"map: \r\n%s\n",s);
+	exits("error");
+}
+
+void
+filerror(char *s, char *f)
+{
+	fprintf(stderr,"\r\n%s %s\n",s,f);
+	exits("error");
+}
+
+char *
+mapindex(char *s)
+{
+	char *t = malloc(strlen(s)+3);
+	strcpy(t,s);
+	strcat(t,".x");
+	return t;
+}
+
+#define NOPT 32767
+static int ox = NOPT;
+static int oy = NOPT;
+
+int
+cpoint(int xi, int yi, int conn)
+{
+	int dx = abs(ox-xi);
+	int dy = abs(oy-yi);
+	if(!xflag && (xi<left||xi>=right || yi<bottom||yi>=top)) {
+		ox = oy = NOPT;
+		return 0;
+	}
+	if(conn == -1)		/* isolated plotting symbol */
+		{}
+	else if(!conn)
+		point(xi,yi);
+	else {
+		if(dx+dy>longlines) {
+			ox = oy = NOPT;	/* don't leap across cuts */
+			return 0;
+		}
+		if(dx || dy)
+			vec(xi,yi);
+	}
+	ox = xi, oy = yi;
+	return dx+dy<=2? 2: 1;	/* 2=very near; see dogrid */
+}
+
+
+struct place oldg;
+
+int
+plotpt(struct place *g, int conn)
+{
+	int kx,ky;
+	int ret;
+	double cutlon;
+	if(!inlimits(g)) {
+		return(0);
+}
+	normalize(g);
+	if(!inwindow(g)) {
+		return(0);
+}
+	switch((*cut)(g,&oldg,&cutlon)) {
+	case 2:
+		if(conn) {
+			ret = duple(g,cutlon)|duple(g,cutlon);
+			oldg = *g;
+			return(ret);
+		}
+	case 0:
+		conn = 0;
+	default:	/* prevent diags about bad return value */
+	case 1:
+		oldg = *g;
+		ret = doproj(g,&kx,&ky);
+		if(ret==0 || !onlimb && ret*vflag<=0)
+			return(0);
+		ret = cpoint(kx,ky,conn);
+		return ret;
+	}
+}
+
+int
+doproj(struct place *g, int *kx, int *ky)
+{
+	int i;
+	double x,y,x1,y1;
+/*fprintf(stderr,"dopr1 %f %f \n",g->nlat.l,g->wlon.l);*/
+	i = fixproj(g,&x,&y);
+	if(i == 0)
+		return(0);
+	if(rflag)
+		x = -x;
+/*fprintf(stderr,"dopr2 %f %f\n",x,y);*/
+	if(!inpoly(x,y)) {
+		return 0;
+}
+	x1 = x - xcent;
+	y1 = y - ycent;
+	x = (x1*crot.c - y1*crot.s + xoff)*scaling;
+	y = (x1*crot.s + y1*crot.c + yoff)*scaling;
+	*kx = x + (x>0?.5:-.5);
+	*ky = y + (y>0?.5:-.5);
+	return(i);
+}
+
+int
+duple(struct place *g, double cutlon)
+{
+	int kx,ky;
+	int okx,oky;
+	struct place ig;
+	revlon(g,cutlon);
+	revlon(&oldg,cutlon);
+	ig = *g;
+	invert(&ig);
+	if(!inlimits(&ig))
+		return(0);
+	if(doproj(g,&kx,&ky)*vflag<=0 ||
+	   doproj(&oldg,&okx,&oky)*vflag<=0)
+		return(0);
+	cpoint(okx,oky,0);
+	cpoint(kx,ky,1);
+	return(1);
+}
+
+void
+revlon(struct place *g, double cutlon)
+{
+	g->wlon.l = reduce(cutlon-reduce(g->wlon.l-cutlon));
+	sincos(&g->wlon);
+}
+
+
+/*	recognize problems of cuts
+ *	move a point across cut to side of its predecessor
+ *	if its very close to the cut
+ *	return(0) if cut interrupts the line
+ *	return(1) if line is to be drawn normally
+ *	return(2) if line is so close to cut as to
+ *	be properly drawn on both sheets
+*/
+
+int
+picut(struct place *g, struct place *og, double *cutlon)
+{
+	*cutlon = PI;
+	return(ckcut(g,og,PI));
+}
+
+int
+nocut(struct place *g, struct place *og, double *cutlon)
+{
+	USED(g);
+	USED(og);
+	USED(cutlon);
+/*
+#pragma	ref g
+#pragma	ref og
+#pragma	ref cutlon
+*/
+	return(1);
+}
+
+int
+ckcut(struct place *g1, struct place *g2, double lon)
+{
+	double d1, d2;
+	double f1, f2;
+	int kx,ky;
+	d1 = reduce(g1->wlon.l -lon);
+	d2 = reduce(g2->wlon.l -lon);
+	if((f1=fabs(d1))<FUZZ)
+		d1 = diddle(g1,lon,d2);
+	if((f2=fabs(d2))<FUZZ) {
+		d2 = diddle(g2,lon,d1);
+		if(doproj(g2,&kx,&ky)*vflag>0)
+			cpoint(kx,ky,0);
+	}
+	if(f1<FUZZ&&f2<FUZZ)
+		return(2);
+	if(f1>PI*TWO_THRD||f2>PI*TWO_THRD)
+		return(1);
+	return(d1*d2>=0);
+}
+
+double
+diddle(struct place *g, double lon, double d)
+{
+	double d1;
+	d1 = FUZZ/2;
+	if(d<0)
+		d1 = -d1;
+	g->wlon.l = reduce(lon+d1);
+	sincos(&g->wlon);
+	return(d1);
+}
+
+double
+reduce(double lon)
+{
+	if(lon>PI)
+		lon -= 2*PI;
+	else if(lon<-PI)
+		lon += 2*PI;
+	return(lon);
+}
+
+
+double tetrapt = 35.26438968;	/* atan(1/sqrt(2)) */
+
+void
+dogrid(double lat0, double lat1, double lon0, double lon1)
+{
+	double slat,slon,tlat,tlon;
+	register int conn, oconn;
+	slat = tlat = slon = tlon = 0;
+	if(lat1>lat0)
+		slat = tlat = fmin(grid[2],dlat);
+	else
+		slon = tlon = fmin(grid[2],dlon);;
+	conn = oconn = 0;
+	while(lat0<=lat1&&lon0<=lon1) {
+		conn = gridpt(lat0,lon0,conn);
+		if(projection==Xguyou&&slat>0) {
+			if(lat0<-45&&lat0+slat>-45)
+				conn = gridpt(-45.,lon0,conn);
+			else if(lat0<45&&lat0+slat>45)
+				conn = gridpt(45.,lon0,conn);
+		} else if(projection==Xtetra&&slat>0) {
+			if(lat0<-tetrapt&&lat0+slat>-tetrapt) {
+				gridpt(-tetrapt-.001,lon0,conn);
+				conn = gridpt(-tetrapt+.001,lon0,0);
+			}
+			else if(lat0<tetrapt&&lat0+slat>tetrapt) {
+				gridpt(tetrapt-.001,lon0,conn);
+				conn = gridpt(tetrapt+.001,lon0,0);
+			}
+		}
+		if(conn==0 && oconn!=0) {
+			if(slat+slon>.05) {
+				lat0 -= slat;	/* steps too big */
+				lon0 -= slon;	/* or near bdry */
+				slat /= 2;
+				slon /= 2;
+				conn = oconn = gridpt(lat0,lon0,conn);
+			} else
+				oconn = 0;
+		} else {
+			if(conn==2) {
+				slat = tlat;
+				slon = tlon;
+				conn = 1;
+			}
+			oconn = conn;
+	 	}
+		lat0 += slat;
+		lon0 += slon;
+	}
+	gridpt(lat1,lon1,conn);
+}
+
+static int gridinv;		/* nonzero when doing window bounds */
+
+int
+gridpt(double lat, double lon, int conn)
+{
+	struct place g;
+/*fprintf(stderr,"%f %f\n",lat,lon);*/
+	latlon(lat,lon,&g);
+	if(gridinv)
+		invert(&g);
+	return(plotpt(&g,conn));
+}
+
+/* win=0 ordinary grid lines, win=1 window lines */
+
+void
+dobounds(double lolat, double hilat, double lolon, double hilon, int win)
+{
+	gridinv = win;
+	if(lolat>-90 || win && (poles&1)!=0)
+		dogrid(lolat+FUZZ,lolat+FUZZ,lolon,hilon);
+	if(hilat<90 || win && (poles&2)!=0)
+		dogrid(hilat-FUZZ,hilat-FUZZ,lolon,hilon);
+	if(hilon-lolon<360 || win && cut==picut) {
+		dogrid(lolat,hilat,lolon+FUZZ,lolon+FUZZ);
+		dogrid(lolat,hilat,hilon-FUZZ,hilon-FUZZ);
+	}
+	gridinv = 0;
+}
+
+static void
+dolimb(void)
+{
+	double lat, lon;
+	double res = fmin(dlat, dlon)/4;
+	int conn = 0;
+	int newconn;
+	if(limb == 0)
+		return;
+	onlimb = gridinv = 1;
+	for(;;) {
+		newconn = (*limb)(&lat, &lon, res);
+		if(newconn == -1)
+			break;
+		conn = gridpt(lat, lon, conn*newconn);
+	}
+	onlimb = gridinv = 0;
+}
+
+
+void
+radbds(double *w, double *rw)
+{
+	int i;
+	for(i=0;i<4;i++)
+		rw[i] = w[i]*RAD;
+	rw[0] -= FUZZ;
+	rw[1] += FUZZ;
+	rw[2] -= FUZZ;
+	rw[3] += FUZZ;
+}
+
+void
+windlim(void)
+{
+	double center = orientation[0];
+	double colat;
+	if(center>90)
+		center = 180 - center;
+	if(center<-90)
+		center = -180 - center;
+	if(fabs(center)>90)
+		error("unreasonable orientation");
+	colat = 90 - window[0];
+	if(center-colat>limits[0])
+		limits[0] = center - colat;
+	if(center+colat<limits[1])
+		limits[1] = center + colat;
+}
+
+
+short
+getshort(FILE *f)
+{
+	int c, r;
+	c = getc(f);
+	r = (c | getc(f)<<8);
+	if (r&0x8000)
+		r |= ~0xFFFF;	/* in case short > 16 bits */
+	return r;
+}
+
+double
+fmin(double x, double y)
+{
+	return(x<y?x:y);
+}
+
+double
+fmax(double x, double y)
+{
+	return(x>y?x:y);
+}
+
+void
+clamp(double *px, double v)
+{
+	*px = (v<0?fmax:fmin)(*px,v);
+}
+
+void
+pathnames(void)
+{
+	int i;
+	char *t, *indexfile, *name;
+	FILE *f, *fx;
+	for(i=0; i<nfile; i++) {
+		name = file[i].name;
+		if(*name=='/')
+			continue;
+		indexfile = mapindex(name);
+			/* ansi equiv of unix access() call */
+		f = fopen(name, "r");
+		fx = fopen(indexfile, "r");
+		if(f) fclose(f);
+		if(fx) fclose(fx);
+		free(indexfile);
+		if(f && fx)
+			continue;
+		t = malloc(strlen(name)+strlen(mapdir)+2);
+		strcpy(t,mapdir);
+		strcat(t,"/");
+		strcat(t,name);
+		file[i].name = t;
+	}
+}
+
+void
+clipinit(void)
+{
+	int i;
+	double s,t;
+	if(nvert<=0)
+		return;
+	for(i=0; i<nvert; i++) {	/*convert latlon to xy*/
+		if(normproj(v[i].x,v[i].y,&v[i].x,&v[i].y)==0)
+			error("invisible clipping vertex");
+	}
+	if(nvert==2) {			/*rectangle with diag specified*/
+		nvert = 4;
+		v[2] = v[1];
+		v[1].x=v[0].x, v[1].y=v[2].y, v[3].x=v[2].x, v[3].y=v[0].y;
+	}	
+	v[nvert] = v[0];
+	v[nvert+1] = v[1];
+	s = 0;
+	for(i=1; i<=nvert; i++) {	/*test for convexity*/
+		t = (v[i-1].x-v[i].x)*(v[i+1].y-v[i].y) -
+		    (v[i-1].y-v[i].y)*(v[i+1].x-v[i].x);
+		if(t<-FUZZ && s>=0) s = 1;
+		if(t>FUZZ && s<=0) s = -1;
+		if(-FUZZ<=t&&t<=FUZZ || t*s>0) {
+			s = 0;
+			break;
+		}
+	}
+	if(s==0)
+		error("improper clipping polygon");
+	for(i=0; i<nvert; i++) {	/*edge equation ax+by=c*/
+		e[i].a = s*(v[i+1].y - v[i].y);
+		e[i].b = s*(v[i].x - v[i+1].x);
+		e[i].c = s*(v[i].x*v[i+1].y - v[i].y*v[i+1].x);
+	}
+}
+
+int
+inpoly(double x, double y)
+{
+	int i;
+	for(i=0; i<nvert; i++) {
+		register struct edge *ei = &e[i];
+		double val = x*ei->a + y*ei->b - ei->c;
+		if(val>10*FUZZ)
+			return(0);
+	}
+	return 1;
+}
+
+void
+realcut()
+{
+	struct place g;
+	double lat;
+	
+	if(cut != picut)	/* punt on unusual cuts */
+		return;
+	for(lat=window[0]; lat<=window[1]; lat+=grid[2]) {
+		g.wlon.l = PI;
+		sincos(&g.wlon);
+		g.nlat.l = lat*RAD;
+		sincos(&g.nlat);
+		if(!inwindow(&g)) {
+			break;
+}
+		invert(&g);
+		if(inlimits(&g)) {
+			return;
+}
+	}
+	longlines = shortlines = LONGLINES;
+	cut = nocut;		/* not necessary; small eff. gain */
+}
diff --git a/src/cmd/map/map.h b/src/cmd/map/map.h
new file mode 100644
index 0000000..d96497c
--- /dev/null
+++ b/src/cmd/map/map.h
@@ -0,0 +1,147 @@
+/*
+#pragma	lib	"/sys/src/cmd/map/libmap/libmap.a$O"
+#pragma	src	"/sys/src/cmd/map/libmap"
+*/
+
+#define index index0
+#ifndef PI
+#define PI	3.1415926535897932384626433832795028841971693993751
+#endif
+
+#define TWOPI (2*PI)
+#define RAD (PI/180)
+double	hypot(double, double);	/* sqrt(a*a+b*b) */
+double	tan(double);		/* not in K&R library */
+
+#define ECC .08227185422	/* eccentricity of earth */
+#define EC2 .006768657997
+
+#define FUZZ .0001
+#define UNUSED 0.0		/* a dummy double parameter */
+
+struct coord {
+	double l;	/* lat or lon in radians*/
+	double s;	/* sin */
+	double c;	/* cos */
+};
+struct place {
+	struct coord nlat;
+	struct coord wlon;
+};
+
+typedef int (*proj)(struct place *, double *, double *);
+
+struct index {		/* index of known projections */
+	char *name;	/* name of projection */
+	proj (*prog)(double, double);
+			/* pointer to projection function */
+	int npar;	/* number of params */
+	int (*cut)(struct place *, struct place *, double *);
+			/* function that handles cuts--eg longitude 180 */
+	int poles;	/*1 S pole is a line, 2 N pole is, 3 both*/
+	int spheroid;	/* poles must be at 90 deg if nonzero */
+	int (*limb)(double *lat, double *lon, double resolution);
+			/* get next place on limb */
+			/* return -1 if done, 0 at gap, else 1 */
+};
+
+
+proj	aitoff(void);
+proj	albers(double, double);
+int	Xazequalarea(struct place *, double *, double *);
+proj	azequalarea(void);
+int	Xazequidistant(struct place *, double *, double *);
+proj	azequidistant(void);
+proj	bicentric(double);
+proj	bonne(double);
+proj	conic(double);
+proj	cylequalarea(double);
+int	Xcylindrical(struct place *, double *, double *);
+proj	cylindrical(void);
+proj	elliptic(double);
+proj	fisheye(double);
+proj	gall(double);
+proj	gilbert(void);
+proj	globular(void);
+proj	gnomonic(void);
+int	guycut(struct place *, struct place *, double *);
+int	Xguyou(struct place *, double *, double *);
+proj	guyou(void);
+proj	harrison(double, double);
+int	hexcut(struct place *, struct place *, double *);
+proj	hex(void);
+proj	homing(double);
+int	hlimb(double*, double*, double resolution);
+proj	lagrange(void);
+proj	lambert(double, double);
+proj	laue(void);
+proj	lune(double, double);
+proj	loxodromic(double);	/* not in library */
+proj	mecca(double);
+int	mlimb(double*, double*, double resolution);
+proj	mercator(void);
+proj	mollweide(void);
+proj	newyorker(double);
+proj	ortelius(double, double);	/* not in library */
+int	Xorthographic(struct place *place, double *x, double *y);
+proj	orthographic(void);
+int	olimb(double*, double*, double);
+proj	perspective(double);
+int	plimb(double*, double*, double resolution);
+int	Xpolyconic(struct place *, double *, double *);
+proj	polyconic(void);
+proj	rectangular(double);
+proj	simpleconic(double, double);
+int	Xsinusoidal(struct place *, double *, double *);
+proj	sinusoidal(void);
+proj	sp_albers(double, double);
+proj	sp_mercator(void);
+proj	square(void);
+int	Xstereographic(struct place *, double *, double *);
+proj	stereographic(void);
+int	Xtetra(struct place *, double *, double *);
+int	tetracut(struct place *, struct place *, double *);
+proj	tetra(void);
+proj	trapezoidal(double, double);
+proj	vandergrinten(void);
+proj	wreath(double, double);	/* not in library */
+
+void	findxy(double, double *, double *);
+void	albscale(double, double, double, double);
+void	invalb(double, double, double *, double *);
+
+void	cdiv(double, double, double, double, double *, double *);
+void	cmul(double, double, double, double, double *, double *);
+void	cpow(double, double, double *, double *, double);
+void	csq(double, double, double *, double *);
+void	csqrt(double, double, double *, double *);
+void	ccubrt(double, double, double *, double *);
+double	cubrt(double);
+int	elco2(double, double, double, double, double, double *, double *);
+void	cdiv2(double, double, double, double, double *, double *);
+void	csqr(double, double, double *, double *);
+
+void	orient(double, double, double);
+void	latlon(double, double, struct place *);
+void	deg2rad(double, struct coord *);
+void	sincos(struct coord *);
+void	normalize(struct place *);
+void	invert(struct place *);
+void	norm(struct place *, struct place *, struct coord *);
+void	printp(struct place *);
+void	copyplace(struct place *, struct place *);
+
+int	picut(struct place *, struct place *, double *);
+int	ckcut(struct place *, struct place *, double);
+double	reduce(double);
+
+void	getsyms(char *);
+int	putsym(struct place *, char *, double, int);
+void	filerror(char *s, char *f);
+void	error(char *s);
+int	doproj(struct place *, int *, int *);
+int	cpoint(int, int, int);
+int	plotpt(struct place *, int);
+int	nocut(struct place *, struct place *, double *);
+
+extern int (*projection)(struct place *, double *, double *);
diff --git a/src/cmd/map/map.rc b/src/cmd/map/map.rc
new file mode 100755
index 0000000..eaa75c9
--- /dev/null
+++ b/src/cmd/map/map.rc
@@ -0,0 +1,103 @@
+#!/bin/rc
+
+rfork en
+
+# F FEATUREs, M map files, A other arguments
+FEATURE=no
+
+if (~ $MAPPROG '')
+	MAPPROG=/bin/aux/mapd
+
+if (~ $MAPDIR '')
+	MAPDIR=/lib/map
+
+F=(); M=(); A=();
+for (i) {
+	switch ($FEATURE) {
+	case no
+		switch ($i) {
+		case -f
+			FEATURE=yes 
+			F=($F)
+		case *
+			A=($A $i)
+		}
+	case yes
+		switch ($i) {
+		case -f
+		case -*
+			A=($A $i)
+			FEATURE=no
+		case riv*2
+			F=($F 201 202)
+		case riv*3
+			F=($F 201 202 203)
+		case riv*4
+			F=($F 201 202 203 204)
+		case riv*
+			F=($F 201)
+		case iriv*2
+			F=($F 206 207)
+		case iriv*[34]
+			F=($F 206 207 208)
+		case iriv*
+			F=($F 206)
+		case coast*2 shore*2 lake*2
+			F=($F 102)
+		case coast*3 shore*3 lake*3
+			F=($F 102 103)
+		case coast*4 shore*4 lake*4
+			F=($F 102 103 104)
+		case coast* shore* lake*
+		case ilake*[234] ishore*[234]
+			F=($F 106 107)
+		case ilake* ishore*
+			F=($F 106)
+		case reef*
+			F=($F 108)
+		case canal*2
+			F=($F 210 211)
+		case canal*[34]
+			F=($F 210 211 212)
+		case canal*
+			F=($F 210)
+		case glacier*
+			F=($F 115)
+		case state* province*
+			F=($F 401)
+		case countr*2
+			F=($F 301 302)
+		case countr*[34]
+			F=($F 301 302 303)
+		case countr*
+			F=($F 301)
+		case salt*[234]
+			F=($F 109 110)
+		case salt*
+			F=($F 109)
+		case ice*[234] shel*[234]
+			F=($F 113 114)
+		case ice* shel*
+			F=($F 113)
+		case *
+			echo map: unknown feature $i >[1=2]
+			exits "unknown feature"
+		}
+	}
+}
+
+for (j in $F) {
+	if (test -r $MAPDIR/$j)
+		M=($M $MAPDIR/$j)
+}
+
+if (~ $F ?*) {
+	if (test -r $MAPDIR/101)
+		M=(101 $M)
+	M=(-m $M)
+}
+
+if (~ $MAP '')
+	MAP=world
+
+MAP=$MAP MAPDIR=$MAPDIR $MAPPROG $A $M
diff --git a/src/cmd/map/mapdemo.rc b/src/cmd/map/mapdemo.rc
new file mode 100755
index 0000000..033969a
--- /dev/null
+++ b/src/cmd/map/mapdemo.rc
@@ -0,0 +1,83 @@
+#!/bin/rc
+
+fn demo {proj=$1;	shift;
+	label=$1;	shift;
+	{	echo 'o'
+		echo 'ra -8192 -8492 8192 8492'
+		echo 'e'
+		echo 'm -8192 8192'
+		echo t $type
+		echo 'm -8192 -8192'
+		echo t $proj - $label
+		MAP=world MAPDIR=/lib/map map $proj $* -s -d 5
+	} 
+	sleep 5
+}
+
+rfork en
+{
+type='Equatorial projections centered on long. 0. Parallels are straight lines.'
+
+demo mercator 'equally spaced straight meridians, conformal, straight compass courses'
+demo sinusoidal 'equally spaced parallels, equal-area, same as bonne(0)'
+demo cylequalarea 'equally spaced straight meridians, equal-area, true scale on Eq' 0
+demo cylindrical 'central projection on tangent cylinder'
+demo rectangular 'equally spaced parallels, equally spaced straight meridians, true scale on Eq' 0
+demo gall 'parallels spaced stereographically on prime meridian, equally spaced straight meridians, true scale on Eq' 0
+demo mollweide '(homalographic) equal-area, hemisphere is a circle'
+demo gilbert 'globe mapped conformally on hemisphere, viewed orthographically'
+
+type='Azimuthal: centered on the North Pole, Parallels are concentric circles, Meridians are equally spaced radial lines'
+
+demo azequidistant 'equally spaced parallels, true distances from pole'
+demo azequalarea 'equal area'
+demo gnomonic 'central projecton on tangent plane, straight great circles'
+demo perspective 'viewed along earth''s axis 2 earth radii from center of earth' 2
+demo orthographic 'viewed from infinity'
+demo stereographic 'conformal, projected from opposite pole'
+demo laue 'radius = tan(2\(mu colatitude ), used in xray crystallography'
+demo fisheye 'fisheye view of stereographic map, index of refraction 2' 2 -o 40.75 74
+demo newyorker 'New Yorker map from viewing pedestal of radius .5' .5 -o 40.75 74
+
+type='Polar conic projections symmetric about the Prime Meridian. Parallels are segments of concentric circles.'
+
+demo conic 'central projection on cone tangent at 40' 40
+demo simpleconic 'equally spaced parallels, true scale on 20 and 50' 20 50
+demo lambert 'conformal, true scale on 20 and 50' 20 50
+demo albers 'equal-area, true scale on 20 and 50' 20 50
+demo bonne 'equally spaced parallels, equal-area, parallel 40 developed from tangent cone' 40
+
+type='Projections with bilateral symmetry about the Prime Meridian and the equator.'
+
+demo polyconic 'parallels developed from tangent cones, equally spaced along Prime Meridian'
+demo aitoff 'equal-area projection of globe onto 2-to-1 ellipse, based on azequalarea'
+demo lagrange 'conformal, maps whole sphere into a circle'
+demo bicentric 'points plotted at true azimuth from two centers on the equator at longitudes +-40, great circles are straight lines' 40
+demo elliptic 'points are plotted at true distance from two centers on the equator at longitudes +-40' 40
+demo globular 'hemisphere is circle, circular meridians and parallels'
+demo vandergrinten 'sphere is circle, meridians as in globular, circular arc parallels resemble mercator'
+
+type='Doubly periodic conformal projections.'
+
+demo guyou 'W and E hemispheres are square'
+demo square 'World is square with Poles at diagonally opposite corners'
+demo tetra 'map on tetrahedron with edge tangent to Prime Meridian at S Pole, unfolded into equilateral triangle'
+demo hex 'world is hexagon centered on N Pole, N and S hemispheres are equilateral
+triangles'
+
+type='Retroazimuthal projections. Directions to center are true.'
+
+demo mecca 'equally spaced vertical meridians' 21.4 -o 90 -39.8
+demo homing 'distances to Mecca are true' 21.4 -o 90 -39.8
+
+type='Miscellaneous projections.'
+
+demo harrison 'oblique perspective from above the North Pole, 2 earth radii from the earth, looking along the Date Line 40 degrees off vertical' 2 40
+demo trapezoidal 'equally spaced parallels, straight meridians equally spaced along parallels, true scale at 20 and 50 on Prime Meridian' 20 50
+demo lune 'conformal, polar cap above Eq is 60-degree lune' 0 60
+
+type='Maps based on the spheroid'
+
+demo sp_mercator 'equally spaced straight meridians, conformal'
+demo sp_albers 'equal-area, true scale on 20 and 50' 20 50
+} | plot
diff --git a/src/cmd/map/mkfile b/src/cmd/map/mkfile
new file mode 100644
index 0000000..4d27274
--- /dev/null
+++ b/src/cmd/map/mkfile
@@ -0,0 +1,32 @@
+<$PLAN9/src/mkhdr
+
+TARG=mapd 
+LIB=libmap/libmap.a
+OFILES=map.$O\
+	symbol.$O\
+	index.$O\
+	sqrt.$O\
+
+HFILES=map.h\
+	iplot.h\
+
+<$PLAN9/src/mkone
+
+
+$O.out:V:	$OFILES $LIB
+	$LD $LDFLAGS -o $target $prereq
+
+$LIB:V:
+	cd libmap
+	mk install
+
+installall:V:
+	for(objtype in $CPUS)
+		mk install
+	cp map.rc /rc/bin/map
+	cp mapdemo.rc /rc/bin/mapdemo
+
+clean nuke:V:
+	rm -f *.[$OS] [$OS].out y.tab.? y.debug y.output $TARG
+	cd libmap; mk clean
+
diff --git a/src/cmd/map/route.c b/src/cmd/map/route.c
new file mode 100644
index 0000000..c4c6713
--- /dev/null
+++ b/src/cmd/map/route.c
@@ -0,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include "map.h"
+
+/* Given two lat-lon pairs, find an orientation for the
+   -o option of "map" that will place those two points
+   on the equator of a standard projection, equally spaced
+   about the prime meridian.
+
+   -w and -l options are suggested also.
+
+   Option -t prints out a series of
+   coordinates that follows the (great circle) track
+   in the original coordinate system,
+   followed by ".
+   This data is just right for map -t.
+
+   Option -i inverts the map top-to-bottom.
+*/
+struct place pole;
+struct coord twist;
+int track;
+int inv = -1;
+
+extern void doroute(double, double, double, double, double);
+
+void
+dorot(double a, double b, double *x, double *y, void (*f)(struct place *))
+{
+	struct place g;
+	deg2rad(a,&g.nlat);
+	deg2rad(b,&g.wlon);
+	(*f)(&g);
+	*x = g.nlat.l/RAD;
+	*y = g.wlon.l/RAD;
+}
+
+void
+rotate(double a, double b, double *x, double *y)
+{
+	dorot(a,b,x,y,normalize);
+}
+
+void
+rinvert(double a, double b, double *x, double *y)
+{
+	dorot(a,b,x,y,invert);
+}
+
+main(int argc, char **argv)
+{
+#pragma ref argv
+	double an,aw,bn,bw;
+	ARGBEGIN {
+	case 't':
+		track = 1;
+		break;
+
+	case 'i':
+		inv = 1;
+		break;
+
+	default:
+		exits("route: bad option");
+	} ARGEND;
+	if (argc<4) {
+		print("use route [-t] [-i] lat lon lat lon\n");
+		exits("arg count");
+	}
+	an = atof(argv[0]);
+	aw = atof(argv[1]);
+	bn = atof(argv[2]);
+	bw = atof(argv[3]);
+	doroute(inv*90.,an,aw,bn,bw);
+	return 0;
+}
+
+void
+doroute(double dir, double an, double aw, double bn, double bw)
+{
+	double an1,aw1,bn1,bw1,pn,pw;
+	double theta;
+	double cn,cw,cn1,cw1;
+	int i,n;
+	orient(an,aw,0.);
+	rotate(bn,bw,&bn1,&bw1);
+/*	printf("b %f %f\n",bn1,bw1);*/
+	orient(an,aw,bw1);
+	rinvert(0.,dir,&pn,&pw);
+/*	printf("p %f %f\n",pn,pw);*/
+	orient(pn,pw,0.);
+	rotate(an,aw,&an1,&aw1);
+	rotate(bn,bw,&bn1,&bw1);
+	theta = (aw1+bw1)/2;
+/*	printf("a %f %f \n",an1,aw1);*/
+	orient(pn,pw,theta);
+	rotate(an,aw,&an1,&aw1);
+	rotate(bn,bw,&bn1,&bw1);
+	if(fabs(aw1-bw1)>180)
+		if(theta<0.) theta+=180;
+		else theta -= 180;
+	orient(pn,pw,theta);
+	rotate(an,aw,&an1,&aw1);
+	rotate(bn,bw,&bn1,&bw1);
+	if(!track) {
+		double dlat, dlon, t;
+		/* printf("A %.4f %.4f\n",an1,aw1); */
+		/* printf("B %.4f %.4f\n",bn1,bw1); */
+		cw1 = fabs(bw1-aw1);	/* angular difference for map margins */
+		/* while (aw<0.0)
+			aw += 360.;
+		while (bw<0.0)
+			bw += 360.; */
+		dlon = fabs(aw-bw);
+		if (dlon>180)
+			dlon = 360-dlon;
+		dlat = fabs(an-bn);
+		printf("-o %.4f %.4f %.4f -w %.2f %.2f %.2f %.2f \n",
+		  pn,pw,theta, -0.3*cw1, .3*cw1, -.6*cw1, .6*cw1);
+		
+	} else {
+		cn1 = 0;
+		n = 1 + fabs(bw1-aw1)/.2;
+		for(i=0;i<=n;i++) {
+			cw1 = aw1 + i*(bw1-aw1)/n;
+			rinvert(cn1,cw1,&cn,&cw);
+			printf("%f %f\n",cn,cw);
+		}
+		printf("\"\n");
+	}
+}
diff --git a/src/cmd/map/sqrt.c b/src/cmd/map/sqrt.c
new file mode 100644
index 0000000..1f93338
--- /dev/null
+++ b/src/cmd/map/sqrt.c
@@ -0,0 +1,52 @@
+/*
+	sqrt returns the square root of its floating
+	point argument. Newton's method.
+
+	calls frexp
+*/
+
+#include <u.h>
+#include <libc.h>
+
+double
+sqrt(double arg)
+{
+	double x, temp;
+	int exp, i;
+
+	if(arg <= 0) {
+		if(arg < 0)
+			return 0.;
+		return 0;
+	}
+	x = frexp(arg, &exp);
+	while(x < 0.5) {
+		x *= 2;
+		exp--;
+	}
+	/*
+	 * NOTE
+	 * this wont work on 1's comp
+	 */
+	if(exp & 1) {
+		x *= 2;
+		exp--;
+	}
+	temp = 0.5 * (1.0+x);
+
+	while(exp > 60) {
+		temp *= (1L<<30);
+		exp -= 60;
+	}
+	while(exp < -60) {
+		temp /= (1L<<30);
+		exp += 60;
+	}
+	if(exp >= 0)
+		temp *= 1L << (exp/2);
+	else
+		temp /= 1L << (-exp/2);
+	for(i=0; i<=4; i++)
+		temp = 0.5*(temp + arg/temp);
+	return temp;
+}
diff --git a/src/cmd/map/symbol.c b/src/cmd/map/symbol.c
new file mode 100644
index 0000000..e8f3c68
--- /dev/null
+++ b/src/cmd/map/symbol.c
@@ -0,0 +1,192 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "map.h"
+#include "iplot.h"
+
+#define NSYMBOL 20
+
+enum flag { POINT,ENDSEG,ENDSYM };
+struct symb {
+	double x, y;
+	char name[10+1];
+	enum flag flag;
+} *symbol[NSYMBOL];
+
+static int nsymbol;
+static double halfrange = 1;
+extern int halfwidth;
+extern int vflag;
+
+static int	getrange(FILE *);
+static int	getsymbol(FILE *, int);
+static void	setrot(struct place *, double, int);
+static void	dorot(struct symb *, double *, double *);
+
+
+void
+getsyms(char *file)
+{
+	FILE *sf = fopen(file,"r");
+	if(sf==0)
+		filerror("cannot open", file);
+	while(nsymbol<NSYMBOL-1 && getsymbol(sf,nsymbol))
+		nsymbol++;
+	fclose(sf);
+}
+
+static int
+getsymbol(FILE *sf, int n)
+{
+	double x,y;
+	char s[2];
+	int i;
+	struct symb *sp;
+	for(;;) {
+		if(fscanf(sf,"%1s",s)==EOF)
+			return 0;
+		switch(s[0]) {
+		case ':':
+			break;
+		case 'o':
+		case 'c':	/* cl */
+			fscanf(sf,"%*[^\n]");
+			continue;
+		case 'r':
+			if(getrange(sf))
+				continue;
+		default:
+			error("-y file syntax error");
+		}
+		break;
+	}
+	sp = (struct symb*)malloc(sizeof(struct symb));
+	symbol[n] = sp;
+	if(fscanf(sf,"%10s",sp->name)!=1)
+		return 0;
+	i = 0;
+	while(fscanf(sf,"%1s",s)!=EOF) {
+		switch(s[0]) {
+		case 'r':
+			if(!getrange(sf))
+				break;
+			continue;
+		case 'm':
+			if(i>0)
+				symbol[n][i-1].flag = ENDSEG;
+			continue;
+		case ':':
+			ungetc(s[0],sf);
+			break;
+		default:
+			ungetc(s[0],sf);
+		case 'v':
+			if(fscanf(sf,"%lf %lf",&x,&y)!=2)
+				break;
+			sp[i].x = x*halfwidth/halfrange;
+			sp[i].y = y*halfwidth/halfrange;
+			sp[i].flag = POINT;
+			i++;
+			sp = symbol[n] = (struct symb*)realloc(symbol[n],
+					(i+1)*sizeof(struct symb));
+			continue;
+		}
+		break;
+	}
+	if(i>0)
+		symbol[n][i-1].flag = ENDSYM;
+	else
+		symbol[n] = 0;
+	return 1;
+}
+
+static int
+getrange(FILE *sf)
+{
+	double x,y,xmin,ymin;
+	if(fscanf(sf,"%*s %lf %lf %lf %lf",
+		&xmin,&ymin,&x,&y)!=4)
+		return 0;
+	x -= xmin;
+	y -= ymin;
+	halfrange = (x>y? x: y)/2;
+	if(halfrange<=0)
+		error("bad ra command in -y file");
+	return 1;
+}
+
+/* r=0 upright;=1 normal;=-1 reverse*/
+int
+putsym(struct place *p, char *name, double s, int r)
+{
+	int x,y,n;
+	struct symb *sp;
+	double dx,dy;
+	int conn = 0;
+	for(n=0; symbol[n]; n++)
+		if(strcmp(name,symbol[n]->name)==0)
+			break;
+	sp = symbol[n];
+	if(sp==0)
+		return 0;
+	if(doproj(p,&x,&y)*vflag <= 0)
+		return 1;
+	setrot(p,s,r);
+	for(;;) {
+		dorot(sp,&dx,&dy);
+		conn = cpoint(x+(int)dx,y+(int)dy,conn);
+		switch(sp->flag) {
+		case ENDSEG:
+			conn = 0;
+		case POINT:
+			sp++;
+			continue;
+		case ENDSYM:
+			break;
+		}
+		break;
+	}
+	return 1;
+}
+
+static double rot[2][2];
+
+static void
+setrot(struct place *p, double s, int r)
+{
+	double x0,y0,x1,y1;
+	struct place up;
+	up = *p;
+	up.nlat.l += .5*RAD;
+	sincos(&up.nlat);
+	if(r&&(*projection)(p,&x0,&y0)) {
+		if((*projection)(&up,&x1,&y1)<=0) {
+			up.nlat.l -= RAD;
+			sincos(&up.nlat);
+			if((*projection)(&up,&x1,&y1)<=0)
+				goto unit;
+			x1 = x0 - x1;
+			y1 = y0 - y1;
+		} else {
+			x1 -= x0;
+			y1 -= y0;
+		}
+		x1 = r*x1;
+		s /= hypot(x1,y1);
+		rot[0][0] = y1*s;
+		rot[0][1] = x1*s;
+		rot[1][0] = -x1*s;
+		rot[1][1] = y1*s;
+	} else {
+unit:
+		rot[0][0] = rot[1][1] = s;
+		rot[0][1] = rot[1][0] = 0;
+	}
+}
+
+static void
+dorot(struct symb *sp, double *px, double *py)
+{
+	*px = rot[0][0]*sp->x + rot[0][1]*sp->y;
+	*py = rot[1][0]*sp->x + rot[1][1]*sp->y;
+}