aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/jpg/writegif.c
diff options
context:
space:
mode:
authorrsc <devnull@localhost>2004-04-21 22:19:33 +0000
committerrsc <devnull@localhost>2004-04-21 22:19:33 +0000
commit28994509cc11ac6a5443054dfae1fedfb69039bc (patch)
tree9d5adcd11af2708db0ecc246e008c308ca0f97d4 /src/cmd/jpg/writegif.c
parenta01e58366c54804f15f84d6e21d13f2e4080977a (diff)
downloadplan9port-28994509cc11ac6a5443054dfae1fedfb69039bc.tar.gz
plan9port-28994509cc11ac6a5443054dfae1fedfb69039bc.tar.bz2
plan9port-28994509cc11ac6a5443054dfae1fedfb69039bc.zip
Why not?
Diffstat (limited to 'src/cmd/jpg/writegif.c')
-rw-r--r--src/cmd/jpg/writegif.c568
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;
+}