shithub: riscv

Download patch

ref: 723018c1deb3d160b86cfaf0be098d97c52b75a1
parent: 8cce104fcb63352a6c297f9d2b48f702d46f3412
parent: 410ce8feeca8e2559f05d940a2fdbaab374b490c
author: cinap_lenrek <[email protected]>
date: Tue May 28 19:42:59 EDT 2013

merge

--- /dev/null
+++ b/rc/bin/togeordi
@@ -1,0 +1,2 @@
+#!/bin/rc
+tojpg -s $*
--- a/sys/man/1/jpg
+++ b/sys/man/1/jpg
@@ -1,6 +1,6 @@
 .TH JPG 1
 .SH NAME
-jpg, gif, png, ppm, bmp, v210, yuv, ico, tga, togif, toppm, topng, toico \- view and convert pictures
+jpg, gif, png, ppm, bmp, v210, yuv, ico, tga, tojpg, togeordi, togif, toppm, topng, toico \- view and convert pictures
 .SH SYNOPSIS
 .B jpg
 [
@@ -58,6 +58,26 @@
 .I file ...
 ]
 .PP
+.B tojpg
+[
+.B -c
+.I comment
+] [
+.B -gs
+] [
+.I file
+]
+.br
+.B togeordi
+[
+.B -c
+.I comment
+] [
+.B -g
+] [
+.I file
+]
+.br
 .B togif
 [
 .B -c
@@ -126,11 +146,12 @@
 read files in the corresponding formats and, by default, display
 them in the current window; options cause them instead to convert the images
 to Plan 9 image format and write them to standard output.
-.IR Togif ,
+.IR Tojpg ,
+.IR togif ,
 .IR toppm ,
 and
 .I topng
-read Plan 9 images files, convert them to GIF, PPM, or PNG, and write them to standard output.
+read Plan 9 images files, convert them to JPEG, GIF, PPM, or PNG, and write them to standard output.
 .PP
 The default behavior of
 .IR jpg ,
@@ -205,12 +226,13 @@
 .PD
 .PP
 The
+.IR tojpg ,
 .IR togif
 and
 .IR toppm
-programs go the other way: they convert from Plan 9 images to GIF and PPM,
+programs go the other way: they convert from Plan 9 images to JPEG, GIF and PPM,
 and have no display capability.
-Both accept an option
+They all accept an option
 .B -c
 to set the comment field of the resulting file.
 The
@@ -219,6 +241,20 @@
 .I toppm
 output raw PPM.
 The default is to output plain PPM.
+The
+.B -g
+option makes
+.I tojpg
+output grayscale images,
+and the
+.B -s
+option makes it output scratched JPEG images.
+.I Togeordi
+is an
+.IR rc (1)
+script that invokes
+.B tojpg
+.BR -s .
 If there is only one input picture,
 .I togif
 converts the image to GIF format.
@@ -269,11 +305,19 @@
 space in the image.  The icon file is written to standard output.
 .SH SOURCE
 .B /sys/src/cmd/jpg
+.br
+.B /rc/bin/togeordi
 .SH "SEE ALSO"
 .IR page (1),
 .IR image (6).
 .br
+.B http://www.w3.org/Graphics/JPEG/jfif3.pdf
+.br
 .B http://www.w3.org/Graphics/JPEG/itu-t81.pdf
+.br
+.B http://en.wikibooks.org/wiki/JPEG_-_Idea_and_Practice
+.br
+.B http://en.wikipedia.org/wiki/JPEG
 .br
 .B http://www.w3.org/Graphics/GIF/spec-gif89a.txt
 .br
--- a/sys/src/9/port/segment.c
+++ b/sys/src/9/port/segment.c
@@ -333,7 +333,7 @@
 	 */
 	for(p = palloc.tail; p && p->image && (n<1000 || !imagealloc.free); p = p->prev) {
 		if(p->ref == 0 && canlock(p)) {
-			if(p->ref == 0) {
+			if(p->ref == 0 && p->image && !p->image->notext) {
 				n++;
 				uncachepage(p);
 			}
--- a/sys/src/cmd/jpg/imagefile.h
+++ b/sys/src/cmd/jpg/imagefile.h
@@ -77,3 +77,6 @@
 Memimage*	memmultichan(Memimage*);
 
 char*		memwritepng(Biobuf*, Memimage*, ImageInfo*);
+
+char*		writejpg(Biobuf*, Image*, char*, int, int);
+char*		memwritejpg(Biobuf*, Memimage*, char*, int, int);
--- a/sys/src/cmd/jpg/mkfile
+++ b/sys/src/cmd/jpg/mkfile
@@ -1,6 +1,8 @@
 </$objtype/mkfile
 
-TARG=jpg\
+TARG=\
+	jpg\
+	tojpg\
 	gif\
 	togif\
 	ppm\
@@ -46,6 +48,7 @@
 
 $O.tga:		$IMFILES readtga.$O tga.$O
 $O.jpg:		$IMFILES readjpg.$O jpg.$O
+$O.tojpg:	writejpg.$O multichan.$O tojpg.$O
 $O.gif:		$IMFILES readgif.$O gif.$O
 $O.togif:	writegif.$O onechan.$O togif.$O torgbv.$O
 $O.ppm:		$IMFILES readppm.$O ppm.$O
--- /dev/null
+++ b/sys/src/cmd/jpg/tojpg.c
@@ -1,0 +1,70 @@
+#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: %s [-c 'comment'] [-gs] [file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Biobuf bout;
+	Memimage *i, *ni;
+	int fd, gflag, sflag;
+	char *err, *file, *com;
+
+	gflag = sflag = 0;
+	com = nil;
+	ARGBEGIN {
+	case 'c':
+		com = EARGF(usage());
+		break;
+	case 'g':
+		gflag = 1;
+		break;
+	case 's':
+		sflag = 1;
+		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("readimage %s: %r", file);
+	close(fd);
+	if((ni = memmultichan(i)) == nil)
+		sysfatal("converting image to RGB24: %r");
+	if(i != ni) {
+		freememimage(i);
+		i = ni;
+	}
+	err = memwritejpg(&bout, i, com, gflag, sflag);
+	freememimage(i);
+
+	if(err != nil)
+		fprint(2, "%s: %s\n", argv0, err);
+	exits(err);
+}
--- /dev/null
+++ b/sys/src/cmd/jpg/writejpg.c
@@ -1,0 +1,912 @@
+/*
+* code/documentation from:
+* http://www.w3.org/Graphics/JPEG/jfif3.pdf
+* http://www.w3.org/Graphics/JPEG/itu-t81.pdf
+* http://en.wikipedia.org/wiki/JPEG
+* http://en.wikibooks.org/wiki/JPEG_-_Idea_and_Practice
+* http://code.google.com/p/go/source/browse/src/pkg/image/jpeg/writer.go
+* /sys/src/cmd/jpg
+*
+* fdct code from:
+* http://www.ijg.org/files/jpegsrc.v8c.tar.gz
+* http://code.google.com/p/go/source/browse/src/pkg/image/jpeg/fdct.go
+*/
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <bio.h>
+#include "imagefile.h"
+
+/*
+* 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
+};
+
+/* fdct constants */
+enum {
+	Fix02 = 2446, /* 0.298631336 */
+	Fix03 = 3196, /* 0.390180644 */
+	Fix05 = 4433, /* 0.541196100 */
+	Fix07 = 6270, /* 0.765366865 */
+	Fix08 = 7373, /* 0.899976223 */
+	Fix11 = 9633, /* 1.175875602 */
+	Fix15 = 12299, /* 1.501321110 */
+	Fix18 = 15137, /* 1.847759065 */
+	Fix19 = 16069, /* 1.961570560 */
+	Fix20 = 16819, /* 2.053119869 */
+	Fix25 = 20995, /* 2.562915447 */
+	Fix30 = 25172 /* 3.072711026 */
+};
+
+static int zigzag[64] = {
+	0, 1, 5, 6, 14, 15, 27, 28,
+	2, 4, 7, 13, 16, 26, 29, 42,
+	3, 8, 12, 17, 25, 30, 41, 43,
+	9, 11, 18, 24, 31, 40, 44, 53,
+	10, 19, 23, 32, 39, 45, 52, 54,
+	20, 22, 33, 38, 46, 51, 55, 60,
+	21, 34, 37, 47, 50, 56, 59, 61,
+	35, 36, 48, 49, 57, 58, 62, 63
+};
+
+static int invzigzag[64] = {
+	0, 1, 8, 16, 9, 2, 3, 10,
+	17, 24, 32, 25, 18, 11, 4, 5,
+	12, 19, 26, 33, 40, 48, 41, 34,
+	27, 20, 13, 6, 7, 14, 21, 28,
+	35, 42, 49, 56, 57, 50, 43, 36,
+	29, 22, 15, 23, 30, 37, 44, 51,
+	58, 59, 52, 45, 38, 31, 39, 46,
+	53, 60, 61, 54, 47, 55, 62, 63
+};
+
+/* section K.1 for quantization tables */
+static int qt[2][64] = {
+	/* luminance */
+	{16, 11, 10, 16, 24, 40, 51, 61,
+	12, 12, 14, 19, 26, 58, 60, 55,
+	14, 13, 16, 24, 40, 57, 69, 56,
+	14, 17, 22, 29, 51, 87, 80, 62,
+	18, 22, 37, 56, 68, 109, 103, 77,
+	24, 35, 55, 64, 81, 104, 113, 92,
+	49, 64, 78, 87, 103, 121, 120, 101,
+	72, 92, 95, 98, 112, 100, 103, 99},
+
+	/* chrominance */
+	{17, 18, 24, 47, 99, 99, 99, 99,
+	18, 21, 26, 66, 99, 99, 99, 99,
+	24, 26, 56, 99, 99, 99, 99, 99,
+	47, 66, 99, 99, 99, 99, 99, 99,
+	99, 99, 99, 99, 99, 99, 99, 99,
+	99, 99, 99, 99, 99, 99, 99, 99,
+	99, 99, 99, 99, 99, 99, 99, 99,
+	99, 99, 99, 99, 99, 99, 99, 99}
+};
+
+/* section K.3.3 for huffman tables */
+static int dcbits[2][16] = {
+	/* luminance */
+	{0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+
+	/* chrominance */
+	{0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+static int dchuffval[2][12] = {
+	/* luminance */
+	{0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+	0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b},
+
+	/* chrominance */
+	{0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+	0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
+};
+
+static int acbits[2][16] = {
+	/* luminance */
+	{0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03,
+	0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d},
+
+	/* chrominance */
+	{0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04,
+	0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77}
+};
+
+static int achuffval[2][162] = {
+	/* luminance */
+	{0x01, 0x02, 0x03, 0x00, 0x04, 0x11,
+	0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+	0x13, 0x51, 0x61, 0x07, 0x22, 0x71,
+	0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+	0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,
+	0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
+	0x82, 0x09, 0x0a, 0x16, 0x17, 0x18,
+	0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+	0x29, 0x2a, 0x34, 0x35, 0x36, 0x37,
+	0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
+	0x46, 0x47, 0x48, 0x49, 0x4a, 0x53,
+	0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+	0x5a, 0x63, 0x64, 0x65, 0x66, 0x67,
+	0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
+	0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
+	0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
+	0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
+	0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+	0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+	0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
+	0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
+	0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
+	0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+	0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
+	0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
+	0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa},
+
+	/* chrominance */
+	{0x00, 0x01, 0x02, 0x03, 0x11, 0x04,
+	0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
+	0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
+	0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+	0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
+	0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1,
+	0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
+	0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+	0x27, 0x28, 0x29, 0x2a, 0x35, 0x36,
+	0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
+	0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+	0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+	0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
+	0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
+	0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+	0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+	0x88, 0x89, 0x8a, 0x92, 0x93, 0x94,
+	0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
+	0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+	0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+	0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba,
+	0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+	0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+	0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+	0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+	0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
+	0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}
+};
+
+static int ehufcod[2][12]; /* dc codes */
+static int ehufsid[2][12]; /* dc sizes */
+static int ehufcoa[2][251]; /* ac codes */
+static int ehufsia[2][251]; /* ac sizes */
+
+static int byte;
+static int nbyte;
+
+/* utilities */
+static int Bputs(Biobuf *, int);
+static int min(int, int);
+
+/* encoding */
+static void grey2rgb(int *, int *, int *, int, int);
+static void rgb2ycc(int *, int *, int *, int, int, int);
+static void fdct(int *, int);
+static int csize(int, int);
+static void writebyte(Biobuf *);
+static void writebits(Biobuf *, int, int);
+static void writecode(Biobuf *, int, int);
+static int huf(Biobuf *, int *, int, int, int);
+static char *toycc1(int *, int *, int *, int, int, int, int, int,
+	uchar *, int, int);
+static char *toycc2(int *, int *, int *, int, int, int, int, int,
+	uchar *, int, int);
+static char *encode(Biobuf *, Rectangle, uchar *, ulong, int,
+	int, int);
+
+/* huffman tables */
+static void makehuf(int *, int *, int *, int *, int);
+
+/* tables, markers, headers, trailers */
+static void writejfif(Biobuf *, int, int);
+static void writecomment(Biobuf *, char *);
+static void writequant(Biobuf *, int, int);
+static void writehuffman(Biobuf *, int, int);
+static void writeframe(Biobuf *, int, int, int);
+static void writescan(Biobuf *, int);
+static void writeheader(Biobuf *, int, int, char *, int, int);
+static void writetrailer(Biobuf *);
+static char *writedata(Biobuf *, Image *, Memimage *, int, int);
+static char *writejpg0(Biobuf *, Image *, Memimage *,
+	Rectangle, ulong, char *, int, int);
+
+static int
+Bputs(Biobuf *b, int s)
+{
+	if(Bputc(b, s>>8) < 0)
+		return -1;
+	return Bputc(b, s);
+}
+
+static int
+min(int a, int b)
+{
+	return a < b? a: b;
+}
+
+static void
+grey2rgb(int *r, int *g, int *b, int c, int depth)
+{
+	if(depth == 1) {
+		if(c != 0)
+			c = 0xff;
+	} else if(depth == 2)
+		c = (c << 6) | (c << 4) | (c << 2) | c;
+	else
+		c = (c << 4) | c;
+	c = cmap2rgb(c);
+	*r = (c >> 16) & 0xff;
+	*g = (c >> 8) & 0xff;
+	*b = c & 0xff;
+}
+
+static void
+rgb2ycc(int *y, int *cb, int *cr, int r, int g, int b)
+{
+	*y = (int)(0.299*r + 0.587*g + 0.114*b);
+	*cb = (int)(128.0 - 0.1687*r - 0.3313*g + 0.5*b);
+	*cr = (int)(128.0 + 0.5*r - 0.4187*g - 0.0813*b);
+}
+
+/* coefficients remain scaled up by 8 at the end */
+static void
+fdct(int *b, int sflag)
+{
+	int x, y, z, tmp0, tmp1, tmp2, tmp3;
+	int tmp10, tmp12, tmp11, tmp13;
+
+	/* rows */
+	for(y = 0; y < 8; y++) {
+		tmp0 = b[y*8+0] + b[y*8+7];
+		tmp1 = b[y*8+1] + b[y*8+6];
+		tmp2 = b[y*8+2] + b[y*8+5];
+		tmp3 = b[y*8+3] + b[y*8+4];
+
+		tmp10 = tmp0 + tmp3;
+		tmp12 = tmp0 - tmp3;
+		tmp11 = tmp1 + tmp2;
+		tmp13 = tmp1 - tmp2;
+
+		tmp0 = b[y*8+0] - b[y*8+7];
+		tmp1 = b[y*8+1] - b[y*8+6];
+		tmp2 = b[y*8+2] - b[y*8+5];
+		tmp3 = b[y*8+3] - b[y*8+4];
+
+		b[y*8+0] = (tmp10 + tmp11 - 8*128) << 2;
+		b[y*8+4] = (tmp10 - tmp11) << 2;
+
+		z = (tmp12 + tmp13) * Fix05;
+		z += 1 << 10;
+		b[y*8+2] = (z + tmp12*Fix07) >> 11;
+		b[y*8+6] = (z - tmp13*Fix18) >> 11;
+		
+		tmp10 = tmp0 + tmp3;
+		tmp11 = tmp1 + tmp2;
+		tmp12 = tmp0 + tmp2;
+		tmp13 = tmp1 + tmp3;
+		z = (tmp12 + tmp13) * Fix11;
+		z += 1 << 10;
+
+		tmp0 *= Fix15;
+		tmp1 *= Fix30;
+		tmp2 *= Fix20;
+		tmp3 *= Fix02;
+		tmp10 *= -Fix08;
+		tmp11 *= -Fix25;
+		tmp12 *= -Fix03;
+		tmp13 *= -Fix19;
+
+		tmp12 += z;
+		tmp13 += z;
+
+		b[y*8+1] = (tmp0 + tmp10 + tmp12) >> 11;
+		b[y*8+3] = (tmp1 + tmp11 + tmp13) >> 11;
+		b[y*8+5] = (tmp2 + tmp11 + tmp12) >> 11;
+		b[y*8+7] = (tmp3 + tmp10 + tmp13) >> 11;
+	}
+	/* columns */
+	for(x = 0; x < 8; x++) {
+		tmp0 = b[0*8+x] + b[7*8+x];
+		tmp1 = b[1*8+x] + b[6*8+x];
+		tmp2 = b[2*8+x] + b[5*8+x];
+		tmp3 = b[3*8+x] + b[4*8+x];
+
+		if(sflag)
+			tmp10 = (tmp0 + tmp3 + 1) << 1;
+		else
+			tmp10 = tmp0 + tmp3 + (1 << 1);
+		tmp12 = tmp0 - tmp3;
+		tmp11 = tmp1 + tmp2;
+		tmp13 = tmp1 - tmp2;
+
+		tmp0 = b[0*8+x] - b[7*8+x];
+		tmp1 = b[1*8+x] - b[6*8+x];
+		tmp2 = b[2*8+x] - b[5*8+x];
+		tmp3 = b[3*8+x] - b[4*8+x];
+		
+		b[0*8+x] = (tmp10 + tmp11) >> 2;
+		b[4*8+x] = (tmp10 - tmp11) >> 2;
+
+		z = (tmp12 + tmp13) * Fix05;
+		z += 1 << 14;
+		b[2*8+x] = (z + tmp12*Fix07) >> 15;
+		b[6*8+x] = (z - tmp13*Fix18) >> 15;
+
+		tmp10 = tmp0 + tmp3;
+		tmp11 = tmp1 + tmp2;
+		tmp12 = tmp0 + tmp2;
+		tmp13 = tmp1 + tmp3;
+		z = (tmp12 + tmp13) * Fix11;
+		z += 1 << 14;
+		
+		tmp0 *= Fix15;
+		tmp1 *= Fix30;
+		tmp2 *= Fix20;
+		tmp3 *= Fix02;
+		tmp10 *= -Fix08;
+		tmp11 *= -Fix25;
+		tmp12 *= -Fix03;
+		tmp13 *= -Fix19;
+		
+		tmp12 += z;
+		tmp13 += z;
+
+		b[1*8+x] = (tmp0 + tmp10 + tmp12) >> 15;
+		b[3*8+x] = (tmp1 + tmp11 + tmp13) >> 15;
+		b[5*8+x] = (tmp2 + tmp11 + tmp12) >> 15;
+		b[7*8+x] = (tmp3 + tmp10 + tmp13) >> 15;
+	}
+}
+
+static int
+csize(int coeff, int ac)
+{
+	int i, max;
+
+	max = 1 << 10;
+	if(!ac)
+		max <<= 1;
+	if(coeff < 0)
+		coeff *= -1;
+	if(coeff > max)
+		sysfatal("csize: coeff too big: %d", coeff);
+	i = ac? 1: 0;
+	while(coeff >= (1<<i))
+		i++;
+	if(ac && (i < 1 || i > 10))
+		sysfatal("csize: invalid ac ssss: %d", i);
+	if(!ac && (i < 0 || i > 11))
+		sysfatal("csize: invalid dc ssss: %d", i);
+	return i;
+}
+
+static void
+writebyte(Biobuf *fd)
+{
+	Bputc(fd, byte);
+	if(byte == 0xff) /* byte stuffing */
+		Bputc(fd, 0x00);
+	byte = 0;
+	nbyte = 7;
+}
+
+static void
+writebits(Biobuf *fd, int co, int si)
+{
+	int i, bit;
+
+	for(i = si-1; i >= 0; i--) {
+		bit = (co >> i) & 0x1;
+		byte |= bit << nbyte;
+		nbyte--;
+		if(nbyte < 0)
+			writebyte(fd);
+	}
+}
+
+static void
+writecode(Biobuf *fd, int co, int si)
+{
+	if(si > 8) {
+		writebits(fd, co>>8, si-8);
+		si = 8;
+	}
+	writebits(fd, co, si);
+}
+
+static int
+huf(Biobuf *fd, int *b, int pred, int chr, int sflag)
+{
+	int k, r, s, rs, si, co, dc, diff, zz[64], p, q, z;
+
+	if(sflag) {
+		for(k = 0; k < 64; k++) {
+			p = b[zigzag[k]];
+			q = qt[chr][zigzag[k]];
+			zz[k] = p / q;
+		}
+	} else {
+		for(k = 0; k < 64; k++) {
+			p = b[k];
+			q = (qt[chr][k] << 3);
+			/* rounding */
+			if(p >= 0)
+				z = (p + (q >> 1)) / q;
+			else
+				z = -(-p + (q >> 1)) / q;
+			zz[zigzag[k]] = z;
+		}
+	}
+
+	/* dc coefficient */
+	dc = zz[0];
+	zz[0] = diff = dc - pred;
+
+	s = csize(diff, 0);
+	si = ehufsid[chr][s];
+	co = ehufcod[chr][s];
+	writecode(fd, co, si);
+	if(diff < 0)
+		diff -= 1;
+	writecode(fd, diff, s);
+
+	/* figure F.2 */
+	for(k = 1, r = 0; k < 64; k++) {
+		if(zz[k] == 0) {
+			if(k < 63)
+				r++;
+			else {
+				si = ehufsia[chr][0x00];
+				co = ehufcoa[chr][0x00];
+				writecode(fd, co, si);
+			}
+		} else {
+			while(r > 15) {
+				si = ehufsia[chr][0xf0];
+				co = ehufcoa[chr][0xf0];
+				writecode(fd, co, si);
+				r -= 16;
+			}
+			/* figure F.3 */
+			s = csize(zz[k], 1);
+			rs = (r << 4) | s;
+			si = ehufsia[chr][rs];
+			co = ehufcoa[chr][rs];
+			writecode(fd, co, si);
+			if(zz[k] < 0)
+				zz[k] -= 1;
+			writecode(fd, zz[k], s);
+			r = 0;
+		}
+	}
+	return dc;
+}
+
+static char *
+toycc1(int *y, int *cb, int *cr, int jx, int jy, int dx, int dy,
+	int bpl, uchar *data, int ndata, int depth)
+{
+	int i, j, k, l, m, n, u, v, pos, pmask, nmask, pix;
+	int r, g, b;
+
+	m = 8 / depth;
+	pmask = (1 << depth) - 1;
+	nmask = 7 >> log2[depth];
+	for(i = jy, k = 0; i < jy+8; i++) {
+		v = min(i, dy-1);
+		for(l = 0, j = jx/m; l < 8; l++, k++) {
+			u = min(j, (dx-1)/m);
+			n = l+jx >= dx? dx-jx-1: l;
+			pos = v*bpl + u;
+			if(pos >= ndata)
+				return "WriteJPG: overflow";
+			/* thanks writeppm */
+			pix = (data[pos] >> depth*((nmask - n) &
+				nmask)) & pmask;
+			if(((n + 1) & nmask) == 0)
+				j++;
+			grey2rgb(&r, &g, &b, pix, depth);
+			rgb2ycc(&y[k], &cb[k], &cr[k], r, g, b);
+		}
+	}
+	return nil;
+}
+
+static char *
+toycc2(int *y, int *cb, int *cr, int jx, int jy, int dx, int dy,
+	int bpl, uchar *data, int ndata, int depth)
+{
+	int i, j, k, m, u, v, pos;
+
+	m = depth / 8;
+	for(i = jy, k = 0; i < jy+8; i++) {
+		v = min(i, dy-1);
+		for(j = jx*m; j < (jx+8)*m; j+=m, k++) {
+			u = min(j, (dx-1)*m);
+			pos = v*bpl + u;
+			if(pos+m-1 >= ndata)
+				return "WriteJPG: overflow";
+			rgb2ycc(&y[k], &cb[k], &cr[k],
+				data[pos+2*m/3],
+				data[pos+m/3],
+				data[pos]);
+		}
+	}
+	return nil;
+}
+
+static char *
+encode(Biobuf *fd, Rectangle r, uchar *data, ulong chan,
+	int ndata, int gflag, int sflag)
+{
+	int k, x, y, dx, dy, depth, bpl, ncomp;
+	int b[3][64], pred[3];
+	char *err;
+	char *(*toycc)(int *, int *, int *, int, int, int, int,
+		int, uchar *, int, int);
+
+	byte = 0;
+	nbyte = 7;
+
+	switch(chan) {
+	case GREY1:
+	case GREY2:
+	case GREY4:
+		toycc = toycc1;
+		break;
+	case GREY8:
+	case RGB24:
+		toycc = toycc2;
+		break;
+	default:
+		return "WriteJPG: can't handle channel type";
+	}
+
+	/*
+	* if dx or dy is not a multiple of 8,
+	* the decoder should continue until reaching
+	* the last mcu, even if the extra pixels go beyond
+	* 0xffff. they are not shown anyway.
+	*/
+	dx = min(Dx(r), 0xffff);
+	dy = min(Dy(r), 0xffff);
+	depth = chantodepth(chan);
+	bpl = bytesperline(r, depth);
+	ncomp = gflag? 1: 3;
+	memset(pred, 0, sizeof pred);
+	for(x = 0, y = 0;;) {
+		err = (*toycc)(b[0], b[1], b[2], x, y, dx, dy,
+			bpl, data, ndata, depth);
+		if(err != nil)
+			return err;
+		for(k = 0; k < ncomp; k++) {
+			fdct(b[k], sflag);
+			pred[k] = huf(fd, b[k], pred[k],
+				k>0, sflag);
+		}
+		if((x += 8) >= dx) {
+			if((y += 8) >= dy)
+				break;
+			x = 0;
+		}
+	}
+	if(nbyte < 7) { /* bit padding */
+		for(; nbyte >= 0; nbyte--)
+			byte |= 0x1 << nbyte;
+		writebyte(fd);
+	}
+	return err;
+}
+
+static void
+makehuf(int *ehufco, int *ehufsi, int *bits, int *huffval, int n)
+{
+	int i, j, k, code, si, lastk, *huffcode, *huffsize;
+
+	/* n+1 for lastk */
+	if((huffcode = malloc((n+1)*sizeof *huffcode)) == nil)
+		sysfatal("malloc: %r");
+	if((huffsize = malloc((n+1)*sizeof *huffsize)) == nil)
+		sysfatal("malloc: %r");
+	/* figure C.1 */
+	for(k = 0, i = 1, j = 1; i <= 16;) {
+		if(j > bits[i-1]) { /* bits[i] in T.81: bug? */
+			i++;
+			j = 1;
+		} else {
+			huffsize[k++] = i;
+			j++;
+		}
+	}
+	huffsize[k] = 0;
+	lastk = k;
+	/* figure C.2 */
+	for(k = 0, code = 0, si = huffsize[0];;) {
+		do {
+			huffcode[k++] = code++;
+		} while(huffsize[k] == si);
+		if(huffsize[k] == 0)
+			break;
+		while(huffsize[k] != si) {
+			code <<= 1;
+			si++;
+		}
+	}
+	/* figure C.3 */
+	for(k = 0; k < lastk; k++) {
+		i = huffval[k];
+		ehufco[i] = huffcode[k];
+		ehufsi[i] = huffsize[k];
+	}
+	free(huffcode);
+	free(huffsize);
+}
+
+static void
+writejfif(Biobuf *fd, int dx, int dy)
+{
+	if(dx > 0xffff || dy > 0xffff)
+		sysfatal("writejfif: dx or dy too big");
+	Bputs(fd, 0xffe0);
+	Bputs(fd, 0x0010);
+	Bputc(fd, 0x4a);
+	Bputc(fd, 0x46);
+	Bputc(fd, 0x49);
+	Bputc(fd, 0x46);
+	Bputc(fd, 0x00);
+	Bputs(fd, 0x0102);
+	Bputc(fd, 0x01);
+	Bputs(fd, dx);
+	Bputs(fd, dy);
+	Bputc(fd, 0x00);
+	Bputc(fd, 0x00);
+}
+
+static void
+writecomment(Biobuf *fd, char *com)
+{
+	int n;
+
+	if(com != nil && com[0] != '\0') {
+		n = min(strlen(com)+2, 0xffff);
+		Bputs(fd, 0xfffe);
+		Bputs(fd, n);
+		Bwrite(fd, com, n-2);
+	}
+}
+
+static void
+writequant(Biobuf *fd, int tq, int sflag)
+{
+	int i, *p, *q;
+
+	if(tq != 0x0 && tq != 0x1)
+		sysfatal("writequant: invalid Tq");
+	q = qt[tq];
+	Bputs(fd, 0xffdb);
+	Bputs(fd, 0x0043);
+	Bputc(fd, (0x0<<4)|tq);
+	p = sflag? zigzag: invzigzag;
+	for(i = 0; i < 64; i++)
+		Bputc(fd, q[p[i]]);
+}
+
+static void
+writehuffman(Biobuf *fd, int tc, int th)
+{
+	int i, n, m, *b, *hv;
+
+	if((tc != 0x0 && tc != 0x1) || (th != 0x0 && th != 0x1))
+		sysfatal("writehuffman: invalid Tc or Th");
+	n = 0x0013;
+	if(tc == 0x0) {
+		b = dcbits[th];
+		hv = dchuffval[th];
+		m = nelem(dchuffval[th]);
+	} else {
+		b = acbits[th];
+		hv = achuffval[th];
+		m = nelem(achuffval[th]);
+	}
+	Bputs(fd, 0xffc4);
+	Bputs(fd, n+m);
+	Bputc(fd, (tc<<4)|th);
+	for(i = 0; i < 16; i++)
+		Bputc(fd, b[i]);
+	for(i = 0; i < m; i++)
+		Bputc(fd, hv[i]);
+}
+
+static void
+writeframe(Biobuf *fd, int y, int x, int gflag)
+{
+	int n, nf;
+
+	nf = gflag? 0x01: 0x03;
+	n = 0x0008 + 0x0003*nf;
+
+	Bputs(fd, 0xffc0);
+	Bputs(fd, n);
+	Bputc(fd, 0x08);
+	Bputs(fd, y);
+	Bputs(fd, x);
+	Bputc(fd, nf);
+	
+	/* Y component */
+	Bputc(fd, 0x00);
+	Bputc(fd, (0x1<<4)|0x1);
+	Bputc(fd, 0x00);
+
+	if(!gflag) {
+		/* Cb component */
+		Bputc(fd, 0x01);
+		Bputc(fd, (0x1<<4)|0x1);
+		Bputc(fd, 0x01);
+		
+		/* Cr component */
+		Bputc(fd, 0x02);
+		Bputc(fd, (0x1<<4)|0x1);
+		Bputc(fd, 0x01);
+	}
+}
+
+static void
+writescan(Biobuf *fd, int gflag)
+{
+	int n, ns;
+
+	ns = gflag? 0x01: 0x03;
+	n = 0x0006 + 0x0002*ns;
+
+	Bputs(fd, 0xffda);
+	Bputs(fd, n);
+	Bputc(fd, ns);
+
+	/* Y component */
+	Bputc(fd, 0x00);
+	Bputc(fd, (0x0<<4)|0x0);
+
+	if(!gflag) {
+		/* Cb component */
+		Bputc(fd, 0x01);
+		Bputc(fd, (0x1<<4)|0x1);
+
+		/* Cr component */
+		Bputc(fd, 0x02);
+		Bputc(fd, (0x1<<4)|0x1);
+	}
+
+	Bputc(fd, 0x00);
+	Bputc(fd, 0x3f);
+	Bputc(fd, (0x0<<4)|0x0);
+}
+
+static void
+writeheader(Biobuf *fd, int dx, int dy, char *s, int gflag, int sflag)
+{
+	int i;
+
+	dx = min(dx, 0xffff);
+	dy = min(dy, 0xffff);
+
+	Bputs(fd, 0xffd8);
+	writejfif(fd, dx, dy);
+	writecomment(fd, s);
+	writequant(fd, 0, sflag);
+	if(!gflag)
+		writequant(fd, 1, sflag);
+	writeframe(fd, dy, dx, gflag);
+	for(i = 0; i < 2; i++) {
+		writehuffman(fd, i, 0);
+		if(!gflag)
+			writehuffman(fd, i, 1);
+	}
+	writescan(fd, gflag);
+}
+
+static void
+writetrailer(Biobuf *fd)
+{
+	Bputs(fd, 0xffd9);
+	Bflush(fd);
+}
+
+static char *
+writedata(Biobuf *fd, Image *i, Memimage *m, int gflag, int sflag)
+{
+	char *err;
+	uchar *data;
+	int ndata, depth;
+	ulong chan;
+	Rectangle r;
+
+	if(m != nil) {
+		r = m->r;
+		depth = m->depth;
+		chan = m->chan;
+	} else {
+		r = i->r;
+		depth = i->depth;
+		chan = i->chan;
+	}
+
+	/*
+	* potentially one extra byte on each
+	* end of each scan line
+	*/
+	ndata = Dy(r) * (2 + Dx(r)*depth/8);
+	if((data = malloc(ndata)) == nil)
+		return "WriteJPG: malloc failed";
+	if(m != nil)
+		ndata = unloadmemimage(m, r, data, ndata);
+	else
+		ndata = unloadimage(i, r, data, ndata);
+	if(ndata < 0) {
+		if((err = malloc(ERRMAX)) == nil)
+			return "WriteJPG: malloc failed";
+		snprint(err, ERRMAX, "WriteJPG: %r");
+	} else
+		err = encode(fd, r, data, chan, ndata, gflag, sflag);
+	free(data);
+	return err;
+}
+
+static char *
+writejpg0(Biobuf *fd, Image *image, Memimage *memimage,
+	Rectangle r, ulong chan, char *s, int gflag, int sflag)
+{
+	int i;
+	char *err;
+
+	switch(chan) {
+	case GREY1:
+	case GREY2:
+	case GREY4:
+	case GREY8:
+	case RGB24:
+		break;
+	default:
+		return "WriteJPG: can't handle channel type";
+	}
+	for(i = 0; i < 2; i++) {
+		memset(ehufcod[i], 0, sizeof ehufcod[i]);
+		memset(ehufsid[i], 0, sizeof ehufsid[i]);
+		memset(ehufcoa[i], 0, sizeof ehufcoa[i]);
+		memset(ehufsia[i], 0, sizeof ehufsia[i]);
+		makehuf(ehufcod[i], ehufsid[i], dcbits[i],
+			dchuffval[i], nelem(dchuffval[i]));
+		makehuf(ehufcoa[i], ehufsia[i], acbits[i],
+			achuffval[i], nelem(achuffval[i]));
+	}
+	writeheader(fd, Dx(r), Dy(r), s, gflag, sflag);
+	err = writedata(fd, image, memimage, gflag, sflag);
+	writetrailer(fd);
+	return err;
+}
+
+char *
+writejpg(Biobuf *fd, Image *i, char *s, int gflag, int sflag)
+{
+	return writejpg0(fd, i, nil, i->r, i->chan, s, gflag, sflag);
+}
+
+char *
+memwritejpg(Biobuf *fd, Memimage *m, char *s, int gflag, int sflag)
+{
+	return writejpg0(fd, nil, m, m->r, m->chan, s, gflag, sflag);
+}