| #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].index = i; |
| tbl[i].prefix = -1; |
| tbl[i].exten = i; |
| } |
| |
| 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 */ |
| } |
| } |
| |
| /* |
| * 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 = drawlog2[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-drawlog2[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 = drawlog2[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; |
| } |