ref: dfb4b522830edab8f3856289d326d6cf5e930644
dir: /sys/src/cmd/jpg/writegif.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <memdraw.h> #include <bio.h> #include "imagefile.h" enum { Nhash = 4001, Nbuf = 300, }; typedef struct Entry Entry; typedef struct IO IO; struct Entry { int index; int prefix; int exten; Entry *next; }; struct IO { Biobuf *fd; uchar buf[Nbuf]; int i; int nbits; /* bits in right side of shift register */ int sreg; /* shift register */ }; static Rectangle mainrect; static Entry tbl[4096]; static uchar *colormap[5]; /* one for each ldepth: GREY1 GREY2 GREY4 CMAP8=rgbv plus GREY8 */ #define GREYMAP 4 static int colormapsize[] = { 2, 4, 16, 256, 256 }; /* 2 for zero is an odd property of GIF */ static void writeheader(Biobuf*, Rectangle, int, ulong, int); static void writedescriptor(Biobuf*, Rectangle); static char* writedata(Biobuf*, Image*, Memimage*); static void writecomment(Biobuf *fd, char*); static void writegraphiccontrol(Biobuf *fd, int, int); static void* gifmalloc(ulong); static void encode(Biobuf*, Rectangle, int, uchar*, uint); static char* startgif0(Biobuf *fd, ulong chan, Rectangle r, int depth, int loopcount) { int i; for(i=0; i<nelem(tbl); i++) tbl[i] = (Entry){i, -1, i, nil}; switch(chan){ case GREY1: case GREY2: case GREY4: case CMAP8: case GREY8: break; default: return "WriteGIF: can't handle channel type"; } mainrect = r; writeheader(fd, r, depth, chan, loopcount); return nil; } char* startgif(Biobuf *fd, Image *image, int loopcount) { return startgif0(fd, image->chan, image->r, image->depth, loopcount); } char* memstartgif(Biobuf *fd, Memimage *memimage, int loopcount) { return startgif0(fd, memimage->chan, memimage->r, memimage->depth, loopcount); } static char* writegif0(Biobuf *fd, Image *image, Memimage *memimage, ulong chan, Rectangle r, char *comment, int dt, int trans) { char *err; switch(chan){ case GREY1: case GREY2: case GREY4: case CMAP8: case GREY8: break; default: return "WriteGIF: can't handle channel type"; } writecomment(fd, comment); writegraphiccontrol(fd, dt, trans); writedescriptor(fd, r); err = writedata(fd, image, memimage); if(err != nil) return err; return nil; } char* writegif(Biobuf *fd, Image *image, char *comment, int dt, int trans) { return writegif0(fd, image, nil, image->chan, image->r, comment, dt, trans); } char* memwritegif(Biobuf *fd, Memimage *memimage, char *comment, int dt, int trans) { return writegif0(fd, nil, memimage, memimage->chan, memimage->r, comment, dt, trans); } /* * Write little-endian 16-bit integer */ static void put2(Biobuf *fd, int i) { Bputc(fd, i); Bputc(fd, i>>8); } /* * Get color map for all ldepths, in format suitable for writing out */ static void getcolormap(void) { int i, col; ulong rgb; uchar *c; if(colormap[0] != nil) return; for(i=0; i<nelem(colormap); i++) colormap[i] = gifmalloc(3* colormapsize[i]); c = colormap[GREYMAP]; /* GREY8 */ for(i=0; i<256; i++){ c[3*i+0] = i; /* red */ c[3*i+1] = i; /* green */ c[3*i+2] = i; /* blue */ } c = colormap[3]; /* RGBV */ for(i=0; i<256; i++){ rgb = cmap2rgb(i); c[3*i+0] = (rgb>>16) & 0xFF; /* red */ c[3*i+1] = (rgb>> 8) & 0xFF; /* green */ c[3*i+2] = (rgb>> 0) & 0xFF; /* blue */ } c = colormap[2]; /* GREY4 */ for(i=0; i<16; i++){ col = (i<<4)|i; rgb = cmap2rgb(col); c[3*i+0] = (rgb>>16) & 0xFF; /* red */ c[3*i+1] = (rgb>> 8) & 0xFF; /* green */ c[3*i+2] = (rgb>> 0) & 0xFF; /* blue */ } c = colormap[1]; /* GREY2 */ for(i=0; i<4; i++){ col = (i<<6)|(i<<4)|(i<<2)|i; rgb = cmap2rgb(col); c[3*i+0] = (rgb>>16) & 0xFF; /* red */ c[3*i+1] = (rgb>> 8) & 0xFF; /* green */ c[3*i+2] = (rgb>> 0) & 0xFF; /* blue */ } c = colormap[0]; /* GREY1 */ for(i=0; i<2; i++){ if(i == 0) col = 0; else col = 0xFF; rgb = cmap2rgb(col); c[3*i+0] = (rgb>>16) & 0xFF; /* red */ c[3*i+1] = (rgb>> 8) & 0xFF; /* green */ c[3*i+2] = (rgb>> 0) & 0xFF; /* blue */ } } /* imported from libdraw/arith.c to permit an extern log2 function */ static int log2[] = { -1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, 4 /* BUG */, -1, -1, -1, -1, -1, -1, -1, 5 }; /* * Write header, logical screen descriptor, and color map */ static void writeheader(Biobuf *fd, Rectangle r, int depth, ulong chan, int loopcount) { /* Header */ Bprint(fd, "%s", "GIF89a"); /* Logical Screen Descriptor */ put2(fd, Dx(r)); put2(fd, Dy(r)); /* Color table present, 4 bits per color (for RGBV best case), size of color map */ Bputc(fd, (1<<7)|(3<<4)|(depth-1)); /* not right for GREY8, but GIF doesn't let us specify enough bits */ Bputc(fd, 0xFF); /* white background (doesn't matter anyway) */ Bputc(fd, 0); /* pixel aspect ratio - unused */ /* Global Color Table */ getcolormap(); if(chan == GREY8) depth = GREYMAP; else depth = log2[depth]; Bwrite(fd, colormap[depth], 3*colormapsize[depth]); if(loopcount >= 0){ /* hard-to-discover way to force cycled animation */ /* Application Extension with (1 loopcountlo loopcounthi) as data */ Bputc(fd, 0x21); Bputc(fd, 0xFF); Bputc(fd, 11); Bwrite(fd, "NETSCAPE2.0", 11); Bputc(fd, 3); Bputc(fd, 1); put2(fd, loopcount); Bputc(fd, 0); } } /* * Write optional comment block */ static void writecomment(Biobuf *fd, char *comment) { int n; if(comment==nil || comment[0]=='\0') return; /* Comment extension and label */ Bputc(fd, 0x21); Bputc(fd, 0xFE); /* Comment data */ n = strlen(comment); if(n > 255) n = 255; Bputc(fd, n); Bwrite(fd, comment, n); /* Block terminator */ Bputc(fd, 0x00); } /* * Write optional control block (sets Delay Time) */ static void writegraphiccontrol(Biobuf *fd, int dt, int trans) { if(dt < 0 && trans < 0) return; /* Comment extension and label and block size*/ Bputc(fd, 0x21); Bputc(fd, 0xF9); Bputc(fd, 0x04); /* Disposal method and other flags (none) */ if(trans >= 0) Bputc(fd, 0x01); else Bputc(fd, 0x00); /* Delay time, in centisec (argument is millisec for sanity) */ if(dt < 0) dt = 0; else if(dt < 10) dt = 1; else dt = (dt+5)/10; put2(fd, dt); /* Transparency index */ if(trans < 0) trans = 0; Bputc(fd, trans); /* Block terminator */ Bputc(fd, 0x00); } /* * Write image descriptor */ static void writedescriptor(Biobuf *fd, Rectangle r) { /* Image Separator */ Bputc(fd, 0x2C); /* Left, top, width, height */ put2(fd, r.min.x-mainrect.min.x); put2(fd, r.min.y-mainrect.min.y); put2(fd, Dx(r)); put2(fd, Dy(r)); /* no special processing */ Bputc(fd, 0); } /* * Write data */ static char* writedata(Biobuf *fd, Image *image, Memimage *memimage) { char *err; uchar *data; int ndata, depth; Rectangle r; if(memimage != nil){ r = memimage->r; depth = memimage->depth; }else{ r = image->r; depth = image->depth; } /* LZW Minimum code size */ if(depth == 1) Bputc(fd, 2); else Bputc(fd, depth); /* * Read image data into memory * potentially one extra byte on each end of each scan line */ ndata = Dy(r)*(2+(Dx(r)>>(3-log2[depth]))); data = gifmalloc(ndata); if(memimage != nil) ndata = unloadmemimage(memimage, r, data, ndata); else ndata = unloadimage(image, r, data, ndata); if(ndata < 0){ err = gifmalloc(ERRMAX); snprint(err, ERRMAX, "WriteGIF: %r"); free(data); return err; } /* Encode and emit the data */ encode(fd, r, depth, data, ndata); free(data); /* Block Terminator */ Bputc(fd, 0); return nil; } /* * Write trailer */ void endgif(Biobuf *fd) { Bputc(fd, 0x3B); Bflush(fd); } void memendgif(Biobuf *fd) { endgif(fd); } /* * Put n bits of c into output at io.buf[i]; */ static void output(IO *io, int c, int n) { if(c < 0){ if(io->nbits != 0) io->buf[io->i++] = io->sreg; Bputc(io->fd, io->i); Bwrite(io->fd, io->buf, io->i); io->nbits = 0; return; } if(io->nbits+n >= 31){ fprint(2, "panic: WriteGIF sr overflow\n"); exits("WriteGIF panic"); } io->sreg |= c<<io->nbits; io->nbits += n; while(io->nbits >= 8){ io->buf[io->i++] = io->sreg; io->sreg >>= 8; io->nbits -= 8; } if(io->i >= 255){ Bputc(io->fd, 255); Bwrite(io->fd, io->buf, 255); memmove(io->buf, io->buf+255, io->i-255); io->i -= 255; } } /* * LZW encoder */ static void encode(Biobuf *fd, Rectangle r, int depth, uchar *data, uint ndata) { int i, c, h, csize, prefix, first, sreg, nbits, bitsperpixel; int CTM, EOD, codesize, ld0, datai, x, ld, pm; int nentry, maxentry, early; Entry *e, *oe; IO *io; Entry **hash; first = 1; ld = log2[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 = gifmalloc(sizeof(IO)); io->fd = fd; sreg = 0; nbits = 0; bitsperpixel = 1<<ld; pm = (1<<bitsperpixel)-1; datai = 0; x = r.min.x; hash = gifmalloc(Nhash*sizeof(Entry*)); Init: memset(hash, 0, Nhash*sizeof(Entry*)); csize = codesize+1; nentry = EOD+1; maxentry = (1<<csize); 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 == r.min.x){ if(datai == ndata) break; sreg = data[datai++]; nbits = 8-((x&(7>>ld))<<ld); } x++; if(x == r.max.x) x = r.min.x; } if(nbits == 0){ if(datai == ndata) break; sreg = 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; goto Next; } oe = e; } output(io, prefix, csize); early = 0; /* peculiar tiff feature here for reference */ if(nentry == maxentry-early){ if(csize == 12){ nbits += bitsperpixel; /* unget pixel */ x--; if(ld != 3 && x == r.min.x) datai--; output(io, CTM, csize); goto Init; } csize++; maxentry = (1<<csize); } e = &tbl[nentry]; e->prefix = prefix; e->exten = c; e->next = hash[h]; hash[h] = e; prefix = c; nentry++; } output(io, prefix, csize); output(io, EOD, csize); output(io, -1, csize); free(io); free(hash); } static void* gifmalloc(ulong sz) { void *v; v = malloc(sz); if(v == nil) { fprint(2, "WriteGIF: out of memory allocating %ld\n", sz); abort(); exits("mem"); } memset(v, 0, sz); return v; }