shithub: purgatorio

ref: d3d1b3b47ff80f451c6c9f8b7f1262fef6545f2b
dir: /appl/lib/readgif.b/

View raw version
implement RImagefile;

include "sys.m";
	sys: Sys;

include "draw.m";

include "bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include "imagefile.m";

Header: adt
{
	fd: ref Iobuf;
	buf: array of byte;
	vers: string;
	screenw: int;
	screenh: int;
	fields: int;
	bgrnd: int;
	aspect: int;
	transp: int;
	trindex: byte;
};

Entry: adt
{
	prefix: int;
	exten: int;
};

tbl: array of Entry;

init(iomod: Bufio)
{
	if(sys == nil)
		sys = load Sys Sys->PATH;
	bufio = iomod;
}
read(fd: ref Iobuf): (ref Rawimage, string)
{
	(a, err) := readarray(fd, 0);
	if(a != nil)
		return (a[0], err);
	return (nil, err);
}

readmulti(fd: ref Iobuf): (array of ref Rawimage, string)
{
	return readarray(fd, 1);
}

readarray(fd: ref Iobuf, multi: int): (array of ref Rawimage, string)
{
	inittbl();

	buf := array[3*256] of byte;

	(header, err) := readheader(fd, buf);
	if(header == nil)
		return (nil, err);

	globalcmap: array of byte;
	if(header.fields & 16r80){
		(globalcmap, err) = readcmap(header, (header.fields&7)+1);
		if(globalcmap == nil)
			return (nil, err);
	}

	images: array of ref Rawimage;
	new: ref Rawimage;

    Loop:
	for(;;){
		case c := fd.getb(){
		Bufio->EOF =>
			if(err == "")
				err = "ReadGIF: premature EOF";
			break Loop;
		Bufio->ERROR =>
			err = sys->sprint("ReadGIF: read error: %r");
			return (nil, err);
		16r21 =>	# Extension (ignored)
			err = skipextension(header);
			if(err != nil)
				return (nil, err);

		16r2C =>	# Image Descriptor
			if(!multi && images!=nil)	# why read the rest?
				break Loop;
			(new, err) = readimage(header);
			if(new == nil)
				return (nil ,err);
			if(new.fields & 16r80){
				(new.cmap, err) = readcmap(header, (new.fields&7)+1);
				if(new.cmap == nil)
					return (nil, err);
			}else
				new.cmap = globalcmap;
			(new.chans[0], err) = decode(header, new);
			if(new.chans[0] == nil)
				return (nil, err);
			if(new.fields & 16r40)
				interlace(new);
			new.transp = header.transp;
			new.trindex = header.trindex;
			nimages := array[len images+1] of ref Rawimage;
			nimages[0:] = images[0:];
			nimages[len images] = new;
			images = nimages;

		16r3B =>	# Trailer
			break Loop;

		* =>
			err = sys->sprint("ReadGIF: unknown block type: %x", c);
			break Loop;
		}
	}

	if(images==nil || images[0].chans[0] == nil){
		if(err == nil)
			err = "ReadGIF: no picture in file";
		return (nil, err);
	}

	return (images, err);
}

readheader(fd: ref Iobuf, buf: array of byte): (ref Header, string)
{
	if(fd.read(buf, 13) != 13){
		err := sys->sprint("ReadGIF: can't read header: %r");
		return (nil, err);
	}
	h := ref Header;
	h.vers = string buf[0:6];
	if(h.vers!="GIF87a" && h.vers!="GIF89a"){
		err := sys->sprint("ReadGIF: can't recognize format %s", h.vers);
		return (nil, err);
	}
	h.screenw = int buf[6]+(int buf[7]<<8);
	h.screenh = int buf[8]+(int buf[9]<<8);
	h.fields = int buf[10];
	h.bgrnd = int buf[11];
	h.aspect = int buf[12];
	h.fd = fd;
	h.buf = buf;
	h.transp = 0;
	return (h, "");
}

readcmap(h: ref Header, size: int): (array of byte,string)
{
	size = 3*(1<<size);
	map := array[size] of byte;
	if(h.fd.read(map, size) != size)
		return (nil, "ReadGIF: short read on color map");
	return (map, "");
}

readimage(h: ref Header): (ref Rawimage, string)
{
	if(h.fd.read(h.buf, 9) != 9){
		err := sys->sprint("ReadGIF: can't read image descriptor: %r");
		return (nil, err);
	}
	i := ref Rawimage;
	left := int h.buf[0]+(int h.buf[1]<<8);
	top := int h.buf[2]+(int h.buf[3]<<8);
	width := int h.buf[4]+(int h.buf[5]<<8);
	height := int h.buf[6]+(int h.buf[7]<<8);
	i.fields = int h.buf[8];
	i.r.min.x = left;
	i.r.min.y = top;
	i.r.max.x = left+width;
	i.r.max.y = top+height;
	i.nchans = 1;
	i.chans = array[1] of array of byte;
	i.chandesc = CRGB1;
	return (i, "");
}

readdata(h: ref Header, ch: chan of (array of byte, string))
{
	err: string;

	# send nil for error, buffer of length 0 for EOF
	for(;;){
		nbytes := h.fd.getb();
		if(nbytes < 0){
			err = sys->sprint("ReadGIF: can't read data: %r");
			ch <-= (nil, err);
			return;
		}
		d := array[nbytes] of byte;
		if(nbytes == 0){
			ch <-= (d, "");
			return;
		}
		n := h.fd.read(d, nbytes);
		if(n != nbytes){
			if(n > 0){
				ch <-= (d[0:n], nil);
				ch <-= (d[0:0], "ReadGIF: short data subblock");
			}else
				ch <-= (nil, sys->sprint("ReadGIF: can't read data: %r"));
			return;
		}
		ch <-= (d, "");
	}
}

readerr: con "ReadGIF: can't read extension: %r";

skipextension(h: ref Header): string
{
	fmterr: con "ReadGIF: bad extension format";

	hsize := 0;
	hasdata := 0;

	case h.fd.getb(){
	Bufio->ERROR or Bufio->EOF =>
		return sys->sprint(readerr);
	16r01 =>	# Plain Text Extension
		hsize = 13;
		hasdata = 1;
	16rF9 =>	# Graphic Control Extension
		return graphiccontrol(h);
	16rFE =>	# Comment Extension
		hasdata = 1;
	16rFF =>	# Application Extension
		hsize = h.fd.getb();
		# standard says this must be 11, but Adobe likes to put out 10-byte ones,
		# so we pay attention to the field.
		hasdata = 1;
	* =>
		return "ReadGIF: unknown extension";
	}
	if(hsize>0 && h.fd.read(h.buf, hsize) != hsize)
		return sys->sprint(readerr);
	if(!hasdata){
		if(int h.buf[hsize-1] != 0)
			return fmterr;
	}else{
		ch := chan of (array of byte, string);
		spawn readdata(h, ch);
		for(;;){
			(data, err) := <-ch;
			if(data == nil)
				return err;
			if(len data == 0)
				break;
		}
	}
	return "";
}

graphiccontrol(h: ref Header): string
{
	if(h.fd.read(h.buf, 5+1) != 5+1)
		return sys->sprint(readerr);
	if(int h.buf[1] & 1){
		h.transp = 1;
		h.trindex = h.buf[4];
	}
	return "";
}

inittbl()
{
	tbl = array[4096] of Entry;
	for(i:=0; i<258; i++) {
		tbl[i].prefix = -1;
		tbl[i].exten = i;
	}
}

decode(h: ref Header, i: ref Rawimage): (array of byte, string)
{
	c, incode: int;

	err := "";
	if(h.fd.read(h.buf, 1) != 1){
		err = sys->sprint("ReadGIF: can't read data: %r");
		return (nil, err);
	}
	codesize := int h.buf[0];
	if(codesize>8 || 0>codesize){
		err = sys->sprint("ReadGIF: can't handle codesize %d", codesize);
		return (nil, err);
	}
	err1 := "";
	if(i.cmap!=nil && len i.cmap!=3*(1<<codesize)
	  && (codesize!=2 || len i.cmap!=3*2)) # peculiar GIF bitmap files...
		err1 = sys->sprint("ReadGIF: codesize %d doesn't match color map 3*%d", codesize, len i.cmap/3);

	ch := chan of (array of byte, string);

	spawn readdata(h, ch);

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

	pic := array[(i.r.max.x-i.r.min.x)*(i.r.max.y-i.r.min.y)] of byte;
	pici := 0;
	data := array[0] of byte;
	datai := 0;

	nbits := 0;
	sreg := 0;
	stack := array[4096] of byte;
	stacki: int;
	fc := 0;

Init:
	for(;;){
		csize := codesize+1;
		nentry := EOD+1;
		maxentry := (1<<csize)-1;
		first := 1;
		ocode := -1;

		for(;; ocode = incode) {
			while(nbits < csize) {
				if(datai == len data){
					(data, err) = <-ch;
					if(data == nil)
						return (nil, err);
					if(err!="" && err1=="")
						err1 = err;
					if(len data == 0)
						break Init;
					datai = 0;
				}
				c = int data[datai++];
				sreg |= c<<nbits;
				nbits += 8;
			}
			code := sreg & ((1<<csize) - 1);
			sreg >>= csize;
			nbits -= csize;

			if(code == EOD){
				(data, err) = <-ch;
				if(len data != 0)
					err = "ReadGIF: unexpected data past EOD";
				if(err!="" && err1=="")
					err1 = err;
				break Init;
			}

			if(code == CTM)
				continue Init;

			stacki = len stack-1;

			incode = code;

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

			if(code > nentry) {
				err = sys->sprint("ReadGIF: bad code %x %x", code, nentry);
				return (nil, err);
			}
		
			for(c=code; c>=0; c=tbl[c].prefix)
				stack[stacki--] = byte tbl[c].exten;

			nb := len stack-(stacki+1);
			if(pici+nb > len pic){
				if(err1 == "")
					err1 = "ReadGIF: data overflows picture";
			}else{
				pic[pici:] = stack[stacki+1:];
				pici += nb;
			}

			fc = int 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);
				if(csize < 12)
					maxentry--;
			}
			tbl[nentry].prefix = ocode;
			tbl[nentry].exten = fc;
			nentry++;
		}
	}
	return (pic, err1);
}

interlace(image: ref Rawimage)
{
	pic := image.chans[0];
	r := image.r;
	dx := r.max.x-r.min.x;
	ipic := array[dx*(r.max.y-r.min.y)] of byte;

	# Group 1: every 8th row, starting with row 0
	yy := 0;
	for(y:=r.min.y; y<r.max.y; y+=8){
		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
		yy++;
	}

	# Group 2: every 8th row, starting with row 4
	for(y=r.min.y+4; y<r.max.y; y+=8){
		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
		yy++;
	}

	# Group 3: every 4th row, starting with row 2
	for(y=r.min.y+2; y<r.max.y; y+=4){
		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
		yy++;
	}

	# Group 4: every 2nd row, starting with row 1
	for(y=r.min.y+1; y<r.max.y; y+=2){
		ipic[y*dx:] = pic[yy*dx:(yy+1)*dx];
		yy++;
	}

	image.chans[0] = ipic;
}