shithub: riscv

Download patch

ref: 360cabb85846e4d344d6f9aaf4082cf42683a91c
parent: 24a5720bec7a90e94ab13d3c32b27f39585a1039
author: ppatience0 <[email protected]>
date: Thu Jul 18 22:16:43 EDT 2013

readtif: fix many bugs

totif: add tiff encoder

--- a/sys/man/1/jpg
+++ b/sys/man/1/jpg
@@ -1,6 +1,6 @@
 .TH JPG 1
 .SH NAME
-jpg, gif, png, tif, ppm, bmp, v210, yuv, ico, tga, tojpg, togeordi, togif, toppm, topng, toico \- view and convert pictures
+jpg, gif, png, tif, ppm, bmp, v210, yuv, ico, tga, tojpg, togeordi, togif, toppm, topng, totif, toico \- view and convert pictures
 .SH SYNOPSIS
 .B jpg
 [
@@ -127,6 +127,16 @@
 ] [
 .I file
 ]
+.br
+.B totif
+[
+.B -c
+.I comment
+] [
+.B -bBgGhHlLmprtT
+] [
+.I file
+]
 .PP
 .B ico
 [
@@ -157,9 +167,11 @@
 .IR Tojpg ,
 .IR togif ,
 .IR toppm ,
+.IR topng ,
 and
-.I topng
-read Plan 9 images files, convert them to JPEG, GIF, PPM, or PNG, and write them to standard output.
+.I totif
+read Plan 9 images files, convert them to JPEG, GIF, PPM,
+PNG, or TIFF and write them to standard output.
 .PP
 The default behavior of
 .IR jpg ,
@@ -246,7 +258,7 @@
 and
 .IR topng
 programs go the other way: they convert from Plan 9 images to JPEG, GIF,
-PPM and PNG and have no display capability.
+PPM, PNG, and TIFF and have no display capability.
 They all accept an option
 .B -c
 to set the comment field of the resulting file.
@@ -270,6 +282,52 @@
 script that invokes
 .B tojpg
 .BR -s .
+.I Totif
+accepts many options.
+Choosing Huffman, T4, or T6 compression
+forces the output to be a bilevel image.
+.TP
+.B -b
+Output a bilevel (GREY1) image.
+.TP
+.B -B
+Output a grayscale (GREY2) image.
+.TP
+.B -g
+Output a grayscale (GREY4) image.
+.TP
+.B -G
+Output a grayscale (GREY8) image.
+.TP
+.B -h
+Use Huffman compression.
+.TP
+.B -H
+Use T4 one-dimensional compression.
+.TP
+.B -l
+Use LZW compression.
+.TP
+.B -L
+Use LZW compression with horizontal differencing.
+Note that some TIFF decoders may not accept horizontal
+differencing applied to images with depths less than eight.
+.TP
+.B -m
+Output a color (CMAP8) image.
+.TP
+.B -p
+Use Packbits compression.
+.TP
+.B -r
+Output a color (BGR24) image.
+.TP
+.B -t
+Use T4 two-dimensional compression.
+.TP
+.B -T
+Use T6 compression.
+.PP
 If there is only one input picture,
 .I togif
 converts the image to GIF format.
--- a/sys/src/cmd/jpg/imagefile.h
+++ b/sys/src/cmd/jpg/imagefile.h
@@ -75,10 +75,12 @@
 
 char*		writeppm(Biobuf*, Image*, char*, int);
 char*		memwriteppm(Biobuf*, Memimage*, char*, int);
+char*		writejpg(Biobuf*, Image*, char*, int, int);
+char*		memwritejpg(Biobuf*, Memimage*, char*, int, int);
 Image*		multichan(Image*);
 Memimage*	memmultichan(Memimage*);
 
 char*		memwritepng(Biobuf*, Memimage*, ImageInfo*);
 
-char*		writejpg(Biobuf*, Image*, char*, int, int);
-char*		memwritejpg(Biobuf*, Memimage*, char*, int, int);
+char*		writetif(Biobuf*, Image*, char*, int, int);
+char*		memwritetif(Biobuf*, Memimage*, char*, int, int);
--- a/sys/src/cmd/jpg/mkfile
+++ b/sys/src/cmd/jpg/mkfile
@@ -10,6 +10,7 @@
 	png\
 	topng\
 	tif\
+	totif\
 	yuv\
 	ico\
 	toico\
@@ -54,9 +55,10 @@
 $O.togif:	writegif.$O onechan.$O togif.$O torgbv.$O
 $O.ppm:		$IMFILES readppm.$O ppm.$O
 $O.toppm:	writeppm.$O multichan.$O toppm.$O
-$O.png:		$IMFILES readpng.$O png.$O
+$O.png:		torgbv.$O writerawimage.$O readpng.$O png.$O
 $O.topng:	writepng.$O topng.$O
-$O.tif:		$IMFILES readtif.$O tif.$O
+$O.tif:		torgbv.$O writerawimage.$O readtif.$O tif.$O
+$O.totif:	writetif.$O totif.$O
 $O.yuv:		$IMFILES readyuv.$O yuv.$O
 $O.bmp:		$IMFILES readbmp.$O bmp.$O
 $O.v210:	$IMFILES readv210.$O v210.$O
--- a/sys/src/cmd/jpg/readtif.c
+++ b/sys/src/cmd/jpg/readtif.c
@@ -7,7 +7,7 @@
 * http://www.itu.int/rec/T-REC-T.4-199904-S/en
 * http://www.itu.int/rec/T-REC-T.6-198811-I/en
 *
-* fax codes and lzw:
+* copy-pasted fax codes and lzw help:
 * http://www.remotesensing.org/libtiff/
 */
 #include <u.h>
@@ -91,9 +91,10 @@
 	Tab *tab[2];
 	int ntab; /* position in tab */
 	Tab *eol;
+	int eolfill;
 	int (*getbit)(Fax *);
-	int *l1;
-	int *l2;
+	ulong *l1;
+	ulong *l2;
 	ulong nl;
 	uchar *data;
 	ulong next; /* next strip offset in data */
@@ -433,7 +434,7 @@
 static int faxalloclines(Fax *);
 static Tab *getfax1d(Fax *, uchar *, ulong, ulong *, ulong *, ulong);
 static Tab *getfax2d(Fax *, uchar *, ulong, ulong *, ulong *, ulong);
-static int faxstrip(Tif *, Fax *, uchar *, ulong, ulong *);
+static int faxstrip(Tif *, Fax *, uchar *, ulong, ulong, ulong *);
 static uchar *fax(Tif *);
 static void tabinit(Lzw *);
 static Code *newcode(Lzw *, Code *);
@@ -441,7 +442,8 @@
 static Code *tabadd(Lzw *, Code *, Code *);
 static int getcode(Lzw *);
 static int wstr(uchar *, ulong, ulong *, Code *, long *);
-static void predict(Tif *, uchar *);
+static int predict1(Tif *, uchar *, ulong);
+static int predict8(Tif *, uchar *, ulong);
 static int lzwstrip(Lzw *, uchar *, ulong, ulong *, long);
 static uchar *lzw(Tif *);
 static uchar *packbits(Tif *);
@@ -669,6 +671,10 @@
 	Tab *p;
 
 	if(f->eol == nil) {
+		if(f->eolfill) {
+			for(i = 0; i < 4; i++)
+				(*f->getbit)(f);
+		}
 		if((p = gettab(f, 0)) == nil || p->run >= 0) {
 			werrstr("first eol");
 			return nil;
@@ -697,10 +703,8 @@
 		werrstr("fax row overflow");
 		return -1;
 	}
-	if((*i += n) >= size) {
-		werrstr("fax data overflow");
+	if((*i += n) > size)
 		return -1;
-	}
 	if(f->st != 0)
 		memset(data+*i-n, f->st, n);
 	return 0;
@@ -744,7 +748,7 @@
 	int j, n;
 	Tab *p;
 
-	for(j = 0; *x < dx;) {
+	for(j = n = 0; *x < dx;) {
 		if((p = gettab(f, 0)) == nil)
 			return nil;
 		if((n = p->run) < 0) {
@@ -760,6 +764,17 @@
 		if(j >= f->nl)
 			faxalloclines(f);
 	}
+	if(n >= 64) {
+		f->l1[j] = dx;
+		if((p = gettab(f, 0)) == nil)
+			return nil;
+		if((n = p->run) < 0)
+			return f->eol;
+		if(n != 0) {
+			werrstr("no terminating code");
+			return nil;
+		}
+	}
 	return nil;
 }
 
@@ -767,7 +782,8 @@
 getfax2d(Fax *f, uchar *data, ulong size, ulong *i, ulong *x,
 	ulong dx)
 {
-	int j, k, n, code, len, a0, a1, b1, b2, v;
+	int j, k, n, code, len, v;
+	long a0, a1, b1, b2;
 	Tab *p;
 
 	a0 = -1;
@@ -795,7 +811,10 @@
 			for(k = 0; k < 2;) {
 				if((p = gettab(f, 0)) == nil)
 					return nil;
-				n = p->run;
+				if((n = p->run) < 0) {
+					werrstr("2d eol");
+					return nil;
+				}
 				if(faxfill(f, data, size, i, x,
 					dx, n) < 0)
 					return nil;
@@ -842,7 +861,8 @@
 }
 
 static int
-faxstrip(Tif *t, Fax *f, uchar *data, ulong size, ulong *i)
+faxstrip(Tif *t, Fax *f, uchar *data, ulong size, ulong rows,
+	ulong *i)
 {
 	int d1;
 	ulong x, y;
@@ -850,7 +870,7 @@
 
 	d1 = t->comp != T6enc;
 	p = nil;
-	for(x = y = 0; x < t->dx || y < t->rows;) {
+	for(x = y = 0; x < t->dx || y < rows;) {
 		f->st = 0;
 		if(t->comp == T4enc) {
 			if(p == nil && geteol(f) == nil) {
@@ -858,8 +878,11 @@
 					return -1;
 				break;
 			}
-			if(y > 0)
+			if(y > 0) {
 				*i += t->dx - x;
+				if(*i > size)
+					break;
+			}
 			if(t->t4 & 1) {
 				d1 = (*f->getbit)(f);
 				if(d1 < 0)
@@ -878,32 +901,34 @@
 		if(t->comp == Huffman)
 			fillbits(f);
 		if(p == nil && x != t->dx) {
-			if(f->st >= 0 || x > t->dx)
+			if(f->st >= 0)
 				return -1;
+			if(x > t->dx)
+				return -1;
 			break;
 		}
 	}
+	if(*i > size) {
+		werrstr("fax data overflow");
+		return -1;
+	}
 	return 0;
 }
 
-/*
-* the t4 fax test images i decoded did not follow the
-* spec. in particular, they did not have rtcs.
-*/
+/* i've encountered t4 images that did not have rtcs */
 static uchar *
 fax(Tif *t)
 {
 	int m;
-	ulong i, j, datasz, linesz;
+	ulong i, j, datasz, r, dy;
 	uchar *data;
 	Fax f;
 
 	datasz = t->dx * t->dy * sizeof *data;
-	data = malloc(datasz);
-	f.nl = t->dx;
-	linesz = f.nl * sizeof *f.l1;
-	f.l1 = malloc(linesz);
-	f.l2 = malloc(linesz);
+	data = mallocz(datasz, 1);
+	f.nl = t->dx + 1;
+	f.l1 = mallocz(f.nl*sizeof *f.l1, 1);
+	f.l2 = mallocz(f.nl*sizeof *f.l2, 1);
 	if(data == nil || f.l1 == nil || f.l2 == nil) {
 		free(t->data);
 		if(data != nil)
@@ -914,9 +939,6 @@
 			free(f.l2);
 		return nil;
 	}
-	memset(data, 0, datasz);
-	memset(f.l1, 0, linesz);
-	memset(f.l2, 0, linesz);
 	if(t->fill == 1) {
 		f.getbit = getbit1;
 		m = 7;
@@ -928,8 +950,12 @@
 	f.tab[1] = faxblack;
 	f.ntab = 0;
 	f.eol = nil;
+	if(t->comp == T4enc && t->t4 & (1<<1))
+		f.eolfill = 1;
+	else
+		f.eolfill = 0;
 	f.data = t->data;
-	for(i = j = 0; i < t->nstrips; i++) {
+	for(i = j = 0, dy = t->dy; i < t->nstrips; i++) {
 		f.l1[0] = t->dx;
 		f.n = t->strips[i];
 		f.m = m;
@@ -937,8 +963,10 @@
 			f.next = t->strips[i+1];
 		else
 			f.next = t->ndata;
-		if(faxstrip(t, &f, data, datasz, &j) < 0)
+		r = dy < t->rows? dy: t->rows;
+		if(faxstrip(t, &f, data, datasz, r, &j) < 0)
 			break;
+		dy -= t->rows;
 	}
 	if(i < t->nstrips) {
 		free(data);
@@ -993,20 +1021,20 @@
 {
 	Code *r, *s;
 
-	if(l->ntab >= Tabsz) {
-		werrstr("lzw table full");
-		return nil;
-	}
-	r = s = &l->tab[l->ntab++];
+	r = s = &l->tab[l->ntab];
 	switch(l->ntab) {
-	case 511:
-	case 1023:
-	case 2047:
+	case 510:
+	case 1022:
+	case 2046:
 		l->len++;
 		break;
 	default:
 		break;
 	}
+	if(l->ntab++ >= Tabsz-3) {
+		werrstr("lzw table full");
+		return nil;
+	}
 	s->val = p->val;
 	while((p = p->next) != nil) {
 		if(s->next != nil)
@@ -1033,6 +1061,7 @@
 	int i, c, code;
 
 	if(l->n >= l->next) {
+eof:
 		werrstr("lzw eof");
 		return -1;
 	}
@@ -1042,8 +1071,9 @@
 		code |= c << i;
 		l->m--;
 		if(l->m < 0) {
-			l->n++;
 			l->m = 7;
+			if(++l->n >= l->next && i > 0)
+				goto eof;
 		}
 	}
 	return code;
@@ -1062,24 +1092,61 @@
 	return 0;
 }
 
-static void
-predict(Tif *t, uchar *data)
+static int
+predict1(Tif *t, uchar *data, ulong ndata)
 {
+	int bpl, pix, b[8], d, m, n, j;
+	ulong i, x, y;
+
+	bpl = bytesperline(Rect(0, 0, t->dx, t->dy), t->depth);
+	d = t->depth;
+	m = (1 << d) - 1;
+	n = 8 / d;
+	for(y = 0; y < t->dy; y++) {
+		for(x = 0; x < bpl; x++) {
+			i = y*bpl + x;
+			if(i >= ndata) {
+				werrstr("pred4 overflow");
+				return -1;
+			}
+			pix = data[i];
+			b[n-1] = (pix >> d*(n-1)) & m;
+			if(x > 0)
+				b[n-1] += data[i-1] & m;
+			for(j = n-2; j >= 0; j--) {
+				b[j] = (pix >> d*j) & m;
+				b[j] += b[j+1];
+			}
+			for(j = pix = 0; j < n; j++)
+				pix |= (b[j] & m) << d*j;
+			data[i] = pix;
+		}
+	}
+	return 0;
+}
+
+static int
+predict8(Tif *t, uchar *data, ulong ndata)
+{
 	char a, b;
-	ulong y, x, i, j, k, s;
+	ulong i, j, s, x, y;
 
 	s = t->samples;
 	for(y = 0; y < t->dy; y++) {
 		for(x = 1; x < t->dx; x++) {
-			i = y*t->dx + x;
-			for(j = 0; j < s; j++) {
-				k = i*s + j;
-				a = (char)data[k];
-				b = (char)data[k-s];
-				data[k] = (uchar)(a + b);
+			i = (y*t->dx + x) * s;
+			if(i+s-1 >= ndata) {
+				werrstr("pred8 overflow");
+				return -1;
 			}
+			for(j = 0; j < s; i++, j++) {
+				a = (char)data[i];
+				b = (char)data[i-s];
+				data[i] = (uchar)(a + b);
+			}
 		}
 	}
+	return 0;
 }
 
 static int
@@ -1102,6 +1169,10 @@
 				break;
 			if(c < 0)
 				return -1;
+			if(c >= l->ntab) {
+				werrstr("table overflow");
+				return -1;
+			}
 			if(wstr(data, size, i, &l->tab[c],
 				&striplen) < 0)
 				return -1;
@@ -1113,7 +1184,7 @@
 			q = &l->tab[oc];
 			if(tabadd(l, q, p) == nil)
 				return -1;
-		} else {
+		} else if(c == l->ntab) {
 			q = &l->tab[oc];
 			if((p = tabadd(l, q, q)) == nil)
 				return -1;
@@ -1120,6 +1191,9 @@
 			if(wstr(data, size, i, p,
 				&striplen) < 0)
 				return -1;
+		} else {
+			werrstr("table overflow");
+			return -1;
 		}
 		if(striplen <= 0)
 			break;
@@ -1132,15 +1206,16 @@
 static uchar *
 lzw(Tif *t)
 {
-	ulong i, j, size;
+	ulong i, j, size, r, dy, n;
 	long striplen;
 	uchar *data;
 	Lzw l;
 	Code *p, *q;
+	int (*predict)(Tif *, uchar *, ulong);
 
-	i = t->dx * t->rows * t->depth;
-	striplen = i%8 == 0? i/8: i/8+1;
-	size = t->nstrips * striplen * sizeof *data;
+	n = t->dx * t->dy * t->depth;
+	n = n%8 == 0? n/8: n/8+1;
+	size = n * sizeof *data;
 	if((data = malloc(size)) == nil) {
 		free(t->data);
 		return nil;
@@ -1151,7 +1226,7 @@
 	}
 	l.data = t->data;
 	l.first = l.last = nil;
-	for(i = j = 0; i < t->nstrips; i++) {
+	for(i = j = 0, dy = t->dy; i < t->nstrips; i++) {
 		tabinit(&l);
 		l.n = t->strips[i];
 		l.m = 7;
@@ -1159,8 +1234,12 @@
 			l.next = t->strips[i+1];
 		else
 			l.next = t->ndata;
+		r = dy < t->rows? dy: t->rows;
+		n = t->dx * r * t->depth;
+		striplen = n%8 == 0? n/8: n/8+1;
 		if(lzwstrip(&l, data, size, &j, striplen) < 0)
 			break;
+		dy -= t->rows;
 	}
 	if(i < t->nstrips) {
 		free(data);
@@ -1177,8 +1256,16 @@
 		free(q);
 	}
 	free(t->data);
-	if(data != nil && t->predictor == 2)
-		predict(t, data);
+	if(data != nil && t->predictor == 2) {
+		if(t->depth < 8)
+			predict = predict1;
+		else
+			predict = predict8;
+		if((*predict)(t, data, size) < 0) {
+			free(data);
+			return nil;
+		}
+	}
 	return data;
 }
 
@@ -1189,7 +1276,9 @@
 	ulong i, j, k, size;
 	uchar *data;
 
-	size = t->dx * t->dy * t->samples * sizeof *data;
+	i = t->dx * t->dy * t->depth;
+	i = i%8 == 0? i/8: i/8+1;
+	size = i * sizeof *data;
 	if((data = malloc(size)) == nil) {
 		free(t->data);
 		return nil;
@@ -1198,11 +1287,9 @@
 		n = (char)t->data[i++];
 		if(n >= 0) {
 			k = n + 1;
-			if(j+k >= size || i+k >= t->ndata)
+			if((j += k) > size || (i += k) > t->ndata)
 				break;
-			memmove(data+j, t->data+i, k);
-			i += k;
-			j += k;
+			memmove(data+j-k, t->data+i-k, k);
 		} else if(n > -128 && n < 0) {
 			k = j - n + 1;
 			if(k > size || i >= t->ndata)
@@ -1461,11 +1548,11 @@
 	size = typesizes[f->typ];
 	if((n = size*f->cnt) <= 4) {
 		for(i = 0; i < f->cnt; i++)
-			f->val[i] = readval(t);
+			f->val[i] = (*readval)(t);
 		f->off = 0x0;
 		f->nval = i;
 		for(j = n; j < 4; j += size)
-			readval(t);
+			(*readval)(t);
 	} else {
 		f->off = readlong(t);
 		off = t->n;
@@ -1472,7 +1559,7 @@
 		if(gototif(t, f->off) < 0)
 			return -1;
 		for(i = 0; i < f->cnt; i++)
-			f->val[i] = readval(t);
+			f->val[i] = (*readval)(t);
 		f->nval = i;
 		if(gototif(t, off) < 0)
 			return -1;
@@ -1600,10 +1687,6 @@
 		werrstr("color map");
 		return -1;
 	}
-	if(t->predictor == 2 && t->depth == 1) {
-		werrstr("depth too low for predictor 2");
-		return -1;
-	}
 	return 0;
 }
 
@@ -1613,9 +1696,14 @@
 	int i, j, n;
 	ulong off;
 
+	t->data = nil;
 	t->ndata = 0;
 	for(i = 0; i < t->nstrips; i++)
 		t->ndata += t->counts[i];
+	if(t->ndata == 0) {
+		werrstr("no image data");
+		return -1;
+	}
 	if((t->data = malloc(t->ndata*sizeof *t->data)) == nil)
 		return -1;
 	off = t->n;
--- a/sys/src/cmd/jpg/tif.c
+++ b/sys/src/cmd/jpg/tif.c
@@ -169,8 +169,8 @@
 	if(array == nil || array[0] == nil) {
 		if(array != nil)
 			free(array);
-		fprint(2, "%s: decode %s failed: %r\n", argv0,
-			name);
+		fprint(2, "%s: decode %s failed: %r\n",
+			argv0, name);
 		return "decode";
 	}
 	Bterm(&b);
@@ -177,9 +177,7 @@
 	if(!dflag) {
 		if(init() < 0)
 			return "initdraw";
-/* fixme: ppm doesn't check for outchan==CMAP8 */
-		if(defaultcolor && screen->depth > 8 &&
-			outchan == CMAP8)
+		if(defaultcolor && screen->depth > 8)
 			outchan = RGB24;
 	}
 	r = array[0];
@@ -232,12 +230,10 @@
 				argv0, name);
 			return "write";
 		}
-	} else if(cflag) {
-		if(writerawimage(1, c) < 0) {
-			fprint(2, "%s: %s: write error: %r\n",
-				argv0, name);
-			return "write";
-		}
+	} else if(cflag && writerawimage(1, c) < 0) {
+		fprint(2, "%s: %s: write error: %r\n",
+			argv0, name);
+		return "write";
 	}
 	if(c != nil && c != r) {
 		free(c->chans[0]);
--- /dev/null
+++ b/sys/src/cmd/jpg/totif.c
@@ -1,0 +1,157 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "imagefile.h"
+
+static Memimage *memtochan(Memimage *, ulong);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-c 'comment'] "
+		"[-bBgGhHlLmprtT] [file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Biobuf bout;
+	Memimage *i, *ni;
+	int fd, chanflag, comp, opt;
+	char *err, *file, *c;
+	ulong chan;
+
+	chan = BGR24;
+	chanflag = opt = 0;
+	comp = 1;
+	c = nil;
+	ARGBEGIN {
+	case 'b':
+		chan = GREY1;
+		chanflag = 1;
+		break;
+	case 'B':
+		chan = GREY2;
+		chanflag = 1;
+		break;
+	case 'c':
+		c = EARGF(usage());
+		break;
+	case 'g':
+		chan = GREY4;
+		chanflag = 1;
+		break;
+	case 'G':
+		chan = GREY8;
+		chanflag = 1;
+		break;
+	case 'h': /* huffman */
+		comp = 2;
+		break;
+	case 'H': /* t4 */
+		comp = 3;
+		opt = 0;
+		break;
+	case 'l': /* lzw */
+		comp = 5;
+		opt = 0;
+		break;
+	case 'L': /* lzw, horizontal differencing */
+		comp = 5;
+		opt = 1;
+		break;
+	case 'm': /* palette */
+		chan = CMAP8;
+		chanflag = 1;
+		break;
+	case 'p': /* packbits */
+		comp = 0x8005;
+		break;
+	case 'r': /* force BGR24 */
+		chan = BGR24;
+		chanflag = 1;
+		break;
+	case 't': /* t4 two-dimensional */
+		comp = 3;
+		opt = 1;
+		break;
+	case 'T': /* t6 */
+		comp = 4;
+		break;
+	default:
+		usage();
+	} ARGEND
+	if(argc > 1)
+		usage();
+	if(argc == 0) {
+		file = "<stdin>";
+		fd = 0;
+	} else {
+		file = argv[0];
+		if((fd = open(file, OREAD)) < 0)
+			sysfatal("open %s: %r", file);
+	}
+	if(Binit(&bout, 1, OWRITE) < 0)
+		sysfatal("Binit: %r");
+	memimageinit();
+	if((i = readmemimage(fd)) == nil)
+		sysfatal("readmemimage %s: %r", file);
+	close(fd);
+	if(comp >= 2 && comp <= 4) {
+		chan = GREY1;
+		chanflag = 1;
+	} else if(chan == GREY2) {
+		if((ni = memtochan(i, chan)) == nil)
+			sysfatal("memtochan: %r");
+		if(i != ni) {
+			freememimage(i);
+			i = ni;
+		}
+		chan = GREY4;
+	}
+	if(!chanflag) {
+		switch(i->chan) {
+		case GREY1:
+		case GREY4:
+		case GREY8:
+		case CMAP8:
+		case BGR24:
+			break;
+		case GREY2:
+			chan = GREY4;
+			chanflag = 1;
+			break;
+		default:
+			chanflag = 1;
+			break;
+		}
+	}
+	if(chanflag) {
+		if((ni = memtochan(i, chan)) == nil)
+			sysfatal("memtochan: %r");
+		if(i != ni) {
+			freememimage(i);
+			i = ni;
+		}
+	}
+	if((err = memwritetif(&bout, i, c, comp, opt)) != nil)
+		fprint(2, "%s: %s\n", argv0, err);
+	freememimage(i);
+	exits(err);
+}
+
+static Memimage *
+memtochan(Memimage *i, ulong chan)
+{
+	Memimage *ni;
+
+	if(i->chan == chan)
+		return i;
+	if((ni = allocmemimage(i->r, chan)) == nil)
+		return nil;
+	memimagedraw(ni, ni->r, i, i->r.min, nil, i->r.min, S);
+	return ni;
+}
--- /dev/null
+++ b/sys/src/cmd/jpg/writetif.c
@@ -1,0 +1,1341 @@
+/*
+* code/documentation:
+* http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
+* http://paulbourke.net/dataformats/tiff/
+* http://www.fileformat.info/format/tiff/egff.htm
+* http://www.fileformat.info/mirror/egff/ch09_05.htm
+* http://www.itu.int/rec/T-REC-T.4-199904-S/en
+* http://www.itu.int/rec/T-REC-T.6-198811-I/en
+*
+* copy-pasted fax codes and copy-pasted lzw encoding
+* hash table implementation:
+* http://www.remotesensing.org/libtiff/
+*/
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "imagefile.h"
+
+enum {
+	Tbyte = 0x0001,
+	Tascii = 0x0002,
+	Tshort = 0x0003,
+	Tlong = 0x0004,
+	Trational = 0x0005,
+	Tnocomp = 0x0001,
+	Thuffman = 0x0002,
+	Tt4enc = 0x0003,
+	Tt6enc = 0x0004,
+	Tlzw = 0x0005,
+	Tpackbits = 0x8005
+};
+
+enum {
+	Twidth,
+	Tlength,
+	Tbits,
+	Tcomp,
+	Tphoto,
+	Tfill,
+	Tdesc,
+	Tstrips,
+	Tsamples,
+	Trows,
+	Tcounts,
+	Txres,
+	Tyres,
+	T4opt,
+	Tresunit,
+	Tpredictor,
+	Tcolor
+};
+
+enum {
+	Kpar = 2,
+	Nfaxtab = 105
+};
+
+enum {
+	Clrcode = 256,
+	Eoicode = 257,
+	Tabsz = 1<<12,
+	Hshift = 13 - 8,
+	Hsize = 9001L
+};
+
+typedef struct Tab Tab;
+typedef struct Fax Fax;
+typedef struct Hash Hash;
+typedef struct Lzw Lzw;
+typedef struct Pkb Pkb;
+typedef struct Fld Fld;
+typedef struct Tif Tif;
+
+struct Tab {
+	int len;
+	int code;
+	int run;
+};
+
+struct Fax {
+	int st;
+	Tab *tab[2];
+	int byte;
+	int nbyte;
+	ulong *l1;
+	ulong *l2;
+	uchar *data;
+	ulong ndata;
+	ulong n;
+};
+
+struct Hash {
+	long hash;
+	u16int code;
+};
+
+struct Lzw {
+	Hash hash[Hsize];
+	int ntab;
+	int len;
+	int byte;
+	int nbyte;
+	uchar *data;
+	ulong ndata;
+	ulong n;
+};
+
+struct Pkb {
+	uchar *data;
+	ulong ndata;
+	ulong n;
+};
+
+struct Fld {
+	ushort tag;
+	ushort typ;
+};
+
+struct Tif {
+	ulong dx;
+	ulong dy;
+	ushort depth[3];
+	ushort comp;
+	ulong opt;
+	char *(*compress)(Tif *);
+	ushort photo;
+	char *desc;
+	ulong *strips;
+	ulong nstrips;
+	ushort samples;
+	ulong rows;
+	ulong *counts;
+	ushort *color;
+	ulong ncolor;
+	uchar *data;
+	ulong ndata;
+	ushort nfld;
+	int bpl;
+};
+
+static Fld flds[] = {
+	[Twidth] {0x0100, Tlong},
+	[Tlength] {0x0101, Tlong},
+	[Tbits] {0x0102, Tshort},
+	[Tcomp] {0x0103, Tshort},
+	[Tphoto] {0x0106, Tshort},
+	[Tfill] {0x010a, Tshort},
+	[Tdesc] {0x010e, Tascii},
+	[Tstrips] {0x0111, Tlong},
+	[Tsamples] {0x0115, Tshort},
+	[Trows] {0x0116, Tlong},
+	[Tcounts] {0x0117, Tlong},
+	[Txres] {0x011a, Trational},
+	[Tyres] {0x011b, Trational},
+	[T4opt] {0x0124, Tlong},
+	[Tresunit] {0x0128, Tshort},
+	[Tpredictor] {0x013d, Tshort},
+	[Tcolor] {0x0140, Tshort}
+};
+
+/*
+* imported from libdraw/arith.c to permit
+* extern log2 function
+*/
+static int log2[] = {
+	-1, 0, 1, -1, 2, -1, -1, -1, 3,
+	-1, -1, -1, -1, -1, -1, -1, 4,
+	-1, -1, -1, -1, -1, -1, -1, 4 /* BUG */,
+	-1, -1, -1, -1, -1, -1, -1, 5
+};
+
+static Tab faxwhite[Nfaxtab] = {
+	{8, 0x35, 0}, /* 0011 0101 */
+	{6, 0x7, 1}, /* 0001 11 */
+	{4, 0x7, 2}, /* 0111 */
+	{4, 0x8, 3}, /* 1000 */
+	{4, 0xb, 4}, /* 1011 */
+	{4, 0xc, 5}, /* 1100 */
+	{4, 0xe, 6}, /* 1110 */
+	{4, 0xf, 7}, /* 1111 */
+	{5, 0x13, 8}, /* 1001 1 */
+	{5, 0x14, 9}, /* 1010 0 */
+	{5, 0x7, 10}, /* 0011 1 */
+	{5, 0x8, 11}, /* 0100 0 */
+	{6, 0x8, 12}, /* 0010 00 */
+	{6, 0x3, 13}, /* 0000 11 */
+	{6, 0x34, 14}, /* 1101 00 */
+	{6, 0x35, 15}, /* 1101 01 */
+	{6, 0x2a, 16}, /* 1010 10 */
+	{6, 0x2b, 17}, /* 1010 11 */
+	{7, 0x27, 18}, /* 0100 111 */
+	{7, 0xc, 19}, /* 0001 100 */
+	{7, 0x8, 20}, /* 0001 000 */
+	{7, 0x17, 21}, /* 0010 111 */
+	{7, 0x3, 22}, /* 0000 011 */
+	{7, 0x4, 23}, /* 0000 100 */
+	{7, 0x28, 24}, /* 0101 000 */
+	{7, 0x2b, 25}, /* 0101 011 */
+	{7, 0x13, 26}, /* 0010 011 */
+	{7, 0x24, 27}, /* 0100 100 */
+	{7, 0x18, 28}, /* 0011 000 */
+	{8, 0x2, 29}, /* 0000 0010 */
+	{8, 0x3, 30}, /* 0000 0011 */
+	{8, 0x1a, 31}, /* 0001 1010 */
+	{8, 0x1b, 32}, /* 0001 1011 */
+	{8, 0x12, 33}, /* 0001 0010 */
+	{8, 0x13, 34}, /* 0001 0011 */
+	{8, 0x14, 35}, /* 0001 0100 */
+	{8, 0x15, 36}, /* 0001 0101 */
+	{8, 0x16, 37}, /* 0001 0110 */
+	{8, 0x17, 38}, /* 0001 0111 */
+	{8, 0x28, 39}, /* 0010 1000 */
+	{8, 0x29, 40}, /* 0010 1001 */
+	{8, 0x2a, 41}, /* 0010 1010 */
+	{8, 0x2b, 42}, /* 0010 1011 */
+	{8, 0x2c, 43}, /* 0010 1100 */
+	{8, 0x2d, 44}, /* 0010 1101 */
+	{8, 0x4, 45}, /* 0000 0100 */
+	{8, 0x5, 46}, /* 0000 0101 */
+	{8, 0xa, 47}, /* 0000 1010 */
+	{8, 0xb, 48}, /* 0000 1011 */
+	{8, 0x52, 49}, /* 0101 0010 */
+	{8, 0x53, 50}, /* 0101 0011 */
+	{8, 0x54, 51}, /* 0101 0100 */
+	{8, 0x55, 52}, /* 0101 0101 */
+	{8, 0x24, 53}, /* 0010 0100 */
+	{8, 0x25, 54}, /* 0010 0101 */
+	{8, 0x58, 55}, /* 0101 1000 */
+	{8, 0x59, 56}, /* 0101 1001 */
+	{8, 0x5a, 57}, /* 0101 1010 */
+	{8, 0x5b, 58}, /* 0101 1011 */
+	{8, 0x4a, 59}, /* 0100 1010 */
+	{8, 0x4b, 60}, /* 0100 1011 */
+	{8, 0x32, 61}, /* 0011 0010 */
+	{8, 0x33, 62}, /* 0011 0011 */
+	{8, 0x34, 63}, /* 0011 0100 */
+	{5, 0x1b, 64}, /* 1101 1 */
+	{5, 0x12, 128}, /* 1001 0 */
+	{6, 0x17, 192}, /* 0101 11 */
+	{7, 0x37, 256}, /* 0110 111 */
+	{8, 0x36, 320}, /* 0011 0110 */
+	{8, 0x37, 384}, /* 0011 0111 */
+	{8, 0x64, 448}, /* 0110 0100 */
+	{8, 0x65, 512}, /* 0110 0101 */
+	{8, 0x68, 576}, /* 0110 1000 */
+	{8, 0x67, 640}, /* 0110 0111 */
+	{9, 0xcc, 704}, /* 0110 0110 0 */
+	{9, 0xcd, 768}, /* 0110 0110 1 */
+	{9, 0xd2, 832}, /* 0110 1001 0 */
+	{9, 0xd3, 896}, /* 0110 1001 1 */
+	{9, 0xd4, 960}, /* 0110 1010 0 */
+	{9, 0xd5, 1024}, /* 0110 1010 1 */
+	{9, 0xd6, 1088}, /* 0110 1011 0 */
+	{9, 0xd7, 1152}, /* 0110 1011 1 */
+	{9, 0xd8, 1216}, /* 0110 1100 0 */
+	{9, 0xd9, 1280}, /* 0110 1100 1 */
+	{9, 0xda, 1344}, /* 0110 1101 0 */
+	{9, 0xdb, 1408}, /* 0110 1101 1 */
+	{9, 0x98, 1472}, /* 0100 1100 0 */
+	{9, 0x99, 1536}, /* 0100 1100 1 */
+	{9, 0x9a, 1600}, /* 0100 1101 0 */
+	{6, 0x18, 1664}, /* 0110 00 */
+	{9, 0x9b, 1728}, /* 0100 1101 1 */
+	{11, 0x8, 1792}, /* 0000 0001 000 */
+	{11, 0xc, 1856}, /* 0000 0001 100 */
+	{11, 0xd, 1920}, /* 0000 0001 101 */
+	{12, 0x12, 1984}, /* 0000 0001 0010 */
+	{12, 0x13, 2048}, /* 0000 0001 0011 */
+	{12, 0x14, 2112}, /* 0000 0001 0100 */
+	{12, 0x15, 2176}, /* 0000 0001 0101 */
+	{12, 0x16, 2240}, /* 0000 0001 0110 */
+	{12, 0x17, 2304}, /* 0000 0001 0111 */
+	{12, 0x1c, 2368}, /* 0000 0001 1100 */
+	{12, 0x1d, 2432}, /* 0000 0001 1101 */
+	{12, 0x1e, 2496}, /* 0000 0001 1110 */
+	{12, 0x1f, 2560}, /* 0000 0001 1111 */
+	{12, 0x1, -1} /* 0000 0000 0001 */
+};
+
+static Tab faxblack[Nfaxtab] = {
+	{10, 0x37, 0}, /* 0000 1101 11 */
+	{3, 0x2, 1}, /* 010 */
+	{2, 0x3, 2}, /* 11 */
+	{2, 0x2, 3}, /* 10 */
+	{3, 0x3, 4}, /* 011 */
+	{4, 0x3, 5}, /* 0011 */
+	{4, 0x2, 6}, /* 0010 */
+	{5, 0x3, 7}, /* 0001 1 */
+	{6, 0x5, 8}, /* 0001 01 */
+	{6, 0x4, 9}, /* 0001 00 */
+	{7, 0x4, 10}, /* 0000 100 */
+	{7, 0x5, 11}, /* 0000 101 */
+	{7, 0x7, 12}, /* 0000 111 */
+	{8, 0x4, 13}, /* 0000 0100 */
+	{8, 0x7, 14}, /* 0000 0111 */
+	{9, 0x18, 15}, /* 0000 1100 0 */
+	{10, 0x17, 16}, /* 0000 0101 11 */
+	{10, 0x18, 17}, /* 0000 0110 00 */
+	{10, 0x8, 18}, /* 0000 0010 00 */
+	{11, 0x67, 19}, /* 0000 1100 111 */
+	{11, 0x68, 20}, /* 0000 1101 000 */
+	{11, 0x6c, 21}, /* 0000 1101 100 */
+	{11, 0x37, 22}, /* 0000 0110 111 */
+	{11, 0x28, 23}, /* 0000 0101 000 */
+	{11, 0x17, 24}, /* 0000 0010 111 */
+	{11, 0x18, 25}, /* 0000 0011 000 */
+	{12, 0xca, 26}, /* 0000 1100 1010 */
+	{12, 0xcb, 27}, /* 0000 1100 1011 */
+	{12, 0xcc, 28}, /* 0000 1100 1100 */
+	{12, 0xcd, 29}, /* 0000 1100 1101 */
+	{12, 0x68, 30}, /* 0000 0110 1000 */
+	{12, 0x69, 31}, /* 0000 0110 1001 */
+	{12, 0x6a, 32}, /* 0000 0110 1010 */
+	{12, 0x6b, 33}, /* 0000 0110 1011 */
+	{12, 0xd2, 34}, /* 0000 1101 0010 */
+	{12, 0xd3, 35}, /* 0000 1101 0011 */
+	{12, 0xd4, 36}, /* 0000 1101 0100 */
+	{12, 0xd5, 37}, /* 0000 1101 0101 */
+	{12, 0xd6, 38}, /* 0000 1101 0110 */
+	{12, 0xd7, 39}, /* 0000 1101 0111 */
+	{12, 0x6c, 40}, /* 0000 0110 1100 */
+	{12, 0x6d, 41}, /* 0000 0110 1101 */
+	{12, 0xda, 42}, /* 0000 1101 1010 */
+	{12, 0xdb, 43}, /* 0000 1101 1011 */
+	{12, 0x54, 44}, /* 0000 0101 0100 */
+	{12, 0x55, 45}, /* 0000 0101 0101 */
+	{12, 0x56, 46}, /* 0000 0101 0110 */
+	{12, 0x57, 47}, /* 0000 0101 0111 */
+	{12, 0x64, 48}, /* 0000 0110 0100 */
+	{12, 0x65, 49}, /* 0000 0110 0101 */
+	{12, 0x52, 50}, /* 0000 0101 0010 */
+	{12, 0x53, 51}, /* 0000 0101 0011 */
+	{12, 0x24, 52}, /* 0000 0010 0100 */
+	{12, 0x37, 53}, /* 0000 0011 0111 */
+	{12, 0x38, 54}, /* 0000 0011 1000 */
+	{12, 0x27, 55}, /* 0000 0010 0111 */
+	{12, 0x28, 56}, /* 0000 0010 1000 */
+	{12, 0x58, 57}, /* 0000 0101 1000 */
+	{12, 0x59, 58}, /* 0000 0101 1001 */
+	{12, 0x2b, 59}, /* 0000 0010 1011 */
+	{12, 0x2c, 60}, /* 0000 0010 1100 */
+	{12, 0x5a, 61}, /* 0000 0101 1010 */
+	{12, 0x66, 62}, /* 0000 0110 0110 */
+	{12, 0x67, 63}, /* 0000 0110 0111 */
+	{10, 0xf, 64}, /* 0000 0011 11 */
+	{12, 0xc8, 128}, /* 0000 1100 1000 */
+	{12, 0xc9, 192}, /* 0000 1100 1001 */
+	{12, 0x5b, 256}, /* 0000 0101 1011 */
+	{12, 0x33, 320}, /* 0000 0011 0011 */
+	{12, 0x34, 384}, /* 0000 0011 0100 */
+	{12, 0x35, 448}, /* 0000 0011 0101 */
+	{13, 0x6c, 512}, /* 0000 0011 0110 0 */
+	{13, 0x6d, 576}, /* 0000 0011 0110 1 */
+	{13, 0x4a, 640}, /* 0000 0010 0101 0 */
+	{13, 0x4b, 704}, /* 0000 0010 0101 1 */
+	{13, 0x4c, 768}, /* 0000 0010 0110 0 */
+	{13, 0x4d, 832}, /* 0000 0010 0110 1 */
+	{13, 0x72, 896}, /* 0000 0011 1001 0 */
+	{13, 0x73, 960}, /* 0000 0011 1001 1 */
+	{13, 0x74, 1024}, /* 0000 0011 1010 0 */
+	{13, 0x75, 1088}, /* 0000 0011 1010 1 */
+	{13, 0x76, 1152}, /* 0000 0011 1011 0 */
+	{13, 0x77, 1216}, /* 0000 0011 1011 1 */
+	{13, 0x52, 1280}, /* 0000 0010 1001 0 */
+	{13, 0x53, 1344}, /* 0000 0010 1001 1 */
+	{13, 0x54, 1408}, /* 0000 0010 1010 0 */
+	{13, 0x55, 1472}, /* 0000 0010 1010 1 */
+	{13, 0x5a, 1536}, /* 0000 0010 1101 0 */
+	{13, 0x5b, 1600}, /* 0000 0010 1101 1 */
+	{13, 0x64, 1664}, /* 0000 0011 0010 0 */
+	{13, 0x65, 1728}, /* 0000 0011 0010 1 */
+	{11, 0x8, 1792}, /* 0000 0001 000 */
+	{11, 0xc, 1856}, /* 0000 0001 100 */
+	{11, 0xd, 1920}, /* 0000 0001 101 */
+	{12, 0x12, 1984}, /* 0000 0001 0010 */
+	{12, 0x13, 2048}, /* 0000 0001 0011 */
+	{12, 0x14, 2112}, /* 0000 0001 0100 */
+	{12, 0x15, 2176}, /* 0000 0001 0101 */
+	{12, 0x16, 2240}, /* 0000 0001 0110 */
+	{12, 0x17, 2304}, /* 0000 0001 0111 */
+	{12, 0x1c, 2368}, /* 0000 0001 1100 */
+	{12, 0x1d, 2432}, /* 0000 0001 1101 */
+	{12, 0x1e, 2496}, /* 0000 0001 1110 */
+	{12, 0x1f, 2560}, /* 0000 0001 1111 */
+	{12, 0x1, -1} /* 0000 0000 0001 */
+};
+
+static Tab faxcodes[] = {
+	{4, 0x1, 0}, /* 0001 */
+	{3, 0x1, 0}, /* 001 */
+	{1, 0x1, 0}, /* 1 */
+	{3, 0x2, 0}, /* 010 */
+	{6, 0x2, 0}, /* 0000 10 */
+	{7, 0x2, 0}, /* 0000 010 */
+	{3, 0x3, 0}, /* 011 */
+	{6, 0x3, 0}, /* 0000 11 */
+	{7, 0x3, 0} /* 0000 011 */
+};
+
+static int typesizes[] = {0, 1, 1, 2, 4, 8};
+static char memerr[] = "WriteTIF: malloc failed";
+
+static int
+put1(Biobuf *b, uchar c)
+{
+	return Bputc(b, c);
+}
+
+static int
+put2(Biobuf *b, uint s)
+{
+	if(put1(b, s>>8) < 0)
+		return -1;
+	return put1(b, s);
+}
+
+static int
+put4(Biobuf *b, ulong l)
+{
+	if(put2(b, l>>16) < 0)
+		return -1;
+	return put2(b, l);
+}
+
+static char *
+nocomp(Tif *)
+{
+	return nil;
+}
+
+static char *
+faxputbyte(Fax *f)
+{
+	if(f->n >= f->ndata) {
+		f->ndata *= 2;
+		f->data = realloc(f->data,
+			f->ndata*sizeof *f->data);
+		if(f->data == nil)
+			return memerr;
+	}
+	f->data[f->n++] = f->byte;
+	f->byte = f->nbyte = 0;
+	return nil;
+}
+
+static char *
+faxputbit(Fax *f, int bit)
+{
+	f->byte = (f->byte << 1) | bit;
+	f->nbyte++;
+	return f->nbyte >= 8? faxputbyte(f): nil;
+}
+
+static char *
+faxbytealign(Fax *f)
+{
+	char *err;
+
+	err = nil;
+	if(f->nbyte != 0) {
+		f->byte <<= 8 - f->nbyte;
+		err = faxputbyte(f);
+	}
+	return err;
+}
+
+static char *
+faxputcode(Fax *f, Tab *tab)
+{
+	int i, bit;
+	char *err;
+
+	for(i = tab->len-1; i >= 0; i--) {
+		bit = (tab->code >> i) & 0x1;
+		if((err = faxputbit(f, bit)) != nil)
+			return err;
+	}
+	return nil;
+}
+
+static int
+faxgettab(int run)
+{
+	if(run >= 0) {
+		if(run <= 64)
+			return run;
+		if(run <= 2560)
+			return 64 + run/64 - 1;
+	}
+	return Nfaxtab - 1;
+}
+
+static char *
+faxputrun(Fax *f, long run)
+{
+	char *err;
+	Tab *tab, *p;
+
+	tab = f->tab[f->st];
+	p = &tab[faxgettab(2560)];
+	while(run >= 2624) {
+		if((err = faxputcode(f, p)) != nil)
+			return err;
+		run -= 2560;
+	}
+	if(run >= 64) {
+		p = &tab[faxgettab(run)];
+		if((err = faxputcode(f, p)) != nil)
+			return err;
+		run -= p->run;
+	}
+	p = &tab[faxgettab(run)];
+	err = faxputcode(f, p);
+	f->st ^= 1;
+	return err;
+}
+
+static char *
+faxputeol(Fax *f)
+{
+	return faxputcode(f, &f->tab[0][faxgettab(-1)]);
+}
+
+static char *
+fax1d(Fax *f, ulong dx)
+{
+	ulong i;
+	long run;
+	char *err;
+
+	f->st = 0;
+	run = f->l2[0];
+	for(i = 0;;) {
+		if((err = faxputrun(f, run)) != nil)
+			return err;
+		if(f->l2[i++] >= dx)
+			break;
+		run = f->l2[i] - f->l2[i-1];
+	}
+	memmove(f->l1, f->l2, i*sizeof *f->l1);
+	return nil;
+}
+
+static char *
+fax2d(Fax *f, ulong dx)
+{
+	int j, v;
+	ulong i;
+	long a0, a1, a2, b1, b2;
+	char *err;
+	Tab *tab, *p;
+
+	f->st = 0;
+	a0 = a1 = -1;
+	tab = faxcodes;
+	for(i = 0, err = nil; err == nil;) {
+		while(a1 <= a0)
+			a1 = f->l2[i++];
+		for(j = 0;; j++) {
+			b1 = f->l1[j];
+			if(b1 > a0 && f->st == j%2)
+				break;
+			if(b1 >= dx)
+				break;
+		}
+		if((b2 = b1) < dx)
+			b2 = f->l1[j+1];
+		if(b2 < a1) {
+			/* pass */
+			p = &tab[0];
+			err = faxputcode(f, p);
+			a0 = b2;
+		} else if(abs(v = a1-b1) < 3) {
+			/* vertical */
+			p = &tab[2+(v>0?3:0)+abs(v)];
+			err = faxputcode(f, p);
+			f->st ^= 1;
+			a0 = a1;
+		} else {
+			/* horizontal */
+			if(a0 < 0)
+				a0 = 0;
+			p = &tab[1];
+			if((err = faxputcode(f, p)) != nil)
+				return err;
+			a2 = a1 < dx? f->l2[i++]: a1;
+			if((err = faxputrun(f, a1-a0)) != nil)
+				return err;
+			err = faxputrun(f, a2-a1);
+			a0 = a2;
+		}
+		if(a0 >= dx)
+			break;
+	}
+	memmove(f->l1, f->l2, i*sizeof *f->l1);
+	return err;
+}
+
+static char *
+faxstrip(Tif *t, Fax *f, uchar *data, ulong n, ulong dx)
+{
+	int k, s, d1, two;
+	ulong i, j, x;
+	char *err;
+
+	d1 = t->comp != Tt6enc;
+	two = 0;
+	if(t->comp == Tt4enc) {
+		if((err = faxputeol(f)) != nil)
+			return err;
+		if(t->opt && (err = faxputbit(f, 1)) != nil)
+			return err;
+	}
+	for(i = j = x = 0; i < n;) {
+		s = 7 - x++%8;
+		k = ((data[i] >> s) & 0x1) ^ 0x1;
+		if(s == 0)
+			i++;
+		if(k != f->st) {
+			f->l2[j++] = x - 1;
+			f->st ^= 1;
+		}
+		if(x == dx) {
+			f->l2[j] = dx;
+			if(d1) {
+				err = fax1d(f, dx);
+				if(t->comp == Tt4enc &&
+					t->opt) {
+					two = Kpar - 1;
+					d1 = 0;
+				}
+			} else {
+				err = fax2d(f, dx);
+				if(two > 0 && --two <= 0)
+					d1 = 1;
+			}
+			if(err != nil)
+				return err;
+			if(t->comp == Thuffman)
+				err = faxbytealign(f);
+			else if(t->comp == Tt4enc &&
+				t->opt) {
+				if((err = faxputeol(f)) != nil)
+					return err;
+				err = faxputbit(f, d1);
+			} else if(t->comp == Tt4enc)
+				err = faxputeol(f);
+			if(err != nil)
+				return err;
+			f->st = 0;
+			if(s != 0)
+				i++;
+			x = 0;
+			j = 0;
+		}
+	}
+	if(t->comp == Tt4enc || t->comp == Tt6enc) {
+		i = t->comp == Tt4enc? 5: 2;
+		for(; i > 0; i--) {
+			if((err = faxputeol(f)) != nil)
+				return err;
+			if(t->comp == Tt4enc && t->opt) {
+				err = faxputbit(f, 1);
+				if(err != nil)
+					return err;
+			}
+		}
+	}
+	return faxbytealign(f);
+}
+
+static char *
+fax(Tif *t)
+{
+	ulong i, m, n;
+	char *err;
+	uchar *data;
+	Fax f;
+
+	f.ndata = t->ndata;
+	if((f.data = malloc(f.ndata*sizeof *f.data)) == nil)
+		return memerr;
+	f.l1 = mallocz((t->dx+1)*sizeof *f.l1, 1);
+	f.l2 = mallocz((t->dx+1)*sizeof *f.l2, 1);
+	if(f.l1 == nil || f.l2 == nil) {
+		free(f.data);
+		if(f.l1 != nil)
+			free(f.l1);
+		if(f.l2 != nil)
+			free(f.l2);
+		return memerr;
+	}
+	f.tab[0] = faxwhite;
+	f.tab[1] = faxblack;
+	f.n = f.byte = f.nbyte = 0;
+	for(i = n = 0, data = t->data; i < t->nstrips; i++) {
+		f.st = 0;
+		f.l1[0] = t->dx;
+		m = t->counts[i];
+		if((err = faxstrip(t, &f, data, m, t->dx)) != nil) {
+			if(f.data != nil)
+				free(f.data);
+			return err;
+		}
+		data += m;
+		t->counts[i] = f.n - n;
+		n = f.n;
+	}
+	free(t->data);
+	free(f.l1);
+	free(f.l2);
+	t->data = f.data;
+	t->ndata = f.n;
+	return nil;
+}
+
+static void
+lzwtabinit(Lzw *l)
+{
+	long i;
+	Hash *hp;
+
+	l->ntab = Eoicode + 1;
+	l->len = 9;
+	hp = &l->hash[Hsize-1];
+	i = Hsize - 8;
+	do {
+		i -= 8;
+		hp[-7].hash = -1;
+		hp[-6].hash = -1;
+		hp[-5].hash = -1;
+		hp[-4].hash = -1;
+		hp[-3].hash = -1;
+		hp[-2].hash = -1;
+		hp[-1].hash = -1;
+		hp[0].hash = -1;
+		hp -= 8;
+	} while(i >= 0);
+	for(i += 8; i > 0; i--, hp--)
+		hp->hash = -1;
+}
+
+static char *
+lzwputbyte(Lzw *l)
+{
+	if(l->n >= l->ndata) {
+		l->ndata *= 2;
+		l->data = realloc(l->data,
+			l->ndata*sizeof *l->data);
+		if(l->data == nil)
+			return memerr;
+	}
+	l->data[l->n++] = l->byte;
+	l->byte = l->nbyte = 0;
+	return nil;
+}
+
+static char *
+lzwbytealign(Lzw *l)
+{
+	char *err;
+
+	err = nil;
+	if(l->nbyte != 0) {
+		l->byte <<= 8 - l->nbyte;
+		err = lzwputbyte(l);
+	}
+	return err;
+}
+
+static char *
+lzwputcode(Lzw *l, int code)
+{
+	int i, c;
+	char *err;
+
+	for(i = l->len-1; i >= 0; i--) {
+		c = (code >> i) & 0x1;
+		l->byte = (l->byte << 1) | c;
+		l->nbyte++;
+		if(l->nbyte >= 8) {
+			if((err = lzwputbyte(l)) != nil)
+				return err;
+		}
+	}
+	return nil;
+}
+
+static void
+predict1(Tif *t)
+{
+	int pix, b[8], d, m, n, j;
+	ulong i, x, y;
+
+	d = *t->depth;
+	m = (1 << d) - 1;
+	n = 8 / d;
+	for(y = 0; y < t->dy; y++) {
+		for(x = t->bpl-1;; x--) {
+			i = y*t->bpl + x;
+			pix = t->data[i];
+			for(j = 0; j < n; j++) {
+				b[j] = (pix >> d*j) & m;
+				if(j > 0)
+					b[j-1] -= b[j];
+			}
+			if(x > 0)
+				b[n-1] -= t->data[i-1] & m;
+			for(j = pix = 0; j < n; j++)
+				pix |= (b[j] & m) << d*j;
+			t->data[i] = pix;
+			if(x == 0)
+				break;
+		}
+	}
+}
+
+static void
+predict8(Tif *t)
+{
+	ulong i, j, s, x, y;
+
+	s = t->samples;
+	for(y = 0; y < t->dy; y++) {
+		for(x = t->dx-1; x >= 1; x--) {
+			i = (y*t->dx + x) * s;
+			for(j = 0; j < s; i++, j++)
+				t->data[i] -= t->data[i-s];
+		}
+	}
+}
+
+static char *
+lzwstrip(Lzw *l, uchar *data, ulong n)
+{
+	int k, h;
+	long fcode, disp;
+	ulong i;
+	char *err;
+	Hash *hp;
+	u16int ent;
+
+	if((err = lzwputcode(l, Clrcode)) != nil)
+		return err;
+	i = 0;
+	ent = data[i++];
+	for(; i < n; i++) {
+		k = data[i];
+		fcode = ((long)k << 12) + ent;
+		h = (k << Hshift) ^ ent;
+		hp = &l->hash[h];
+		if(hp->hash == fcode) {
+hit:
+			ent = hp->code;
+			continue;
+		}
+		if(hp->hash >= 0) {
+			disp = h == 0? 1: Hsize - h;
+			do {
+				if((h -= disp) < 0)
+					h += Hsize;
+				hp = &l->hash[h];
+				if(hp->hash == fcode)
+					goto hit;
+			} while(hp->hash >= 0);
+		}
+		if((err = lzwputcode(l, ent)) != nil)
+			return err;
+		ent = k;
+		hp->hash = fcode;
+		switch(hp->code = l->ntab) {
+		case 511:
+		case 1023:
+		case 2047:
+			l->len++;
+			break;
+		default:
+			break;
+		}
+		if(l->ntab++ >= Tabsz-2) {
+			err = lzwputcode(l, Clrcode);
+			if(err != nil)
+				return err;
+			lzwtabinit(l);
+		}
+	}
+	if((err = lzwputcode(l, ent)) != nil)
+		return err;
+	if((err = lzwputcode(l, Eoicode)) != nil)
+		return err;
+	return lzwbytealign(l);
+}
+
+static char *
+lzw(Tif *t)
+{
+	ulong i, m, n;
+	char *err;
+	uchar *data;
+	Lzw l;
+
+	if(t->opt)
+		*t->depth < 8? predict1(t): predict8(t);
+	l.ndata = t->ndata;
+	if((l.data = malloc(l.ndata*sizeof *l.data)) == nil)
+		return memerr;
+	l.n = l.byte = l.nbyte = 0;
+	err = nil;
+	for(i = n = 0, data = t->data; i < t->nstrips; i++) {
+		lzwtabinit(&l);
+		m = t->counts[i];
+		if((err = lzwstrip(&l, data, m)) != nil)
+			break;
+		data += m;
+		t->counts[i] = l.n - n;
+		n = l.n;
+	}
+	if(err != nil) {
+		if(l.data != nil)
+			free(l.data);
+		return err;
+	}
+	free(t->data);
+	t->data = l.data;
+	t->ndata = l.n;
+	return nil;
+}
+
+static char *
+pkbrow(Pkb *p, uchar *data, int ndata, long *buf)
+{
+	int b, repl;
+	long i, j, k, n;
+
+	i = n = 0;
+	buf[n++] = i;
+	b = data[i++];
+	if(i < ndata)
+		repl = b == data[i]? 1: 0;
+	else
+		repl = 0;
+	for(; i < ndata; i++) {
+		k = data[i];
+		j = labs(buf[n-1]);
+		if(repl) {
+			if(b != k) {
+				repl ^= 1;
+				buf[n++] = -i;
+			}
+		} else {
+			if(b == k) {
+				repl ^= 1;
+				if(i-j > 1)
+					buf[n++] = i - 1;
+			}
+		}
+		b = k;
+	}
+	buf[n++] = repl? -i: i;
+	for(i = 1; i < n;) {
+		k = buf[i];
+		j = labs(buf[i-1]);
+		if(i < n-2 && k > 0 && buf[i+1] < 0 &&
+			buf[i+2] > 0 && -buf[i+1]-k <= 2) {
+			buf[i] = buf[i+1] = buf[i+2];
+			continue;
+		}
+		if((b = labs(k) - j) > 128) {
+			b = 128;
+			buf[i-1] += buf[i-1] < 0? -b: b;
+		} else
+			i++;
+		if(b == 0)
+			continue;
+		if(p->n+1+(k<0?1:b) > p->ndata) {
+			p->ndata *= 2;
+			p->data = realloc(p->data,
+				p->ndata*sizeof *p->data);
+			if(p->data == nil)
+				return memerr;
+		}
+		if(k < 0) {
+			p->data[p->n++] = 1 - b;
+			p->data[p->n++] = data[j];
+		} else {
+			p->data[p->n++] = b - 1;
+			memmove(p->data+p->n, data+j, b);
+			p->n += b;
+		}
+	}
+	return nil;
+}
+
+static char *
+packbits(Tif *t)
+{
+	ulong i, j, n;
+	char *err;
+	uchar *data;
+	long *buf;
+	Pkb p;
+
+	p.ndata = t->ndata;
+	if((p.data = malloc(p.ndata*sizeof *p.data)) == nil)
+		return memerr;
+	if((buf = malloc((t->bpl+1)*sizeof *buf)) == nil) {
+		free(p.data);
+		return memerr;
+	}
+	p.n = 0;
+	data = t->data;
+	for(i = j = n = 0, err = nil; i < t->dy; i++) {
+		if((err = pkbrow(&p, data, t->bpl, buf)) != nil)
+			break;
+		data += t->bpl;
+		if(i%t->rows == t->rows-1) {
+			t->counts[j++] = p.n - n;
+			n = p.n;
+		}
+	}
+	free(buf);
+	if(err != nil) {
+		if(p.data != nil)
+			free(p.data);
+		return err;
+	}
+	if(j < t->nstrips)
+		t->counts[j] = p.n - n;
+	free(t->data);
+	t->data = p.data;
+	t->ndata = p.n;
+	return nil;
+}
+
+static char *
+alloctif(Tif *t)
+{
+	int rgb;
+	ulong i, count, n;
+	double a, b;
+
+	count = t->ndata < 0x2000? t->ndata: 0x2000;
+	t->rows = count / t->bpl;
+	if(count%t->bpl != 0)
+		t->rows++;
+	if(t->comp == Tt4enc && t->opt) {
+		if((n = t->rows%Kpar) != 0)
+			t->rows += Kpar - n;
+	}
+	a = (double)t->dy;
+	b = (double)t->rows;
+	t->nstrips = (ulong)floor((a+b-1)/b);
+	t->strips = malloc(t->nstrips*sizeof *t->strips);
+	if(t->strips == nil)
+		return memerr;
+	t->counts = malloc(t->nstrips*sizeof *t->counts);
+	if(t->counts == nil) {
+		free(t->strips);
+		return memerr;
+	}
+	if(t->ncolor > 0) {
+		t->color = malloc(t->ncolor*sizeof *t->color);
+		if(t->color == nil) {
+			free(t->strips);
+			free(t->counts);
+			return memerr;
+		}
+		for(i = 0; i < 256; i++) {
+			rgb = cmap2rgb(i);
+			t->color[i] = (rgb >> 16) & 0xff;
+			t->color[i+256] = (rgb >> 8) & 0xff;
+			t->color[i+256*2] = rgb & 0xff;
+		}
+	}
+	count = t->rows * t->bpl;
+	for(i = 0, n = t->ndata; i < t->nstrips-1; i++) {
+		t->counts[i] = count;
+		n -= count;
+	}
+	t->counts[i] = n;
+	return nil;
+}
+
+static void
+freetif(Tif *t)
+{
+	free(t->strips);
+	free(t->counts);
+	if(t->color != nil)
+		free(t->color);
+	free(t->data);
+}
+
+static int
+typesize(int fld)
+{
+	return typesizes[flds[fld].typ];
+}
+
+static void
+writefld(Biobuf *fd, int fld, ulong cnt, ulong val)
+{
+	put2(fd, flds[fld].tag);
+	put2(fd, flds[fld].typ);
+	put4(fd, cnt);
+	put4(fd, val);
+}
+
+static void
+writeflds(Biobuf *fd, Tif *t)
+{
+	int n;
+	ulong i, off, slen, s, offs[7];
+
+	slen = t->desc == nil? 0: strlen(t->desc) + 1;
+	put2(fd, 0x4d4d);
+	put2(fd, 0x002a);
+	off = 0x00000008;
+	memset(offs, 0, sizeof offs);
+	n = 0;
+	offs[n++] = off;
+	if(t->samples > 1) {
+		off += t->samples * typesize(Tbits);
+		offs[n++] = off;
+	}
+	if(slen > 4) {
+		off += slen * typesize(Tdesc);
+		offs[n++] = off;
+	}
+	if(t->nstrips > 1) {
+		off += t->nstrips * typesize(Tstrips);
+		offs[n++] = off;
+		off += t->nstrips * typesize(Tcounts);
+		offs[n++] = off;
+	}
+	off += typesize(Txres);
+	offs[n++] = off;
+	off += typesize(Tyres);
+	offs[n] = off;
+	if(t->color != nil)
+		off += t->ncolor * typesize(Tcolor);
+	for(i = 0; i < t->nstrips-1; i++) {
+		t->strips[i] = off;
+		off += t->counts[i];
+	}
+	t->strips[i] = off;
+	off += t->counts[i];
+	put4(fd, off);
+	if(t->samples > 1) {
+		for(i = 0; i < t->samples; i++)
+			put2(fd, t->depth[i]);
+	}
+	if(slen > 4) {
+		Bwrite(fd, t->desc, slen-1);
+		put1(fd, 0x00);
+	}
+	if(t->nstrips > 1) {
+		for(i = 0; i < t->nstrips; i++)
+			put4(fd, t->strips[i]);
+		for(i = 0; i < t->nstrips; i++)
+			put4(fd, t->counts[i]);
+	}
+	put4(fd, t->dx);
+	put4(fd, 0x00000004);
+	put4(fd, t->dy);
+	put4(fd, 0x00000004);
+	if(t->color != nil) {
+		for(i = 0; i < t->ncolor; i++)
+			put2(fd, t->color[i]);
+	}
+	Bwrite(fd, t->data, t->ndata);
+	n = 0;
+	put2(fd, t->nfld);
+	writefld(fd, Twidth, 1, t->dx);
+	writefld(fd, Tlength, 1, t->dy);
+	if(t->samples > 1)
+		writefld(fd, Tbits, t->samples, offs[n++]);
+	else
+		writefld(fd, Tbits, t->samples, *t->depth<<16);
+	writefld(fd, Tcomp, 1, t->comp<<16);
+	writefld(fd, Tphoto, 1, t->photo<<16);
+	if(t->comp >= 2 && t->comp <= 4)
+		writefld(fd, Tfill, 1, 1<<16);
+	if(slen > 1) {
+		if(slen <= 4) {
+			for(i = s = 0; i < slen-1; i++)
+				s = (s << 8) | t->desc[i];
+			s <<= 8;
+			writefld(fd, Tdesc, slen, s);
+		} else
+			writefld(fd, Tdesc, slen, offs[n++]);
+	}
+	if(t->nstrips > 1)
+		writefld(fd, Tstrips, t->nstrips, offs[n++]);
+	else
+		writefld(fd, Tstrips, t->nstrips, *t->strips);
+	if(t->samples > 1)
+		writefld(fd, Tsamples, 1, t->samples<<16);
+	writefld(fd, Trows, 1, t->rows);
+	if(t->nstrips > 1)
+		writefld(fd, Tcounts, t->nstrips, offs[n++]);
+	else
+		writefld(fd, Tcounts, t->nstrips, *t->counts);
+	writefld(fd, Txres, 1, offs[n++]);
+	writefld(fd, Tyres, 1, offs[n++]);
+	if(t->comp == Tt4enc && t->opt)
+		writefld(fd, T4opt, 1, 1);
+	writefld(fd, Tresunit, 1, 2<<16);
+	if(t->comp == Tlzw && t->opt)
+		writefld(fd, Tpredictor, 1, 2<<16);
+	if(t->color != nil)
+		writefld(fd, Tcolor, t->ncolor, offs[n]);
+	put4(fd, 0x00000000);
+}
+
+static char *
+writedata(Biobuf *fd, Image *i, Memimage *m, Tif *t)
+{
+	char *err;
+	uchar *data;
+	int j, ndata, depth;
+	Rectangle r;
+
+	if(m != nil) {
+		r = m->r;
+		depth = m->depth;
+	} else {
+		r = i->r;
+		depth = i->depth;
+	}
+	t->dx = Dx(r);
+	t->dy = Dy(r);
+	for(j = 0; j < t->samples; j++)
+		t->depth[j] = depth / t->samples;
+	/*
+	* potentially one extra byte on each
+	* end of each scan line
+	*/
+	ndata = t->dy * (2 + t->dx*depth/8);
+	if((data = malloc(ndata)) == nil)
+		return memerr;
+	if(m != nil)
+		ndata = unloadmemimage(m, r, data, ndata);
+	else
+		ndata = unloadimage(i, r, data, ndata);
+	if(ndata < 0) {
+		free(data);
+		if((err = malloc(ERRMAX*sizeof *err)) == nil)
+			return memerr;
+		snprint(err, ERRMAX, "WriteTIF: %r");
+	} else {
+		t->data = data;
+		t->ndata = ndata;
+		t->bpl = bytesperline(r, depth);
+		err = alloctif(t);
+		if(err != nil) {
+			freetif(t);
+			return err;
+		}
+		if((err = (*t->compress)(t)) == nil)
+			writeflds(fd, t);
+		freetif(t);
+	}
+	return err;
+}
+
+static char *
+writetif0(Biobuf *fd, Image *image, Memimage *memimage,
+	ulong chan, char *s, int comp, int opt)
+{
+	Tif t;
+
+	t.nfld = 11;
+	t.color = nil;
+	if((t.desc = s) != nil)
+		t.nfld++;
+	t.opt = opt;
+	t.comp = comp;
+	switch(chan) {
+	case GREY1:
+	case GREY4:
+	case GREY8:
+		t.photo = 1;
+		t.samples = 1;
+		t.ncolor = 0;
+		break;
+	case CMAP8:
+		t.photo = 3;
+		t.samples = 1;
+		t.ncolor = 3 * 256;
+		t.nfld++;
+		break;
+	case BGR24:
+		t.photo = 2;
+		t.samples = 3;
+		t.ncolor = 0;
+		t.nfld++;
+		break;
+	default:
+		return "WriteTIF: can't handle channel type";
+	}
+	switch(t.comp) {
+	case Tnocomp:
+		t.compress = nocomp;
+		break;
+	case Thuffman:
+	case Tt4enc:
+	case Tt6enc:
+		t.photo = 0;
+		t.nfld++;
+		if(t.comp == Tt4enc && t.opt)
+			t.nfld++;
+		t.compress = fax;
+		break;
+	case Tlzw:
+		t.compress = lzw;
+		if(t.opt)
+			t.nfld++;
+		break;
+	case Tpackbits:
+		t.compress = packbits;
+		break;
+	default:
+		return "WriteTIF: unknown compression";
+	}
+	return writedata(fd, image, memimage, &t);
+}
+
+char *
+writetif(Biobuf *fd, Image *i, char *s, int comp, int opt)
+{
+	return writetif0(fd, i, nil, i->chan, s, comp, opt);
+}
+
+char *
+memwritetif(Biobuf *fd, Memimage *m, char *s, int comp, int opt)
+{
+	return writetif0(fd, nil, m, m->chan, s, comp, opt);
+}