diff options
Diffstat (limited to 'src/cmd/jpg/writegif.c')
-rw-r--r-- | src/cmd/jpg/writegif.c | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/src/cmd/jpg/writegif.c b/src/cmd/jpg/writegif.c new file mode 100644 index 00000000..16402ef8 --- /dev/null +++ b/src/cmd/jpg/writegif.c @@ -0,0 +1,568 @@ +#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 */ + } +} + +/* + * 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; +} |