575 lines
11 KiB
C
575 lines
11 KiB
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;
|
|
}
|