Plan 9 from Bell Labs’s /usr/web/sources/extra/i/img.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include "i.h"


// big tables in separate files
#include "rgb.h"
#include "ycbcr.h"

enum {
	CLAMPBOFF = 300,
	NCLAMPB = CLAMPBOFF+256+CLAMPBOFF,
	CLAMPNOFF = 64,
	NCLAMPN = CLAMPNOFF+256+CLAMPNOFF
};

int	dbgimg = 0;

static int		progressive = 0;

static uchar	clampb[NCLAMPB];
static int		clampn_b[NCLAMPN];
static int		clampn_g[NCLAMPN];
static int		clampn_r[NCLAMPN];
static int		cmap_r[256];
static int		cmap_g[256];
static int		cmap_b[256];

static void	imgerror(ImageSource* is, int err);
static int	getc(ImageSource* is);
static void	ungetc(ImageSource* is);
static void	ungetc2(ImageSource* is);
static int	getlew(ImageSource* is);
static int	getbew(ImageSource* is);
static void	readis(ImageSource* is, uchar* buf, int n);
static int	getn(ImageSource* is, int n);
static Image*	newimage(ImageSource* is, int w, int h);
static MaskedImage*	newmi(Image* im);
static void	setdims(ImageSource* is);
static void	tracearray(int* a, int n, char* name);
static void	getxbitmaphdr(ImageSource* is);
static MaskedImage*	getxbitmapmim(ImageSource* is);
static int	getxbitmapdefine(ImageSource* is, int* pval);
static void	get_to_char(ImageSource* is, int cterm);
static int	get_hexbyte(ImageSource* is);
static int	hexdig(int c);
static void	getgifhdr(ImageSource* is);
static int	gifgettoimage(ImageSource* is);
static MaskedImage*	getgifmim(ImageSource* is);
static uchar*	gifreadcmap(ImageSource* is, int bpe, int* pmaplen);
static void	getjpeghdr(ImageSource* is);
static int	jpegmarker(ImageSource* is);
static void	jpegtabmisc(ImageSource* is, int* pm, int* pn);
static void	jpeghuffmantables(ImageSource* is, int n);
static int	jpeghuffmantable(ImageSource* is);
static void	jpegquanttables(ImageSource* is, int n);
static int	jpegquanttable(ImageSource* is);
static MaskedImage*	getjpegmim(ImageSource* is);
static uchar**	jpegbaselinescan(ImageSource* is, int* pnchans);
static void	jrestart(ImageSource* is, int mcu);
static void	jpegprogressivescan(ImageSource* is);
static uchar**	jprogressiveIDCT(ImageSource* is, int* pnchans);
static void	jprogressiveinit(ImageSource* is, Jpegstate* h);
static void	jprogressivedc(ImageSource* is, int comp);
static void	jprogressiveac(ImageSource* is, int comp);
static void	jprogressiveacinc(ImageSource* is, int comp);
static void	jincrement(ImageSource* is, int* acc, int k, int Pt);
static void	colormap1(Jpegstate* h, uchar* pic, int* data, int mcu, int nacross);
static void	colormapall1(Jpegstate* h, uchar** chans, int* data0, int* data1, int* data2, int mcu, int nacross);
static void	colormap(Jpegstate* h, uchar** chans, int** data0, int** data1, int** data2, int mcu, int nacross, int Hmax, int Vmax, int* H, int* V);
static int	jdecode(ImageSource* is, Huffman* t);
static int	jnextbyte(ImageSource* is);
static int	jnextborm(ImageSource* is);
static int	jreceive(ImageSource* is, int s);
static int	jreceiveEOB(ImageSource* is, int s);
static int	jreceivebit(ImageSource* is);
static void	nibbles(int c, int* p1, int* p2);
static void	idct(int* b);
static void	init_tabs(void);
static void	remap1(uchar* pic, int dx, int dy, uchar* cmap, int cmaplen);
static void	remapgrey(uchar* pic, int dx, int dy);
static void	remaprgb(uchar** chans, int dx, int dy);
static uchar*	resample(uchar* src, int sw, int sh, int dw, int dh);

void
imginit(void)
{
	init_tabs();
}

// Return true if mtype is an image type we can handle
int
supported(int mtype)
{
	switch(mtype) {
	case ImageJpeg:
	case ImageGif:
	case ImageXXBitmap:
		return 1;
	}
	return 0;
}

// w,h passed in are specified width and height.
// Result will be resampled if they don't match the dimensions
// in the decoded picture (if only one of w,h is specified, the other
// dimension is scaled by the same factor).
ImageSource*
newimagesource(ByteSource* bs, int w, int h)
{
	int	mtype;
	ImageSource*	is;

	dbgimg = config.dbg['i'];
	mtype = UnknownType;
	if(bs->hdr != nil)
		mtype = bs->hdr->mtype;
	is = (ImageSource*)emallocz(sizeof(ImageSource));
	is->width = w;
	is->height = h;
	is->mtype = mtype;
	is->bs = bs;
	return is;
}

int
getmim(ImageSource* is, MaskedImage** pans)
{
	MaskedImage*	ans;
	int	ret;
	int	v;
	int	ed;

	if(dbgimg)
		trace("img: getmim %U %dx%d\n", is->bs->req->url, is->width, is->height);
	if(dbgev)
		logtime("IMAGE_GETMIM", is->width*is->height);
	ans = nil;
	*pans = nil;
	ret = Mimnone;
	ed = is->bs->edata;

	// To avoid the need to check for error returns on a lot of low level functions,
	// we setjmp here, and call imgerror to return control to this place if
	// there is an error in processing the image.
	if((v = setjmp(is->getmimsave))) {
		is->err = v;
		ret = Mimerror;
		if(dbgimg)
			trace("getmim got err: %S\n", errphrase(is->err));
	}
	else {
		if(is->bs->hdr == nil)
			return Mimnone;
		else if(is->mtype == UnknownType)
			is->mtype = is->bs->hdr->mtype;
		// temporary hack: wait until whole file is here first
		if(is->bs->edata == is->bs->dalloclen) {
			switch(is->mtype) {
			case ImageJpeg:
				ans = getjpegmim(is);
				break;
			case ImageGif:
				ans = getgifmim(is);
				break;
			case ImageXXBitmap:
				ans = getxbitmapmim(is);
				break;
			default:
				is->err = ERRunsupimg;
				ret = Mimerror;
				ans = nil;
				break;
			}
			if(ans != nil)
				ret = Mimdone;
		}
	}
	if(dbgimg)
		trace("img: getmim returns (%d,%p)\n", ret, (void*)ans);
	if(dbgev)
		logtime("IMAGE_GETMIM_END", 0);
	is->bs->lim = ed;
	if(is->i > ed)
		is->bs->lim = is->i;
	*pans = ans;
	return ret;
}

// transfer control back to getmim, with given error code
static void
imgerror(ImageSource* is, int err)
{
	if(dbgimg)
		trace("Image error: %S\n", errphrase(err));
	longjmp(is->getmimsave, err);
}

// Get next char or call imgerror if cannot
static int
getc(ImageSource* is)
{
	if(is->i >= is->bs->dalloclen) {
		imgerror(is, ERReof);
	}
	return is->bs->data[is->i++];
}

// Unget the last character.
// When called before any other getting routines, we
// know the buffer still has that character in it.
static void
ungetc(ImageSource* is)
{
	assert(is->i > 0);
	is->i--;
}

// Like ungetc, but ungets two bytes
static void
ungetc2(ImageSource* is)
{
	assert(is->i > 1);
	is->i -= 2;
}

// Get 2 bytes and return the 16-bit value, little-endian order.
static int
getlew(ImageSource* is)
{
	int	c0;
	int	c1;

	c0 = getc(is);
	c1 = getc(is);
	return c0 + (c1 << 8);
}

// Get 2 bytes and return the 16-bit value, big-endian order.
static int
getbew(ImageSource* is)
{
	int	c0;
	int	c1;

	c0 = getc(is);
	c1 = getc(is);
	return (c0 << 8) + c1;
}

// Copy next n bytes of input into buf
// or call imgerror if cannot.
static void
readis(ImageSource* is, uchar* buf, int n)
{
	if(is->i + n < is->bs->dalloclen) {
		memmove(buf, is->bs->data+is->i, n);
		is->i += n;
	}
	else
		imgerror(is, ERReof);
}

// Caller needs n bytes.
// Return index into is->bs->data where at least
// the next n bytes can be found.
// There might be a "premature eof" exception.
static int
getn(ImageSource* is, int n)
{
	int	i;

	i = is->i;
	if(i + n <= is->bs->dalloclen)
		is->i += n;
	else
		imgerror(is, ERReof);
	return i;
}

// draw's allocimage but with some defaults.
// calls imgerror if out of memory.
static Image*
newimage(ImageSource* is, int w, int h)
{
	Image*	im;

	if(!imcacheneed(w*h))
		imgerror(is, ERRnomem);
	im = allocimage(display, Rect(0, 0, w, h), CMAP8, 1, DWhite);
	if(im == nil)
		imgerror(is, ERRnomem);
	return im;
}

static MaskedImage*
newmi(Image* im)
{
	MaskedImage* mi;

	mi = (MaskedImage*)emallocz(sizeof(MaskedImage));
	mi->im = im;
	return mi;
}

// Call this after origw and origh are set to set the width and height
// to our desired (rescaled) answer dimensions.
// If only one of the dimensions is specified, the other is scaled by
// the same factor.
static void
setdims(ImageSource* is)
{
	int	sw;
	int	sh;
	int	dw;
	int	dh;

	sw = is->origw;
	sh = is->origh;
	dw = is->width;
	dh = is->height;
	if(dw == 0 && dh == 0) {
		dw = sw;
		dh = sh;
	}
	else if(dw == 0 || dh == 0) {
		if(dw == 0) {
			dw = (int)((((double)sw)*((double)dh/(double)sh)));
			if(dw == 0)
				dw = 1;
		}
		else {
			dh = (int)((((double)sh)*((double)dw/(double)sw)));
			if(dh == 0)
				dh = 1;
		}
	}
	is->width = dw;
	is->height = dh;
}

// for debugging
static void
tracearray(int* a, int n, char* name)
{
	int	i;

	trace("%s:", name);
	for(i = 0; i < n; i++) {
		if((i%10) == 0)
			trace("\n%5d: ", i);
		trace("%6d", a[i]);
	}
	trace("\n");
}

// XBitmaps

static void
getxbitmaphdr(ImageSource* is)
{
	int	fnd;

	fnd = getxbitmapdefine(is, &is->origw);
	if(fnd)
		fnd = getxbitmapdefine(is, &is->origh);
	if(!fnd)
		imgerror(is, ERRimgbad);
	fnd = getxbitmapdefine(is, nil);
	if(fnd)
		getxbitmapdefine(is, nil);
	get_to_char(is, '{');
}

static MaskedImage*
getxbitmapmim(ImageSource* is)
{
	int	bytesperline;
	uchar*	pixels;
	int	npixels;
	int	pixi;
	int	i;
	Image*	im;
	int	v;
	int	kend;
	int	k;
	int	j;

	getxbitmaphdr(is);
	setdims(is);
	bytesperline = (is->origw + 7)/8;
	npixels = is->origw*is->origh;
	pixels = (uchar*)emalloc(npixels);
	pixi = 0;
	for(i = 0; i < is->origh; i++) {
		for(j = 0; j < bytesperline; j++) {
			v = get_hexbyte(is);
			kend = 7;
			if(j == bytesperline - 1)
				kend = (is->origw - 1)%8;
			for(k = 0; k <= kend; k++) {
				if(v&(1 << k))
					pixels[pixi] = Black;
				else
					pixels[pixi] = White;
				pixi++;
			}
		}
	}
	if(is->width != is->origw || is->height != is->origh)
		pixels = resample(pixels, is->origw, is->origh, is->width, is->height);
	im = newimage(is, is->width, is->height);
	loadimage(im, im->r, pixels, npixels);
	return newmi(im);
}

// get a line, which should be of form
//	'//define fieldname val'
// and return 1 if found.
// If pval != nil, put val in *pval.
static int
getxbitmapdefine(ImageSource* is, int* pval)
{
	int	fnd;
	int	n;
	int	c;

	fnd = 0;
	n = 0;
	c = getc(is);
	if(c == '#') {
		get_to_char(is, ' ');
		get_to_char(is, ' ');
		c = getc(is);
		while(c >= '0' && c <= '9') {
			fnd = 1;
			n = n*10 + c - '0';
			c = getc(is);
		}
	}
	else
		ungetc(is);
	get_to_char(is, '\n');
	if(pval != nil)
		*pval = n;
	return fnd;
}

// read fd until get char cterm
// (getc will call imgerror if eof hit first)
static void
get_to_char(ImageSource* is, int cterm)
{
	while(1) {
		if(getc(is) == cterm)
			return;
	}
}

// read fd until get xDD, were DD are hex digits.
// (getc will call imgerror if eof hit first)
static int
get_hexbyte(ImageSource* is)
{
	int	n1;
	int	n2;

	get_to_char(is, 'x');
	n1 = hexdig(getc(is));
	n2 = hexdig(getc(is));
	if(n1 < 0 || n2 < 0)
		imgerror(is, ERRimgbad);
	return (n1 << 4) + n2;
}

static int
hexdig(int c)
{
	if('0' <= c && c <= '9')
		c -= 48;
	else if('a' <= c && c <= 'f')
		c += 10 - 'a';
	else if('A' <= c && c <= 'F')
		c += 10 - 'A';
	else
		c = -1;
	return c;
}

// GIF

// GIF flags
enum {
	TRANSP = 1,
	INPUT = 2,
	DISPMASK = (7<<2),
	HASCMAP = 0x80,
	INTERLACED = 0x40
};

static void
getgifhdr(ImageSource* is)
{
	Gifstate*	h;
	int	i;
	char*	vers;

	if(dbgimg)
		trace("img: getgifhdr\n");
	h = (Gifstate*)emallocz(sizeof(Gifstate));
	i = getn(is, 6);
	vers = (char*)(is->bs->data+i);
	if(strncmp(vers, "GIF87a", 6) && strncmp(vers, "GIF89a", 6))
		imgerror(is, ERRimgbad);
	is->origw = getlew(is);
	is->origh = getlew(is);
	h->fields = getc(is);
	h->bgrnd = getc(is);
	h->aspect = getc(is);
	setdims(is);
	if(dbgimg)
		trace("img: getgifhdr has origw=%d, origh=%d, w=%d, h=%d, fields=16r%x, bgrnd=%d, aspect=%d\n",
			is->origw, is->origh, is->width, is->height, h->fields, h->bgrnd, h->aspect);
	h->tbl = (GifEntry*)emalloc(4096 * sizeof(GifEntry));
	for(i = 0; i < 258; i++) {
		h->tbl[i].prefix = -1;
		h->tbl[i].exten = i;
	}
	if(h->fields&HASCMAP)
		h->globalcmap = gifreadcmap(is, (h->fields&7) + 1, &h->globalcmaplen);
	is->gstate = h;
	if(warn && h->aspect != 0)
		trace("warning: non-standard aspect ratio in GIF image ignored\n");
	if(!gifgettoimage(is))
		imgerror(is, ERRimgbad);
}

static int
gifgettoimage(ImageSource* is)
{
	Gifstate*	h;
	int	nbytes;
	int	i;
	int	hsize;
	int	hasdata;
	int	c;

	if(dbgimg)
		trace("img: gifgettoimage\n");
	h = is->gstate;
	while(1) {
		// some GIFs omit Trailer
		if(is->i >= is->bs->dalloclen)
			break;

		switch(c = getc(is)) {
		case 0x2C:			// Image Descriptor
			return 1;
			break;

		case 0x21:			// Extension
			hsize = 0;
			hasdata = 0;

			switch(getc(is)) {
			case 0x01:			// Plain Text Extension
				hsize = 14;
				hasdata = 1;
				if(dbgimg)
					trace("gifgettoimage: text extension\n");
				break;
			case 0xF9:			// Graphic Control Extension
				getc(is);			// blocksize (should be 4)
				h->flags = getc(is);
				h->delay = getlew(is);
				h->trindex = getc(is);
				getc(is);			// block terminator (should be 0)
				if(dbgimg)
					trace("gifgettoimage: graphic control flags=16r%x, delay=%d, trindex=%d\n",
						h->flags, h->delay, h->trindex);
				break;
			case 0xFE:			// Comment Extension
				if(dbgimg)
					trace("gifgettoimage: comment extension\n");
				hasdata = 1;
				break;
			case 0xFF:			// Application Extension
				if(dbgimg)
					trace("gifgettoimage: application extension\n");
				hsize = getc(is);
				// 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:
				imgerror(is, ERRimgbad);
				break;
			}
			if(hsize > 0)
				getn(is, hsize);
			if(hasdata) {
				while(1) {
					if((nbytes = getc(is)) == 0)
						break;
					i = getn(is, nbytes);
					if(dbgimg)
						trace("extension data: '%S'\n", toStr(is->bs->data+i, nbytes, UTF_8));
				}
			}
			break;

		case 0x3B:		// Trailer
			goto loop_done;
			break;

		default:
			if(c == 0)
				continue;				// Fix for some buggy gifs
			imgerror(is, ERRimgbad);
			break;
		}
	}
loop_done:
	return 0;
}

static MaskedImage*
getgifmim(ImageSource* is)
{
	Gifstate*	h;
	int	left;
	int	top;
	int	width;
	int	height;
	int	totpix;
	int	c;
	int	incode;
	int	codesize;
	int	CTM;
	int	EOD;
	uchar*	pic;
	int	pici;
	uchar*	data;
	int	datai;
	int	dataend;
	int	nbits;
	int	sreg;
	uchar*	stack;
	int	stacki;
	int	fc;
	GifEntry*	tbl;
	Image*	mask;
	int	bgcolor;
	int	i;
	Image*	im;
	MaskedImage*	mi;
	int	dispmeth;
	int	nbytes;
	int	code;
	int	nb;
	int	early;
	int	csize;
	int	csmask;
	int	nentry;
	int	maxentry;
	int	first;
	int	ocode;
	int	start;
	int	by;
	uchar*	ipic;
	int	ipici;
	int	ipiclim;
	int	w2;
	int	w4;
	int	w8;
	int	k;
	double	wscale;
	double	hscale;
	int	owidth;
	int	oheight;
	uchar	v;
	int	x;
	int	bytesperrow;
	uchar	trpix;
	uchar*	mpic;
	int	mpici;
	int	y;
	Image*	newmask;
	Rectangle	r;
	int	col;
	Image*	newim;
	int	ilstart[4];
	int	ilby[4];

	if(is->gstate == nil)
		getgifhdr(is);

	// At this point, should just have read Image Descriptor marker byte
	h = is->gstate;
	left = getlew(is);
	top = getlew(is);
	width = getlew(is);
	height = getlew(is);
	h->fields = getc(is);
	totpix = width*height;
	h->cmap = h->globalcmap;
	h->cmaplen = h->globalcmaplen;
	if(dbgimg)
		trace("getgifmim, left=%d, top=%d, width=%d, height=%d, fields=16r%x\n",
			left, top, width, height, h->fields);
	if(dbgev)
		logtime("IMAGE_GETGIFMIM", 0);
	if(h->fields&HASCMAP)
		h->cmap = gifreadcmap(is, (h->fields&7) + 1, &h->cmaplen);
	if(h->cmap == nil)
		imgerror(is, ERRimgbad);

	// now decode the image
	codesize = getc(is);
	if(codesize > 8)
		imgerror(is, ERRimgbad);
	if(h->cmaplen != 3*(1 << codesize) && (codesize != 2 || h->cmaplen != 3*2))
		imgerror(is, ERRimgbad);

	CTM = 1 << codesize;
	EOD = CTM + 1;

	pic = (uchar*)emalloc(totpix);
	pici = 0;
	data = is->bs->data;
	datai = 0;
	dataend = 0;

	nbits = 0;
	sreg = 0;
	stack = (uchar*)emalloc(4096);
	fc = 0;
	tbl = h->tbl;
	while(1) {
Decode_continue:
		csize = codesize + 1;
		csmask = ((1 << csize) - 1);
		nentry = EOD + 1;
		maxentry = csmask;
		first = 1;

		for(ocode = -1; ; ocode = incode) {
			while(nbits < csize) {
				if(datai == dataend) {
					nbytes = getc(is);
					if(nbytes == 0)
						goto Decode_done;
					datai = getn(is, nbytes);
					dataend = datai + nbytes;
				}
				c = data[datai++];
				sreg |= c << nbits;
				nbits += 8;
			}
			code = sreg&csmask;
			sreg >>= csize;
			nbits -= csize;

			if(code == EOD) {
				nbytes = getc(is);
				if(nbytes != 0 && warn)
					trace("warning: unexpected data past EOD\n");
				goto Decode_done;
			}
			if(code == CTM)
				goto Decode_continue;

			stacki = 4096 - 1;

			incode = code;

			// special case for KwKwK 
			if(code == nentry) {
				stack[stacki--] = fc;
				code = ocode;
			}

			if(code > nentry)
				imgerror(is, ERRimgbad);

			for(c = code; c >= 0; c = tbl[c].prefix)
				stack[stacki--] = tbl[c].exten;

			nb = 4096 - (stacki + 1);
			if(pici + nb > totpix) {
				// this common error is harmless
				// we have to keep reading to keep the blocks in sync
				;
			}
			else {
				memmove(pic+pici, stack+stacki+1, nb);
				pici += nb;
			}

			fc = stack[stacki + 1];

			if(first) {
				first = 0;
				continue;
			}

			early = 0; // peculiar tiff feature here for reference
			if(nentry == maxentry - early) {
				if(csize >= 12)
					continue;
				csize++;
				maxentry = (1 << csize);
				csmask = maxentry - 1;
				if(csize < 12)
					maxentry--;
			}
			tbl[nentry].prefix = ocode;
			tbl[nentry].exten = fc;
			nentry++;
		}
	}
Decode_done:
	while(pici < totpix) {
		// shouldn't happen, but sometimes get buggy gifs
		pic[pici++] = 0;
	}

	if(h->fields&INTERLACED) {
		if(dbgimg)
			trace("getgifmim uninterlacing\n");
		if(dbgev)
			logtime("IMAGE_GETGIFMIM_INTERLACE_START", 0);
		// (TODO: Could un-interlace in place.
		// Decompose permutation into cycles,
		// then need one double-copy of a line
		// per cycle).
		ipic = (uchar*)emalloc(totpix);
		pici = 0;
		ipiclim = totpix - width;
		w2 = width + width;
		w4 = w2 + w2;
		w8 = w4 + w4;
		ilstart[0] = 0;
		ilstart[1] = w4;
		ilstart[2] = w2;
		ilstart[3] = width;
		ilby[0] = w8;
		ilby[1] = w8;
		ilby[2] = w4;
		ilby[3] = w2;
		for(k = 0; k < 4; k++) {
			start = ilstart[k];
			by = ilby[k];
			for(ipici = start; ipici <= ipiclim; ipici += by) {
				memmove(ipic+ipici, pic+pici, width);
				pici += width;
			}
		}
		pic = ipic;
		if(dbgev)
			logtime("IMAGE_GETGIFMIM_INTERLACE_END", 0);
	}
	if(is->width != is->origw || is->height != is->origh) {
		// need to resample, using same factors as original image
		wscale = (double)is->width/(double)is->origw;
		hscale = (double)is->height/(double)is->origh;
		owidth = width;
		oheight = height;
		width = (int)((wscale*(double)width)+.5);
		if(width == 0)
			width = 1;
		height = (int)((hscale*(double)height)+.5);
		if(height == 0)
			height = 1;
		left = (int)((wscale*(double)left)+.5);
		top = (int)((hscale*(double)top)+.5);
		pic = resample(pic, owidth, oheight, width, height);
	}
	mask = nil;
	if(h->flags&TRANSP) {
		if(dbgimg)
			trace("getgifmim making mask, trindex=%d\n", h->trindex);
		if(dbgev)
			logtime("IMAGE_GETGIFMIM_MASK_START", 0);
		// make a 1-bit deep bitmap for mask
		// expect most mask bits will be 1
		bytesperrow = (width + 7)/8;
		trpix = h->trindex;
		nb = bytesperrow*height;
		mpic = (uchar*)emalloc(nb);
		mpici = 0;
		pici = 0;
		for(y = 0; y < height; y++) {
			v = 0xFF;
			k = 0;
			for(x = 0; x < width; x++) {
				if(pic[pici++] == trpix)
					v &= ~(0x80 >> k);
				if(++k == 8) {
					k = 0;
					mpic[mpici++] = v;
					v = 0xFF;
				}
			}
			if(k != 0)
				mpic[mpici++] = v;
		}
		if(!imcacheneed(nb))
			imgerror(is, ERRnomem);
		mask = allocimage(display, Rect(0, 0, width, height), GREY1, 0, DOpaque);
		if(mask == nil)
			imgerror(is, ERRnomem);
		loadimage(mask, mask->r, mpic, nb);
		if(dbgev)
			logtime("IMAGE_GETGIFMIM_MASK_END", 0);
	}
	if(dbgev)
		logtime("IMAGE_GETGIFMIM_REMAP_START", 0);
	remap1(pic, width, height, h->cmap, h->cmaplen);
	if(dbgev)
		logtime("IMAGE_GETGIFMIM_REMAP_END", 0);
	bgcolor = -1;
	i = h->bgrnd;
	if(i >= 0 && 3*i + 2 < h->cmaplen) {
		bgcolor = ((h->cmap[3*i]) << 16)
			| ((h->cmap[3*i + 1]) << 8)
			| (h->cmap[3*i + 2]);
	}
	im = newimage(is, width, height);
	loadimage(im, im->r, pic, totpix);
	if(is->curframe == 0) {
		// make sure first frame fills up whole rectangle
		if(is->width != width || is->height != height || left != 0 || top != 0) {
			r = Rect(left, top, left + width, top + height);
			col = DWhite;
			if(bgcolor != -1)
				col = (bgcolor<<8)|0xFF;
			newim = allocimage(display, Rect(0, 0, is->width, is->height), im->chan, 0, col);
			if(newim == nil)
				imgerror(is, ERRnomem);
			draw(newim, r, im, mask, ZP);
			im = newim;
			if(mask != nil) {
				newmask = allocimage(display, Rect(0, 0, is->width, is->height), GREY1, 0, DTransparent);
				if(newmask == nil)
					imgerror(is, ERRnomem);
				draw(newmask, r, mask, nil, ZP);
				mask = newmask;
			}
			left = 0;
			top = 0;
		}
	}
	mi = newmi(im);
	mi->mask = mask;
	mi->delay = h->delay*10;	// convert centiseconds to milliseconds
	mi->origin = Pt(left, top);
	dispmeth = (h->flags >> 2)&7;
	if(dispmeth == 2) {
		// reset to background color after displaying this frame
		mi->bgcolor = bgcolor;
	}
	else if(dispmeth == 3) {
		// Supposed to "reset to previous", which appears to
		// mean the previous frame that didn't have a "reset to previous".
		// Signal this special case to layout by setting bgcolor to -2.
		mi->bgcolor = -2;
	}
	if(gifgettoimage(is)) {
		mi->more = 1;
		is->curframe++;
		// have to reinitialize table for next time
		for(i = 0; i < 258; i++) {
			h->tbl[i].prefix = -1;
			h->tbl[i].exten = i;
		}
	}
	if(dbgev)
		logtime("IMAGE_GETGIFMIM_END", 0);
	return mi;
}

// Read a GIF colormap, where bpe is number of bits in an entry.
// Causes a 'premature eof' imgerror if can't get the whole map.
static uchar*
gifreadcmap(ImageSource* is, int bpe, int* pmaplen)
{
	int	size;
	uchar*	map;

	size = 3*(1 << bpe);
	map = (uchar*)emalloc(size);
	if(dbgimg > 1)
		trace("gifreadcmap wants %d bytes\n", size);
	readis(is, map, size);
	*pmaplen = size;
	return map;
}

// JPG

// Constants
enum {
	// All of these are preceded by 0xFF byte
	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

	NBUF = 16*1024,

	// channel descriptions
	CRGB = 0,		// three channels, R, G, B, no map
	CY = 1,		// one channel, luminance
	CRGB1 = 2,	// one channel, map present
	CYCbCr = 3,	// three channels, Y, Cb, Cr, no map

	jpegcolorspace = CYCbCr,	// default space to convert jpeg to

	// Some floats converted to fixed point (scaling by 2048)
	jc1 = 2871,		// 1.402 * 2048
	jc2 = 705,			// 0.34414 * 2048
	jc3 = 1463,		// 0.71414 * 2048
	jc4 = 3629,		// 1.772 * 2048

	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
getjpeghdr(ImageSource* is)
{
	Jpegstate*	h;
	int	m;
	int	n;
	int	i;
	int	H;
	int	V;

	if(dbgimg)
		trace("getjpeghdr\n");
	h = (Jpegstate*)emallocz(sizeof(Jpegstate));
	is->jstate = h;
	if(jpegmarker(is) != SOI)
		imgerror(is, ERRimgbad);
	jpegtabmisc(is, &m, &n);
	if(!(m == SOF || m == SOF2))
		imgerror(is, ERRimgbad);
	getc(is);			// sample precision
	h->Y = getbew(is);
	h->X = getbew(is);
	h->Nf = getc(is);
	if(dbgimg)
		trace("start of frame, Y=%d, X=%d, Nf=%d\n", h->Y, h->X, h->Nf);
	h->comp = (Framecomp*)emalloc(h->Nf * sizeof(Framecomp));
	h->nblock = (int*)emalloc(h->Nf * sizeof(int));
	for(i = 0; i < h->Nf; i++) {
		h->comp[i].C = getc(is);
		nibbles(getc(is), &H, &V);
		h->comp[i].H = H;
		h->comp[i].V = V;
		h->comp[i].Tq = getc(is);
		h->nblock[i] = H*V;
		if(dbgimg)
			trace("comp[%d]: C=%d, H=%d, V=%d, Tq=%d\n",
				i, h->comp[i].C, H, V, h->comp[i].Tq);
	}
	h->mode = m;
	is->origw = h->X;
	is->origh = h->Y;
	setdims(is);
	if(n != 6 + 3*h->Nf)
		imgerror(is, ERRimgbad);
}

static int
jpegmarker(ImageSource* is)
{
	if(getc(is) != 0xFF)
		imgerror(is, ERRimgbad);
	return getc(is);
}

// Consume tables and miscellaneous marker segments,
// returning the marker id and length of the first non-such-segment
// (after having consumed the marker) in *pm and *pn.
static void
jpegtabmisc(ImageSource* is, int *pm, int *pn)
{
	Jpegstate*	h;
	int	m;
	int	n;
	int	vers0;
	int	vers1;
	uchar*	buf;
	int	i;

	h = is->jstate;
	while(1) {
		h->nseg++;
		m = jpegmarker(is);
		n = 0;
		if(m != EOI)
			n = getbew(is) - 2;
		if(dbgimg > 1)
			trace("jpegtabmisc reading segment, got m=%x, n=%d\n", m, n);

		switch(m) {
		case SOF:
		case SOF2:
		case SOS:
		case EOI:
			goto Loop_done;
			break;

		case APPn+0:
			if(h->nseg == 1 && n >= 6) {
				i = getn(is, 6);
				buf = is->bs->data;
				n -= 6;
				if(!strncmp((char*)(buf+i), "JFIF", 4)) {
					vers0 = buf[i + 5];
					vers1 = buf[i + 6];
					if(vers0 > 1 || vers1 > 2)
						imgerror(is, ERRimgbad);		// unimplemented version
				}
			}
			break;

		case DQT:
			jpegquanttables(is, n);
			n = 0;
			break;

		case DHT:
			jpeghuffmantables(is, n);
			n = 0;
			break;
		case DRI:
			h->ri = getbew(is);
			n -= 2;
			break;

		case COM:
			break;

		default:
			if(!(m >= APPn+1 && m <= APPn+15))
				imgerror(is, ERRimgbad);		// unexpected marker
			break;
		}
		if(n > 0)
			getn(is, n);
	}
Loop_done:
	*pm = m;
	*pn = n;
}

// Consume huffman tables
static void
jpeghuffmantables(ImageSource* is, int n)
{
	Jpegstate*	h;
	int	l;

	if(dbgimg)
		trace("jpeghuffmantables\n");
	h = is->jstate;
	if(h->dcht == nil) {
		h->dcht = (Huffman**)emallocz(4 * sizeof(Huffman*));
		h->acht = (Huffman**)emallocz(4 * sizeof(Huffman*));
	}
	l = 0;
	while(l < n)
		l += jpeghuffmantable(is);
	if(l != n)
		imgerror(is, ERRimgbad);		// huffman table bad length
}

static int
jpeghuffmantable(ImageSource* is)
{
	Huffman*	t;
	Jpegstate*	h;
	int	Tc;
	int	th;
	uchar*	b;
	int	bi;
	int	nsize;
	int	i;
	int	k;
	int	code;
	int	si;
	int	j;
	int*	maxcode;
	int	v;
	int	n;
	int	cnt;
	int	m;
	int	sr;
	int	numcodes[16];

	t = (Huffman*)emalloc(sizeof(Huffman));
	h = is->jstate;
	nibbles(getc(is), &Tc, &th);
	if(dbgimg > 1)
		trace("jpeghuffmantable, Tc=%d, th=%d\n", Tc, th);
	if(Tc > 1)
		imgerror(is, ERRimgbad);		// unknown Huffman table class
	if(th > 3 || (h->mode == (uchar)SOF && th > 1))
		imgerror(is, ERRimgbad);		// unknown Huffman table index
	if(Tc == 0)
		h->dcht[th] = t;
	else
		h->acht[th] = t;

	// flow chart C-2
	bi = getn(is, 16);
	b = is->bs->data;
	nsize = 0;
	for(i = 0; i < 16; i++)
		nsize += (numcodes[i] = b[bi + i]);
	t->size = (int*)emalloc((nsize + 1) * sizeof(int));
	k = 0;
	for(i = 1; i <= 16; i++) {
		n = numcodes[i - 1];
		for(j = 0; j < n; j++)
			t->size[k++] = i;
	}
	t->size[k] = 0;

	// initialize HUFFVAL
	t->val = (int*)emalloc(nsize * sizeof(int));
	bi = getn(is, nsize);
	for(i = 0; i < nsize; i++)
		t->val[i] = b[bi++];

	// flow chart C-3
	t->code = (int*)emalloc((nsize + 1) * sizeof(int));
	k = 0;
	code = 0;
	si = t->size[0];
	while(1) {
		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;
	while(1) {
		while(1) {
			i++;
			if(i > 16)
				goto F25_done;
			if(numcodes[i - 1] != 0)
				break;
			t->maxcode[i] = -1;
		}
		t->valptr[i] = j;
		t->mincode[i] = t->code[j];
		j += numcodes[i - 1] - 1;
		t->maxcode[i] = t->code[j];
		j++;
	}
F25_done:

	// stupid startup algorithm: just run machine for each byte value
	maxcode = t->maxcode;
	for(v = 0; v < 256; v++) {
		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 Bytes_continue;
			}
			cnt--;
		}
		t->shift[v] = 8 - cnt;
		t->value[v] = t->val[t->valptr[i] + (code - t->mincode[i])];
Bytes_continue:
		;
	}
	if(dbgimg > 2) {
		trace("Huffman table %d:\n", th);
		tracearray(t->size, nsize+1, "size");
		tracearray(t->code, nsize+1, "code");
		tracearray(t->val, nsize, "val");
		tracearray(t->mincode, 17, "mincode");
		tracearray(t->maxcode, 17, "maxcode");
		tracearray(t->value, 256, "value");
		tracearray(t->shift, 256, "shift");
	}
	return nsize + 17;
}

static void
jpegquanttables(ImageSource* is, int n)
{
	Jpegstate*	h;
	int	l;

	if(dbgimg)
		trace("jpegquanttables\n");
	h = is->jstate;
	if(h->qt == nil)
		h->qt = (int**)emalloc(4 * sizeof(int*));
	l = 0;
	while(l < n)
		l += jpegquanttable(is);
	if(l != n)
		imgerror(is, ERRimgbad);
}

static int
jpegquanttable(ImageSource* is)
{
	int	pq;
	int	tq;
	int*	q;
	int	i;

	nibbles(getc(is), &pq, &tq);
	if(dbgimg)
		trace("jpegquanttable pq=%d tq=%d\n", pq, tq);
	if(pq > 1)
		imgerror(is, ERRimgbad);		// unknown quantization table class
	if(tq > 3)
		imgerror(is, ERRimgbad);		// bad quantization table index
	q = (int*)emalloc(64 * sizeof(int));
	is->jstate->qt[tq] = q;
	for(i = 0; i < 64; i++) {
		if(pq == 0)
			q[i] = getc(is);
		else
			q[i] = getbew(is);
	}
	if(dbgimg > 2)
		tracearray(q, 64, "quant table");
	return 1 + (64*(1 + pq));
}

// Have just read Frame header.
// Now expect:
//	((tabl/misc segment(s))* (scan header) (entropy coded segment)+)+ EOI
static MaskedImage*
getjpegmim(ImageSource* is)
{
	Jpegstate*	h;
	uchar**	chans;
	int	width;
	int	height;
	Image*	im;
	int	m;
	int	n;
	Scancomp*	scomp;
	int	i;
	int	k;
	int	nchans;

	if(dbgimg)
		trace("getjpegmim\n");
	if(dbgev)
		logtime("IMAGE_GETJPGMIM", is->width*is->height);
	getjpeghdr(is);
	h = is->jstate;
	chans = nil;
	while(1) {
		jpegtabmisc(is, &m, &n);
		if(m == EOI)
			break;
		if(m != SOS)
			imgerror(is, ERRimgbad);		// expected start of scan
		h->Ns = getc(is);
		if(dbgimg)
			trace("start of scan, Ns=%d\n", h->Ns);
		scomp = (Scancomp*)emalloc(h->Ns * sizeof(Scancomp));
		for(i = 0; i < h->Ns; i++) {
			scomp[i].C = getc(is);
			nibbles(getc(is), &scomp[i].tdc, &scomp[i].tac);
		}
		h->scomp = scomp;
		h->Ss = getc(is);
		h->Se = getc(is);
		nibbles(getc(is), &h->Ah, &h->Al);
		if(n != 4 + h->Ns*2)
			imgerror(is, ERRimgbad);		// SOS header wrong length
		if(h->mode == SOF) {
			if(chans != nil)
				imgerror(is, ERRimgbad);	// baseline has > 1 scan
			chans = jpegbaselinescan(is, &nchans);
		}
		else
			jpegprogressivescan(is);
	}
	if(h->mode == SOF2)
		chans = jprogressiveIDCT(is, &nchans);
	if(chans == nil)
		imgerror(is, ERRimgbad);		// jpeg has no image
	width = is->width;
	height = is->height;
	if(width != h->X || height != h->Y) {
		for(k = 0; k < nchans; k++)
			chans[k] = resample(chans[k], h->X, h->Y, width, height);
	}
	if(dbgev)
		logtime("IMAGE_JPG_REMAP", 0);
	if(nchans == 1)
		remapgrey(chans[0], width, height);
	else
		remaprgb(chans, width, height);
	if(dbgev)
		logtime("IMAGE_JPG_REMAP_END", 0);
	im = newimage(is, width, height);
	loadimage(im, im->r, chans[0], width*height);
	if(dbgev)
		logtime("IMAGE_GETJPGMIM_END", 0);
	return newmi(im);
}

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 uchar**
jpegbaselinescan(ImageSource* is, int *pnchans)
{
	Jpegstate*	h;
	int	Ns;
	uchar**	chans;
	int	k;
	int*	Td;
	int*	Ta;
	int***	data;
	int*	H;
	int*	V;
	int*	DC;
	int	Hmax;
	int	Vmax;
	int	comp;
	int	allHV1;
	int	ri;
	int	nacross;
	int	nmcu;
	int	mcu;
	int	nblock;
	int	m;
	int	z;
	int	rs;
	int	rrrr;
	int	ssss;
	int	t;
	int	diff;
	int*	zz;
	Huffman*	dcht;
	Huffman*	acht;
	int*	qt;
	int	block;

	if(dbgimg)
		trace("jpegbaselinescan\n");
	if(dbgev)
		logtime("IMAGE_JPGBASELINESCAN", 0);
	h = is->jstate;
	Ns = h->Ns;
	if(Ns != h->Nf)
		imgerror(is, ERRimgbad);		// baseline needs Ns==Nf
	if(!(Ns == 3 || Ns == 1))
		imgerror(is, ERRimgbad);		// baseline needs Ns==1 or 3
	chans = (uchar**)emalloc(h->Nf * sizeof(uchar*));
	*pnchans = h->Nf;
	for(k = 0; k < h->Nf; k++)
		chans[k] = (uchar*)emalloc(h->X*h->Y);

	// build per-component arrays
	Td = (int*)emalloc(Ns * sizeof(int));
	Ta = (int*)emalloc(Ns * sizeof(int));
	data = (int***)emalloc(Ns * sizeof(int**));
	H = (int*)emalloc(Ns * sizeof(int));
	V = (int*)emalloc(Ns * sizeof(int));
	DC = (int*)emalloc(Ns * sizeof(int));

	// 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;
	}
	if(dbgimg > 1)
		trace("Hmax=%d, Vmax=%d\n", Hmax, Vmax);

	// initialize data structures
	allHV1 = 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
		Td[comp] = h->scomp[comp].tdc;
		Ta[comp] = h->scomp[comp].tac;
		H[comp] = h->comp[comp].H;
		V[comp] = h->comp[comp].V;
		nblock = H[comp]*V[comp];
		if(nblock != 1)
			allHV1 = 0;
		data[comp] = (int**)emalloc(nblock * sizeof(int*));
		DC[comp] = 0;
		for(m = 0; m < nblock; m++)
			data[comp][m] = (int*)emalloc(8*8 * sizeof(int));
		if(dbgimg > 2)
			trace("scan comp %d: H=%d, V=%d, nblock=%d, Td=%d, Ta=%d\n",
					comp, H[comp], V[comp], nblock, Td[comp], Ta[comp]);
	}

	ri = h->ri;

	h->cnt = 0;
	h->sr = 0;
	nacross = ((h->X + (8*Hmax - 1))/(8*Hmax));
	nmcu = ((h->Y + (8*Vmax - 1))/(8*Vmax))*nacross;
	if(dbgimg)
		trace("nacross=%d, nmcu=%d\n", nacross, nmcu);
	mcu = 0;
	while(mcu < nmcu) {
		if(dbgimg > 2)
			trace("mcu %d\n", mcu);
		for(comp = 0; comp < Ns; comp++) {
			if(dbgimg > 2)
				trace("comp %d\n", 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++) {
				if(dbgimg > 2)
					trace("block %d\n", block);
				// F-22
				t = jdecode(is, dcht);
				diff = jreceive(is, t);
				DC[comp] += diff;
				if(dbgimg > 2)
					trace("t=%d, diff=%d, DC=%d\n", t, diff, DC[comp]);

				// F=23
				zz = data[comp][block];
				memset(zz, 0, 64*sizeof(int));
				zz[0] = qt[0]*DC[comp];
				k = 1;

				while(1) {
					rs = jdecode(is, acht);
					nibbles(rs, &rrrr, &ssss);
					if(ssss == 0) {
						if(rrrr != 15)
							break;
						k += 16;
					}
					else {
						k += rrrr;
						z = jreceive(is, ssss);
						zz[zig[k]] = z*qt[k];
						if(k == 63)
							break;
						k++;
					}
				}
				idct(zz);
			}
		}

		// rotate colors to RGB and assign to bytes
		if(Ns == 1)
			colormap1(h, chans[0], data[0][0], mcu, nacross);
		else if(allHV1)
			colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, nacross);
		else
			colormap(h, chans, 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) {
			jrestart(is, mcu);
			for(comp = 0; comp < Ns; comp++)
				DC[comp] = 0;
		}
	}
	if(dbgev)
		logtime("IMAGE_JPGBASELINESCAN_END", 0);
	return chans;
}

static void
jrestart(ImageSource* is, int mcu)
{
	Jpegstate*	h;
	int	ri;
	int	restart;
	int	rst;
	int	nskip;

	h = is->jstate;
	ri = h->ri;
	restart = mcu/ri - 1;
	nskip = 0;
	do {
		do {
			rst = jnextborm(is);
			nskip++;
		} while(rst >= 0 && rst != 0xFF);
		if(rst == 0xFF) {
			rst = jnextborm(is);
			nskip++;
		}
	} while(rst >= 0 && (rst&~7) != RST);
	if(nskip != 2 || rst < 0 || ((rst&7) != (restart&7)))
		imgerror(is, ERRimgbad);		// restart problem
	h->cnt = 0;
	h->sr = 0;
}

static void
jpegprogressivescan(ImageSource* is)
{
	Jpegstate*	h;
	int	c;
	int	comp;
	int	i;

	if(dbgev)
		logtime("IMAGE_JPGPROGSCAN", 0);
	h = is->jstate;
	if(h->dccoeff == nil)
		jprogressiveinit(is, h);

	c = h->scomp[0].C;
	comp = -1;
	for(i = 0; i < h->Nf; i++)
		if(h->comp[i].C == c)
			comp = i;
	if(comp == -1)
		imgerror(is, ERRimgbad);		// bad component index in scan header

	if(h->Ss == 0)
		jprogressivedc(is, comp);
	else if(h->Ah == 0)
		jprogressiveac(is, comp);
	else
		jprogressiveacinc(is, comp);
	if(dbgev)
		logtime("IMAGE_JPGPROGSCAN_END", 0);
}

static uchar**
jprogressiveIDCT(ImageSource* is, int* pnchans)
{
	Jpegstate*	h;
	int	Nf;
	int*	H;
	int*	V;
	int	allHV1;
	int***	data;
	int	comp;
	uchar**	chans;
	int	k;
	int*	blockno;
	int	nmcu;
	int	mcu;
	int	nblock;
	int	m;
	int*	zz;
	int*	dccoeff;
	int**	accoeff;
	int	bn;
	int	block;

	if(dbgev)
		logtime("IMAGE_JPGPROGIDCT", 0);
	h = is->jstate;
	Nf = h->Nf;
	H = (int*)emalloc(Nf * sizeof(int));
	V = (int*)emalloc(Nf * sizeof(int));
	allHV1 = 1;
	data = (int***)emalloc(Nf * sizeof(int**));
	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;
		data[comp] = (int**)emalloc(nblock * sizeof(int*));
		for(m = 0; m < nblock; m++)
			data[comp][m] = (int*)emalloc(8*8 * sizeof(int));
	}
	chans = (uchar**)emalloc(h->Nf*sizeof(uchar*));
	*pnchans = h->Nf;
	for(k = 0; k < h->Nf; k++)
		chans[k] = (uchar*)emalloc(h->X*h->Y);
	blockno = (int*)emallocz(Nf * sizeof(int));
	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)
			colormap1(h, chans[0], data[0][0], mcu, h->nacross);
		else if(allHV1)
			colormapall1(h, chans, data[0][0], data[1][0], data[2][0], mcu, h->nacross);
		else
			colormap(h, chans, data[0], data[1], data[2], mcu, h->nacross, h->Hmax, h->Vmax, H, V);
	}
	return chans;
}

static void
jprogressiveinit(ImageSource* is, Jpegstate* h)
{
	int	Ns;
	int	Nf;
	int	comp;
	int	nmcu;
	int	k;
	int	j;

	Ns = h->Ns;
	Nf = h->Nf;
	if((Ns != 3 && Ns != 1) || Ns != Nf)
		imgerror(is, ERRimgbad);
	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;
	h->dccoeff = (int**)emalloc(Nf * sizeof(int*));
	h->accoeff = (int***)emalloc(Nf * sizeof(int**));
	for(k = 0; k < Nf; k++) {
		h->dccoeff[k] = (int*)emallocz(h->nblock[k]*nmcu * sizeof(int));
		h->accoeff[k] = (int**)emalloc(h->nblock[k]*nmcu * sizeof(int*));
		for(j = 0; j < h->nblock[k]*nmcu; j++)
			h->accoeff[k][j] = (int*)emallocz(64 * sizeof(int));
	}
}

static void
jprogressivedc(ImageSource* is, int comp)
{
	Jpegstate*	h;
	int	Ns;
	int	Ah;
	int	Al;
	int*	Td;
	int*	DC;
	int	ri;
	int	nmcu;
	int*	blockno;
	int	mcu;
	int	t;
	int	diff;
	Huffman*	dcht;
	int	qt;
	int*	dc;
	int	bn;
	int	block;

	h = is->jstate;
	Ns = h->Ns;
	Ah = h->Ah;
	Al = h->Al;
	if(Ns != h->Nf)
		imgerror(is, ERRimgbad);		// progressive with Nf!=Ns in DC scan

	// build per-component arrays
	Td = (int*)emalloc(Ns * sizeof(int));
	DC = (int*)emalloc(Ns * sizeof(int));
	h->cnt = 0;
	h->sr = 0;
	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
		Td[comp] = h->scomp[comp].tdc;
		DC[comp] = 0;
	}

	ri = h->ri;

	nmcu = h->nacross*h->ndown;
	blockno = (int*)emallocz(Ns * sizeof(int));
	mcu = 0;
	while(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 = jdecode(is, dcht);
					diff = jreceive(is, t);
					DC[comp] += diff;
					dc[bn] = qt*DC[comp] << Al;
				}
				else
					dc[bn] |= qt*jreceivebit(is) << Al;
				bn++;
			}
			blockno[comp] = bn;
		}

		// process restart marker, if present
		mcu++;
		if(ri > 0 && mcu < nmcu && mcu%ri == 0) {
			jrestart(is, mcu);
			for(comp = 0; comp < Ns; comp++)
				DC[comp] = 0;
		}
	}
}

static void
jprogressiveac(ImageSource* is, int comp)
{
	Jpegstate*	h;
	int	Ns;
	int	Al;
	int	Ss;
	int	Se;
	int	H;
	int	V;
	int	nacross;
	int	ndown;
	int	q;
	int	nhor;
	int	nver;
	int	Ta;
	int	ri;
	int	eobrun;
	Huffman*	acht;
	int*	qt;
	int	nmcu;
	int	mcu;
	int	y;
	int	z;
	int	rs;
	int	rrrr;
	int	ssss;
	int	tmcu;
	int	blockno;
	int*	acc;
	int	k;
	int	x;

	h = is->jstate;
	Ns = h->Ns;
	Al = h->Al;
	if(Ns != 1)
		imgerror(is, ERRimgbad);		//  illegal Ns>1 in progressive AC scan
	Ss = h->Ss;
	Se = h->Se;
	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;
	Ta = h->scomp[0].tac;

	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;
			while(1) {
				rs = jdecode(is, acht);
				nibbles(rs, &rrrr, &ssss);
				if(ssss == 0) {
					if(rrrr < 15) {
						eobrun = 0;
						if(rrrr > 0)
							eobrun = jreceiveEOB(is, rrrr) - 1;
						break;
					}
					k += 16;
				}
				else {
					k += rrrr;
					z = jreceive(is, 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) {
			jrestart(is, mcu);
			eobrun = 0;
		}
	}
}

static void
jprogressiveacinc(ImageSource* is, int comp)
{
	Jpegstate*	h;
	int	Ns;
	int	Ss;
	int	Se;
	int	H;
	int	V;
	int	Al;
	int	nacross;
	int	ndown;
	int	q;
	int	nhor;
	int	nver;
	int	Ta;
	int	ri;
	int	eobrun;
	int**	ac;
	Huffman*	acht;
	int*	qt;
	int	nmcu;
	int	mcu;
	int	pending;
	int	nzeros;
	int	y;
	int	k;
	int	i;
	int	rs;
	int	rrrr;
	int	ssss;
	int	tmcu;
	int	blockno;
	int*	acc;
	int	x;

	h = is->jstate;
	Ns = h->Ns;
	if(Ns != 1)
		imgerror(is, ERRimgbad);		// illegal Ns>1 in progressive AC scan
	Ss = h->Ss;
	Se = h->Se;
	H = h->comp[comp].H;
	V = h->comp[comp].V;
	Al = h->Al;

	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;
	Ta = h->scomp[0].tac;
	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)
					imgerror(is, ERRimgbad);		// zeros pending at block start
				for(k = Ss; k <= Se; k++)
					jincrement(is, acc, k, qt[k] << Al);
				--eobrun;
				continue;
			}

			k = Ss;
			while(k <= Se) {
				if(nzeros >= 0) {
					if(acc[k] != 0)
						jincrement(is, acc, k, qt[k] << Al);
					else if(nzeros-- == 0)
						acc[k] = pending;
					k++;
					continue;
				}
				rs = jdecode(is, acht);
				nibbles(rs, &rrrr, &ssss);
				if(ssss == 0) {
					if(rrrr < 15) {
						eobrun = 0;
						if(rrrr > 0)
							eobrun = jreceiveEOB(is, rrrr) - 1;
						while(k <= Se) {
							jincrement(is, acc, k, qt[k] << Al);
							k++;
						}
						break;
					}
					for(i = 0; i < 16; k++) {
						jincrement(is, acc, k, qt[k] << Al);
						if(acc[k] == 0)
							i++;
					}
					continue;
				}
				else if(ssss != 1)
					imgerror(is, ERRimgbad);		// Jpeg ssss!=1 in progressive increment
				nzeros = rrrr;
				pending = jreceivebit(is);
				if(pending == 0)
					pending = -1;
				pending *= qt[k] << Al;
			}
		}

		// process restart marker, if present
		mcu++;
		if(ri > 0 && mcu < nmcu && mcu%ri == 0) {
			jrestart(is, mcu);
			eobrun = 0;
			nzeros = -1;
		}
	}
}

static void
jincrement(ImageSource* is, int* acc, int k, int Pt)
{
	int	b;

	if(acc[k] == 0)
		return ;
	b = jreceivebit(is);
	if(b != 0)
		if(acc[k] < 0)
			acc[k] -= Pt;
		else
			acc[k] += Pt;
}

// Fills in pixels (x,y) for x = minx=8*(mcu%nacross), minx+1, ..., minx+7 (or h.X-1, if less)
// and for y = miny=8*(mcu/nacross), miny+1, ..., miny+7 (or h.Y-1, if less)
static void
colormap1(Jpegstate* h, uchar* pic, int* data, int mcu, int nacross)
{
	int	minx;
	int	dx;
	int	miny;
	int	dy;
	int	pici;
	int	k;
	int	y;
	int	x;

	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++)
			pic[pici + x] = clampb[(data[k + x] + 128) + CLAMPBOFF];
		pici += h->X;
		k += 8;
	}
}

// Fills in same pixels as colormap1
static void
colormapall1(Jpegstate* h, uchar** chans, int* data0, int* data1, int* data2, int mcu, int nacross)
{
	uchar*	rpic;
	uchar*	gpic;
	uchar*	bpic;
	int	minx;
	int	dx;
	int	miny;
	int	dy;
	int	pici;
	int	k;
	int	y;
	int	Y;
	int	Cb;
	int	Cr;
	int	r;
	int	g;
	int	b;
	int	x;

	rpic = chans[0];
	gpic = chans[1];
	bpic = 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++) {
		for(x = 0; x < dx; x++) {
			if(jpegcolorspace == CYCbCr) {
				rpic[pici + x] = clampb[data0[k + x] + 128 + CLAMPBOFF];
				gpic[pici + x] = clampb[data1[k + x] + 128 + CLAMPBOFF];
				bpic[pici + x] = clampb[data2[k + x] + 128 + CLAMPBOFF];
			}
			else {
				Y = (data0[k + x] + 128) << 11;
				Cb = data1[k + x];
				Cr = data2[k + x];
				r = Y + jc1*Cr;
				g = Y - jc2*Cb - jc3*Cr;
				b = Y + jc4*Cb;
				rpic[pici + x] = clampb[(r >> 11) + CLAMPBOFF];
				gpic[pici + x] = clampb[(g >> 11) + CLAMPBOFF];
				bpic[pici + x] = clampb[(b >> 11) + CLAMPBOFF];
			}
		}
		pici += h->X;
		k += 8;
	}
}

// Fills in pixels (x,y) for x = minx=8*Hmax*(mcu%nacross), minx+1, ..., minx+8*Hmax-1 (or h.X-1, if less)
// and for y = miny=8*Vmax*(mcu/nacross), miny+1, ..., miny+8*Vmax-1 (or h.Y-1, if less)
static void
colormap(Jpegstate* h, uchar** chans, int** data0, int** data1, int** data2, int mcu, int nacross, int Hmax, int Vmax, int* H, int* V)
{
	uchar*	rpic;
	uchar*	gpic;
	uchar*	bpic;
	int	minx;
	int	dx;
	int	miny;
	int	dy;
	int	pici;
	int	H0;
	int	H1;
	int	H2;
	int	y;
	int	Y;
	int	Cb;
	int	Cr;
	int	r;
	int	g;
	int	b;
	int	t;
	int	b0;
	int	y0;
	int	b1;
	int	y1;
	int	b2;
	int	y2;
	int	x0;
	int	x1;
	int	x2;
	int	x;

	rpic = chans[0];
	gpic = chans[1];
	bpic = 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];
	if(dbgimg > 2)
		trace("colormap, minx=%d, miny=%d, dx=%d, dy=%d, pici=%d, H0=%d, H1=%d, H2=%d\n",
			minx, miny, dx, dy, pici, H0, H1, H2);
	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(jpegcolorspace == CYCbCr) {
				rpic[pici + x] = clampb[data0[b0][y0 + x0++*H0/Hmax] + 128 + CLAMPBOFF];
				gpic[pici + x] = clampb[data1[b1][y1 + x1++*H1/Hmax] + 128 + CLAMPBOFF];
				bpic[pici + x] = clampb[data2[b2][y2 + x2++*H2/Hmax] + 128 + CLAMPBOFF];
			}
			else { // RGB
				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 + jc1*Cr;
				g = Y - jc2*Cb - jc3*Cr;
				b = Y + jc4*Cb;
				rpic[pici + x] = clampb[(r >> 11) + CLAMPBOFF];
				gpic[pici + x] = clampb[(g >> 11) + CLAMPBOFF];
				bpic[pici + x] = clampb[(b >> 11) + CLAMPBOFF];
			}
			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
jdecode(ImageSource* is, Huffman* t)
{
	Jpegstate*	h;
	int*	maxcode;
	int	code;
	int	v;
	int	cnt;
	int	m;
	int	sr;
	int	i;

	h = is->jstate;
	maxcode = t->maxcode;
	if(h->cnt < 8)
		jnextbyte(is);
	// fast lookup
	code = (h->sr >> (h->cnt - 8))&255;
	v = t->value[code];
	if(v >= 0) {
		h->cnt -= t->shift[code];
		return v;
	}

	h->cnt -= 8;
	if(h->cnt == 0)
		jnextbyte(is);
	h->cnt--;
	cnt = h->cnt;
	m = 1 << cnt;
	sr = h->sr;
	code <<= 1;
	for(i = 9; ; i++) {
		if(sr&m)
			code |= 1;
		if(code <= maxcode[i])
			break;
		code <<= 1;
		m >>= 1;
		if(m == 0) {
			sr = jnextbyte(is);
			m = 0x80;
			cnt = 8;
		}
		cnt--;
	}
	h->cnt = cnt;
	return t->val[t->valptr[i] + (code - t->mincode[i])];
}

// load next byte of input
static int
jnextbyte(ImageSource* is)
{
	int	b;
	Jpegstate*	h;
	int	b2;

	b = getc(is);
	if(b == 0xFF) {
		b2 = getc(is);
		if(b2 != 0) {
			if(b2 == DNL)
				imgerror(is, ERRimgbad);		// DNL marker unimplemented
			ungetc2(is);
		}
	}
	h = is->jstate;
	h->cnt += 8;
	h->sr = (h->sr << 8)|b;
	return b;
}

// like jnextbyte, but look for marker too
static int
jnextborm(ImageSource* is)
{
	int	b;
	Jpegstate*	h;

	b = getc(is);

	if(b == 0xFF)
		return b;
	h = is->jstate;
	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
jreceive(ImageSource* is, int s)
{
	Jpegstate*	h;
	int	v;
	int	m;

	h = is->jstate;
	while(h->cnt < s)
		jnextbyte(is);
	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
jreceiveEOB(ImageSource* is, int s)
{
	Jpegstate*	h;
	int	v;
	int	m;

	h = is->jstate;
	while(h->cnt < s)
		jnextbyte(is);
	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
jreceivebit(ImageSource* is)
{
	Jpegstate*	h;

	h = is->jstate;
	if(h->cnt < 1)
		jnextbyte(is);
	h->cnt--;
	return (h->sr >> h->cnt)&1;
}

static void
nibbles(int c, int* p1, int* p2)
{
	*p1 = c >> 4;
	*p2 = c & 15;
}

// 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

static void
idct(int* b)
{
	int	y;
	int	x;
	int	v;
	int	eighty;
	int	x0;
	int	x1;
	int	x2;
	int	x3;
	int	x4;
	int	x5;
	int	x6;
	int	x7;
	int	x8;

	// transform horizontally
	for(y = 0; y < 8; y++) {
		eighty = y << 3;
		// if all non-DC components are zero, just propagate the DC term
		if(b[eighty + 1] == 0)
		if(b[eighty + 2] == 0 && b[eighty + 3] == 0)
		if(b[eighty + 4] == 0 && b[eighty + 5] == 0)
		if(b[eighty + 6] == 0 && b[eighty + 7] == 0) {
			v = b[eighty] << 3;
			b[eighty + 0] = v;
			b[eighty + 1] = v;
			b[eighty + 2] = v;
			b[eighty + 3] = v;
			b[eighty + 4] = v;
			b[eighty + 5] = v;
			b[eighty + 6] = v;
			b[eighty + 7] = v;
			continue;
		}
		// prescale
		x0 = (b[eighty + 0] << 11) + 128;
		x1 = b[eighty + 4] << 11;
		x2 = b[eighty + 6];
		x3 = b[eighty + 2];
		x4 = b[eighty + 1];
		x5 = b[eighty + 7];
		x6 = b[eighty + 5];
		x7 = b[eighty + 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
		b[eighty + 0] = (x7 + x1) >> 8;
		b[eighty + 1] = (x3 + x2) >> 8;
		b[eighty + 2] = (x0 + x4) >> 8;
		b[eighty + 3] = (x8 + x6) >> 8;
		b[eighty + 4] = (x8 - x6) >> 8;
		b[eighty + 5] = (x0 - x4) >> 8;
		b[eighty + 6] = (x3 - x2) >> 8;
		b[eighty + 7] = (x7 - x1) >> 8;
	}
	// transform vertically
	for(x = 0; x < 8; x++) {
		// if all non-DC components are zero, just propagate the DC term
		if(b[x + 8*1] == 0)
		if(b[x + 8*2] == 0 && b[x + 8*3] == 0)
		if(b[x + 8*4] == 0 && b[x + 8*5] == 0)
		if(b[x + 8*6] == 0 && b[x + 8*7] == 0) {
			v = (b[x + 8*0] + 32) >> 6;
			b[x + 8*0] = v;
			b[x + 8*1] = v;
			b[x + 8*2] = v;
			b[x + 8*3] = v;
			b[x + 8*4] = v;
			b[x + 8*5] = v;
			b[x + 8*6] = v;
			b[x + 8*7] = v;
			continue;
		}
		// prescale
		x0 = (b[x + 8*0] << 8) + 8192;
		x1 = b[x + 8*4] << 8;
		x2 = b[x + 8*6];
		x3 = b[x + 8*2];
		x4 = b[x + 8*1];
		x5 = b[x + 8*7];
		x6 = b[x + 8*5];
		x7 = b[x + 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
		b[x + 8*0] = (x7 + x1) >> 14;
		b[x + 8*1] = (x3 + x2) >> 14;
		b[x + 8*2] = (x0 + x4) >> 14;
		b[x + 8*3] = (x8 + x6) >> 14;
		b[x + 8*4] = (x8 - x6) >> 14;
		b[x + 8*5] = (x0 - x4) >> 14;
		b[x + 8*6] = (x3 - x2) >> 14;
		b[x + 8*7] = (x7 - x1) >> 14;
	}
}

// Remap colors and dither

int
closest_rgbpix(int r, int g, int b)
{
	int	pix;
	int	dr;
	int	dg;
	int	db;
	int	d;
	int	bestdist;
	int	i;

	pix = closestrgb[((r >> 4) << 8) + ((g >> 4) << 4) + (b >> 4)];
	// If white is the closest but original r,g,b wasn't white,
	// look for another color, because web page designer probably
	// cares more about contrast than actual color
	if(pix == 255 && !(r == 255 && g == 255 && b == 255)) {
		bestdist = 1000000;
		for(i = 1; i < 256; i++) {
			dr = r - rgbvmap_r[i];
			dg = g - rgbvmap_g[i];
			db = b - rgbvmap_b[i];
			d = dr*dr + dg*dg + db*db;
			if(d < bestdist) {
				bestdist = d;
				pix = i;
			}
		}
	}
	return pix;
}

static void
init_tabs(void)
{
	int	j;
	int	t;

	for(j = 0; j < CLAMPNOFF; j++) {
		clampn_b[j] = 0;
		clampn_g[j] = 0;
		clampn_r[j] = 0;
	}
	for(j = 0; j < 256; j++) {
		t = j >> 4;
		clampn_b[CLAMPNOFF + j] = t;
		clampn_g[CLAMPNOFF + j] = t << 4;
		clampn_r[CLAMPNOFF + j] = t << 8;
	}
	for(j = 0; j < CLAMPNOFF; j++) {
		clampn_b[CLAMPNOFF + 256 + j] = 15;
		clampn_g[CLAMPNOFF + 256 + j] = 240;
		clampn_r[CLAMPNOFF + 256 + j] = 3840;
	}
	for(j = 0; j < CLAMPBOFF; j++)
		clampb[j] = (uchar)0;
	for(j = 0; j < 256; j++)
		clampb[CLAMPBOFF + j] = j;
	for(j = 0; j < CLAMPBOFF; j++)
		clampb[CLAMPBOFF + 256 + j] = 255;
}

// Remap pixels of pic[] into the closest colors in the rgbv map,
// and do error diffusion of the result.
// pic is a one-channel image whose rgb values are given by looking
// up values in cmap.
static void
remap1(uchar* pic, int dx, int dy, uchar* cmap, int cmaplen)
{
	int	i;
	int	j;
	int*	ered;
	int*	egrn;
	int*	eblu;
	int	p;
	int	y;
	int	x1;
	int	in;
	int	r;
	int	g;
	int	b;
	int	col;
	int	t;
	int	er;
	int	eg;
	int	eb;
	int	x;

	if(dbgimg)
		trace("remap1, pic len %d, dx=%d, dy=%d\n", dx*dy, dx, dy);
	i = 0;
	for(j = 0; j < 256 && i < cmaplen; j++) {
		cmap_r[j] = cmap[i++];
		cmap_g[j] = cmap[i++];
		cmap_b[j] = cmap[i++];
	}
	// in case input has bad indices
	for(; j < 256; j++) {
		cmap_r[j] = 0;
		cmap_g[j] = 0;
		cmap_b[j] = 0;
	}
	// modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
	ered = (int*)emallocz((dx + 1) * sizeof(int));
	egrn = (int*)emallocz((dx + 1) * sizeof(int));
	eblu = (int*)emallocz((dx + 1) * sizeof(int));
	p = 0;
	for(y = 0; y < dy; y++) {
		er = 0;
		eg = 0;
		eb = 0;
		x = 0;
		while(x < dx) {
			x1 = x + 1;
			in = pic[p];
			r = cmap_r[in] + ered[x];
			g = cmap_g[in] + egrn[x];
			b = cmap_b[in] + eblu[x];
			col = (pic[p++] = closestrgb[clampn_r[r + CLAMPNOFF]
						+ clampn_g[g + CLAMPNOFF]
						+ clampn_b[b + CLAMPNOFF]]);

			r -= rgbvmap_r[col];
			t = (3*r) >> 4;
			ered[x] = t + er;
			ered[x1] += t;
			er = r - 3*t;

			g -= rgbvmap_g[col];
			t = (3*g) >> 4;
			egrn[x] = t + eg;
			egrn[x1] += t;
			eg = g - 3*t;

			b -= rgbvmap_b[col];
			t = (3*b) >> 4;
			eblu[x] = t + eb;
			eblu[x1] += t;
			eb = b - 3*t;

			x = x1;
		}
	}
}

// Remap pixels of pic[] into the closest greyscale colors in the rgbv map,
// and do error diffusion of the result.
// pic is a one-channel greyscale image.
static void
remapgrey(uchar* pic, int dx, int dy)
{
	int*	e;
	int	p;
	int	y;
	int	x1;
	int	b;
	int	b1;
	int	col;
	int	t;
	int	eb;
	int	x;

	if(dbgimg)
		trace("remapgrey, pic len %d, dx=%d, dy=%d\n", dx*dy, dx, dy);
	// modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
	e = (int*)emallocz((dx + 1) * sizeof(int));
	p = 0;
	for(y = 0; y < dy; y++) {
		eb = 0;
		x = 0;
		while(x < dx) {
			x1 = x + 1;
			b = pic[p] + e[x];
			b1 = clampn_b[b + CLAMPNOFF];
			col = 17*b1;
			pic[p++] = col;

			b -= rgbvmap_b[col];
			t = (3*b) >> 4;
			e[x] = t + eb;
			e[x1] += t;
			eb = b - 3*t;
			x = x1;
		}
	}
}

// Remap pixels of chans into the closest colors in the rgbv map,
// and do error diffusion of the result.
// chans is a 3-channel image whose channels are either (y,cb,cr) or
// (r,g,b), depending on whether jpegcolorspace is CYCbCr or CRGB.
// Variable names use r,g,b (historical).
// (right now we only call this routine from jpeg code).
static void
remaprgb(uchar** chans, int dx, int dy)
{
	uchar*	rpic;
	uchar*	gpic;
	uchar*	bpic;
	uchar*	pic;
	int*	ered;
	int*	egrn;
	int*	eblu;
	uchar*	closest;
	int*	map0;
	int*	map1;
	int*	map2;
	int	p;
	int	y;
	int	x1;
	int	r;
	int	g;
	int	b;
	int	col;
	int	t;
	int	er;
	int	eg;
	int	eb;
	int	x;

	if(dbgimg)
		trace("remaprgb, pic len %d, dx=%d, dy=%d\n", dx*dy, dx, dy);
	rpic = chans[0];
	gpic = chans[1];
	bpic = chans[2];
	pic = chans[0];
	// modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16
	ered = (int*)emallocz((dx + 1) * sizeof(int));
	egrn = (int*)emallocz((dx + 1) * sizeof(int));
	eblu = (int*)emallocz((dx + 1) * sizeof(int));
	if(jpegcolorspace == CRGB) {
		closest = closestrgb;
		map0 = rgbvmap_r;
		map1 = rgbvmap_g;
		map2 = rgbvmap_b;
	}
	else {
		closest = closestycbcr;
		map0 = rgbvmap_y;
		map1 = rgbvmap_cb;
		map2 = rgbvmap_cr;
	}
	p = 0;
	for(y = 0; y < dy; y++) {
		er = 0;
		eg = 0;
		eb = 0;
		x = 0;
		while(x < dx) {
			x1 = x + 1;
			r = rpic[p] + ered[x];
			g = gpic[p] + egrn[x];
			b = bpic[p] + eblu[x];
			// 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;
			col = (pic[p++] = closest[(b >> 4) + 16*((g >> 4) + 16*(r >> 4))]);

			r -= map0[col];
			t = (3*r) >> 4;
			ered[x] = t + er;
			ered[x1] += t;
			er = r - 3*t;

			g -= map1[col];
			t = (3*g) >> 4;
			egrn[x] = t + eg;
			egrn[x1] += t;
			eg = g - 3*t;

			b -= map2[col];
			t = (3*b) >> 4;
			eblu[x] = t + eb;
			eblu[x1] += t;
			eb = b - 3*t;

			x = x1;
		}
	}
}

// Given src array, representing sw*sh pixel values, resample them into
// the returned array, with dimensions dw*dh.
//
// Quick and dirty resampling: just interpolate.
// This lets us resample arrays of pixels indices (e.g., result of gif decoding).
// The filter-based resampling methods need conversion to rgb or grayscale.
// Also, although the results won't look good, people really shouldn't be
// asking the browser to resample except for special purposes (like the common
// case of resizing a 1x1 image to make a spacer).
static uchar*
resample(uchar* src, int sw, int sh, int dw, int dh)
{
	double	xfac;
	double	yfac;
	int	totpix;
	uchar*	dst;
	int	dindex;
	int*	sindices;
	int	dx;
	int	dy;
	int	sx;
	int	sy;
	int	soffset;

	if(dbgev)
		logtime("IMAGE_RESAMPLE_START", 0);
	if(src == nil || sw == 0 || sh == 0 || dw == 0 || dh == 0)
		return src;
	xfac = (double)sw/(double)dw;
	yfac = (double)sh/(double)dh;
	totpix = dw*dh;
	dst = (uchar*)emalloc(totpix);
	dindex = 0;

	// precompute index in src row corresponding to each index in dst row
	sindices = (int*)emalloc(dw * sizeof(int));
	for(dx = 0; dx < dw; dx++) {
		sx = (int)((xfac*(double)dx)+.5);
		if(sx >= sw)
			sx = sw - 1;
		sindices[dx] = sx;
	}
	for(dy = 0; dy < dh; dy++) {
		sy = (int)((yfac*(double)dy)+.5);
		if(sy >= sh)
			sy = sh - 1;
		soffset = sy*sw;
		for(dx = 0; dx < dw; dx++)
			dst[dindex++] = src[soffset + sindices[dx]];
	}
	if(dbgev)
		logtime("IMAGE_RESAMPLE_END", 0);
	return dst;
}


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].