ref: 02ac617541ca1a7bf82b1615fb5a58235469b5d3
dir: /appl/lib/writegif.b/
implement WImagefile; include "sys.m"; sys: Sys; include "draw.m"; draw: Draw; Chans, Display, Image, Rect: import draw; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "imagefile.m"; Nhash: con 4001; Entry: adt { index: int; prefix: int; exten: int; next: cyclic ref Entry; }; IO: adt { fd: ref Iobuf; buf: array of byte; i: int; nbits: int; # bits in right side of shift register sreg: int; # shift register }; tbl: array of ref Entry; colormap: array of array of byte; log2 := array[] of {1 => 0, 2 => 1, 4 => 2, 8 => 3, * => -1}; init(iomod: Bufio) { if(sys == nil){ sys = load Sys Sys->PATH; draw = load Draw Draw->PATH; } bufio = iomod; } writeimage(fd: ref Iobuf, image: ref Image): string { case image.chans.desc { (Draw->GREY1).desc or (Draw->GREY2).desc or (Draw->GREY4).desc or (Draw->GREY8).desc or (Draw->CMAP8).desc => if(image.depth > 8 || (image.depth&(image.depth-1)) != 0) return "inconsistent depth"; * => return "unsupported channel type"; } inittbl(); writeheader(fd, image); writedescriptor(fd, image); err := writedata(fd, image); if(err != nil) return err; writetrailer(fd); fd.flush(); return err; } inittbl() { tbl = array[4096] of ref Entry; for(i:=0; i<len tbl; i++) tbl[i] = ref Entry(i, -1, i, nil); } # Write header, logical screen descriptor, and color map writeheader(fd: ref Iobuf, image: ref Image): string { # Header fd.puts("GIF89a"); # Logical Screen Descriptor put2(fd, image.r.dx()); put2(fd, image.r.dy()); # color table present, 4 bits per color (for RGBV best case), size of color map fd.putb(byte ((1<<7)|(3<<4)|(image.depth-1))); fd.putb(byte 0); # white background (doesn't matter anyway) fd.putb(byte 0); # pixel aspect ratio - unused # Global Color Table getcolormap(image); ldepth := log2[image.depth]; if(image.chans.eq(Draw->GREY8)) ldepth = 4; fd.write(colormap[ldepth], len colormap[ldepth]); return nil; } # Write image descriptor writedescriptor(fd: ref Iobuf, image: ref Image) { # Image Separator fd.putb(byte 16r2C); # Left, top, width, height put2(fd, 0); put2(fd, 0); put2(fd, image.r.dx()); put2(fd, image.r.dy()); # no special processing fd.putb(byte 0); } # Write data writedata(fd: ref Iobuf, image: ref Image): string { # LZW Minimum code size if(image.depth == 1) fd.putb(byte 2); else fd.putb(byte image.depth); # Encode and emit the data err := encode(fd, image); if(err != nil) return err; # Block Terminator fd.putb(byte 0); return nil; } # Write data writetrailer(fd: ref Iobuf) { fd.putb(byte 16r3B); } # Write little-endian 16-bit integer put2(fd: ref Iobuf, i: int) { fd.putb(byte i); fd.putb(byte (i>>8)); } # Get color map for all ldepths, in format suitable for writing out getcolormap(image: ref Draw->Image) { if(colormap != nil) return; colormap = array[5] of array of byte; display := image.display; colormap[4] = array[3*256] of byte; colormap[3] = array[3*256] of byte; colormap[2] = array[3*16] of byte; colormap[1] = array[3*4] of byte; colormap[0] = array[3*2] of byte; c := colormap[4]; for(i:=0; i<256; i++){ c[3*i+0] = byte i; c[3*i+1] = byte i; c[3*i+2] = byte i; } c = colormap[3]; for(i=0; i<256; i++){ (r, g, b) := display.cmap2rgb(i); c[3*i+0] = byte r; c[3*i+1] = byte g; c[3*i+2] = byte b; } c = colormap[2]; for(i=0; i<16; i++){ col := (i<<4)|i; (r, g, b) := display.cmap2rgb(col); c[3*i+0] = byte r; c[3*i+1] = byte g; c[3*i+2] = byte b; } c = colormap[1]; for(i=0; i<4; i++){ col := (i<<6)|(i<<4)|(i<<2)|i; (r, g, b) := display.cmap2rgb(col); c[3*i+0] = byte r; c[3*i+1] = byte g; c[3*i+2] = byte b; } c = colormap[0]; for(i=0; i<2; i++){ if(i == 0) col := 0; else col = 16rFF; (r, g, b) := display.cmap2rgb(col); c[3*i+0] = byte r; c[3*i+1] = byte g; c[3*i+2] = byte b; } } # Put n bits of c into output at io.buf[i]; output(io: ref IO, c, n: int) { if(c < 0){ if(io.nbits != 0) io.buf[io.i++] = byte io.sreg; io.fd.putb(byte io.i); io.fd.write(io.buf, io.i); io.nbits = 0; return; } if(io.nbits+n >= 31){ sys->print("panic: WriteGIF sr overflow\n"); exit; } io.sreg |= c<<io.nbits; io.nbits += n; while(io.nbits >= 8){ io.buf[io.i++] = byte io.sreg; io.sreg >>= 8; io.nbits -= 8; } if(io.i >= 255){ io.fd.putb(byte 255); io.fd.write(io.buf, 255); io.buf[0:] = io.buf[255:io.i]; io.i -= 255; } } # LZW encoder encode(fd: ref Iobuf, image: ref Image): string { c, h, csize, prefix: int; e, oe: ref Entry; first := 1; ld := log2[image.depth]; # ldepth 0 must generate codesize 2 with values 0 and 1 (see the spec.) ld0 := ld; if(ld0 == 0) ld0 = 1; codesize := (1<<ld0); CTM := 1<<codesize; EOD := CTM+1; io := ref IO (fd, array[300] of byte, 0, 0, 0); sreg := 0; nbits := 0; bitsperpixel := 1<<ld; pm := (1<<bitsperpixel)-1; # Read image data into memory # potentially one extra byte on each end of each scan line data := array[image.r.dy()*(2+(image.r.dx()>>(3-log2[image.depth])))] of byte; ndata := image.readpixels(image.r, data); if(ndata < 0) return sys->sprint("WriteGIF: readpixels: %r"); datai := 0; x := image.r.min.x; Init: for(;;){ csize = codesize+1; nentry := EOD+1; maxentry := (1<<csize); hash := array[Nhash] of ref Entry; for(i := 0; i<nentry; i++){ e = tbl[i]; h = (e.prefix<<24) | (e.exten<<8); h %= Nhash; if(h < 0) h += Nhash; e.next = hash[h]; hash[h] = e; } prefix = -1; if(first) output(io, CTM, csize); first = 0; # Scan over pixels. Because of partially filled bytes on ends of scan lines, # which must be ignored in the data stream passed to GIF, this is more # complex than we'd like Next: for(;;){ if(ld != 3){ # beginning of scan line is difficult; prime the shift register if(x == image.r.min.x){ if(datai == ndata) break; sreg = int data[datai++]; nbits = 8-((x&(7>>ld))<<ld); } x++; if(x == image.r.max.x) x = image.r.min.x; } if(nbits == 0){ if(datai == ndata) break; sreg = int data[datai++]; nbits = 8; } nbits -= bitsperpixel; c = sreg>>nbits & pm; h = prefix<<24 | c<<8; h %= Nhash; if(h < 0) h += Nhash; oe = nil; for(e = hash[h]; e!=nil; e=e.next){ if(e.prefix == prefix && e.exten == c){ if(oe != nil){ oe.next = e.next; e.next = hash[h]; hash[h] = e; } prefix = e.index; continue Next; } oe = e; } output(io, prefix, csize); early:=0; # peculiar tiff feature here for reference if(nentry == maxentry-early){ if(csize == 12){ nbits += codesize; # unget pixel x--; output(io, CTM, csize); continue Init; } csize++; maxentry = (1<<csize); } e = tbl[nentry]; e.prefix = prefix; e.exten = c; e.next = hash[h]; hash[h] = e; prefix = c; nentry++; } break Init; } output(io, prefix, csize); output(io, EOD, csize); output(io, -1, csize); return nil; }