Why not?
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;
+}