ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/readgif.b/
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;
}