summaryrefslogtreecommitdiff
path: root/appl/lib/writegif.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/writegif.b')
-rw-r--r--appl/lib/writegif.b362
1 files changed, 362 insertions, 0 deletions
diff --git a/appl/lib/writegif.b b/appl/lib/writegif.b
new file mode 100644
index 00000000..3990ed63
--- /dev/null
+++ b/appl/lib/writegif.b
@@ -0,0 +1,362 @@
+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;
+}