123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- #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;
- uint8_t 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 uint8_t *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, uint32_t,
- int);
- static void writedescriptor(Biobuf*, Rectangle);
- static char* writedata(Biobuf*, Image*, Memimage*);
- static void writetrailer(Biobuf *fd);
- static void writecomment(Biobuf *fd, char*);
- static void writegraphiccontrol(Biobuf *fd, int, int);
- static void* gifmalloc(uint32_t);
- static void encode(Biobuf*, Rectangle, int, uint8_t*, uint);
- static
- char*
- startgif0(Biobuf *fd, uint32_t 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, uint32_t 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;
- uint32_t rgb;
- uint8_t *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, uint32_t 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;
- uint8_t *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, uint8_t *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(uint32_t 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;
- }
|