plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

writegif.c (10728B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <memdraw.h>
      5 #include <bio.h>
      6 #include "imagefile.h"
      7 
      8 enum
      9 {
     10 	Nhash	= 4001,
     11 	Nbuf		= 300
     12 };
     13 
     14 typedef struct Entry Entry;
     15 typedef struct IO IO;
     16 
     17 
     18 struct Entry
     19 {
     20 	int		index;
     21 	int		prefix;
     22 	int		exten;
     23 	Entry	*next;
     24 };
     25 
     26 struct IO
     27 {
     28 	Biobuf	*fd;
     29 	uchar	buf[Nbuf];
     30 	int		i;
     31 	int		nbits;	/* bits in right side of shift register */
     32 	int		sreg;		/* shift register */
     33 };
     34 
     35 static Rectangle	mainrect;
     36 static Entry	tbl[4096];
     37 static uchar	*colormap[5];	/* one for each ldepth: GREY1 GREY2 GREY4 CMAP8=rgbv plus GREY8 */
     38 #define	GREYMAP	4
     39 static int		colormapsize[] = { 2, 4, 16, 256, 256 };	/* 2 for zero is an odd property of GIF */
     40 
     41 static void		writeheader(Biobuf*, Rectangle, int, ulong, int);
     42 static void		writedescriptor(Biobuf*, Rectangle);
     43 static char*	writedata(Biobuf*, Image*, Memimage*);
     44 static void		writecomment(Biobuf *fd, char*);
     45 static void		writegraphiccontrol(Biobuf *fd, int, int);
     46 static void*	gifmalloc(ulong);
     47 static void		encode(Biobuf*, Rectangle, int, uchar*, uint);
     48 
     49 static
     50 char*
     51 startgif0(Biobuf *fd, ulong chan, Rectangle r, int depth, int loopcount)
     52 {
     53 	int i;
     54 
     55 	for(i=0; i<nelem(tbl); i++){
     56 		tbl[i].index = i;
     57 		tbl[i].prefix = -1;
     58 		tbl[i].exten = i;
     59 	}
     60 
     61 	switch(chan){
     62 	case GREY1:
     63 	case GREY2:
     64 	case GREY4:
     65 	case CMAP8:
     66 	case GREY8:
     67 		break;
     68 	default:
     69 		return "WriteGIF: can't handle channel type";
     70 	}
     71 
     72 	mainrect = r;
     73 	writeheader(fd, r, depth, chan, loopcount);
     74 	return nil;
     75 }
     76 
     77 char*
     78 startgif(Biobuf *fd, Image *image, int loopcount)
     79 {
     80 	return startgif0(fd, image->chan, image->r, image->depth, loopcount);
     81 }
     82 
     83 char*
     84 memstartgif(Biobuf *fd, Memimage *memimage, int loopcount)
     85 {
     86 	return startgif0(fd, memimage->chan, memimage->r, memimage->depth, loopcount);
     87 }
     88 
     89 static
     90 char*
     91 writegif0(Biobuf *fd, Image *image, Memimage *memimage, ulong chan, Rectangle r, char *comment, int dt, int trans)
     92 {
     93 	char *err;
     94 
     95 	switch(chan){
     96 	case GREY1:
     97 	case GREY2:
     98 	case GREY4:
     99 	case CMAP8:
    100 	case GREY8:
    101 		break;
    102 	default:
    103 		return "WriteGIF: can't handle channel type";
    104 	}
    105 
    106 	writecomment(fd, comment);
    107 	writegraphiccontrol(fd, dt, trans);
    108 	writedescriptor(fd, r);
    109 
    110 	err = writedata(fd, image, memimage);
    111 	if(err != nil)
    112 		return err;
    113 
    114 	return nil;
    115 }
    116 
    117 char*
    118 writegif(Biobuf *fd, Image *image, char *comment, int dt, int trans)
    119 {
    120 	return writegif0(fd, image, nil, image->chan, image->r, comment, dt, trans);
    121 }
    122 
    123 char*
    124 memwritegif(Biobuf *fd, Memimage *memimage, char *comment, int dt, int trans)
    125 {
    126 	return writegif0(fd, nil, memimage, memimage->chan, memimage->r, comment, dt, trans);
    127 }
    128 
    129 /*
    130  * Write little-endian 16-bit integer
    131  */
    132 static
    133 void
    134 put2(Biobuf *fd, int i)
    135 {
    136 	Bputc(fd, i);
    137 	Bputc(fd, i>>8);
    138 }
    139 
    140 /*
    141  * Get color map for all ldepths, in format suitable for writing out
    142  */
    143 static
    144 void
    145 getcolormap(void)
    146 {
    147 	int i, col;
    148 	ulong rgb;
    149 	uchar *c;
    150 
    151 	if(colormap[0] != nil)
    152 		return;
    153 	for(i=0; i<nelem(colormap); i++)
    154 		colormap[i] = gifmalloc(3* colormapsize[i]);
    155 	c = colormap[GREYMAP];	/* GREY8 */
    156 	for(i=0; i<256; i++){
    157 		c[3*i+0] = i;	/* red */
    158 		c[3*i+1] = i;	/* green */
    159 		c[3*i+2] = i;	/* blue */
    160 	}
    161 	c = colormap[3];	/* RGBV */
    162 	for(i=0; i<256; i++){
    163 		rgb = cmap2rgb(i);
    164 		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
    165 		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
    166 		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
    167 	}
    168 	c = colormap[2];	/* GREY4 */
    169 	for(i=0; i<16; i++){
    170 		col = (i<<4)|i;
    171 		rgb = cmap2rgb(col);
    172 		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
    173 		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
    174 		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
    175 	}
    176 	c = colormap[1];	/* GREY2 */
    177 	for(i=0; i<4; i++){
    178 		col = (i<<6)|(i<<4)|(i<<2)|i;
    179 		rgb = cmap2rgb(col);
    180 		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
    181 		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
    182 		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
    183 	}
    184 	c = colormap[0];	/* GREY1 */
    185 	for(i=0; i<2; i++){
    186 		if(i == 0)
    187 			col = 0;
    188 		else
    189 			col = 0xFF;
    190 		rgb = cmap2rgb(col);
    191 		c[3*i+0] = (rgb>>16) & 0xFF;	/* red */
    192 		c[3*i+1] = (rgb>> 8) & 0xFF;	/* green */
    193 		c[3*i+2] = (rgb>> 0) & 0xFF;	/* blue */
    194 	}
    195 }
    196 
    197 /*
    198  * Write header, logical screen descriptor, and color map
    199  */
    200 static
    201 void
    202 writeheader(Biobuf *fd, Rectangle r, int depth, ulong chan, int loopcount)
    203 {
    204 	/* Header */
    205 	Bprint(fd, "%s", "GIF89a");
    206 
    207 	/*  Logical Screen Descriptor */
    208 	put2(fd, Dx(r));
    209 	put2(fd, Dy(r));
    210 
    211 	/* Color table present, 4 bits per color (for RGBV best case), size of color map */
    212 	Bputc(fd, (1<<7)|(3<<4)|(depth-1));	/* not right for GREY8, but GIF doesn't let us specify enough bits */
    213 	Bputc(fd, 0xFF);	/* white background (doesn't matter anyway) */
    214 	Bputc(fd, 0);	/* pixel aspect ratio - unused */
    215 
    216 	/* Global Color Table */
    217 	getcolormap();
    218 	if(chan == GREY8)
    219 		depth = GREYMAP;
    220 	else
    221 		depth = drawlog2[depth];
    222 	Bwrite(fd, colormap[depth], 3*colormapsize[depth]);
    223 
    224 	if(loopcount >= 0){	/* hard-to-discover way to force cycled animation */
    225 		/* Application Extension with (1 loopcountlo loopcounthi) as data */
    226 		Bputc(fd, 0x21);
    227 		Bputc(fd, 0xFF);
    228 		Bputc(fd, 11);
    229 		Bwrite(fd, "NETSCAPE2.0", 11);
    230 		Bputc(fd, 3);
    231 		Bputc(fd, 1);
    232 		put2(fd, loopcount);
    233 		Bputc(fd, 0);
    234 	}
    235 }
    236 
    237 /*
    238  * Write optional comment block
    239  */
    240 static
    241 void
    242 writecomment(Biobuf *fd, char *comment)
    243 {
    244 	int n;
    245 
    246 	if(comment==nil || comment[0]=='\0')
    247 		return;
    248 
    249 	/* Comment extension and label */
    250 	Bputc(fd, 0x21);
    251 	Bputc(fd, 0xFE);
    252 
    253 	/* Comment data */
    254 	n = strlen(comment);
    255 	if(n > 255)
    256 		n = 255;
    257 	Bputc(fd, n);
    258 	Bwrite(fd, comment, n);
    259 
    260 	/* Block terminator */
    261 	Bputc(fd, 0x00);
    262 }
    263 
    264 /*
    265  * Write optional control block (sets Delay Time)
    266  */
    267 static
    268 void
    269 writegraphiccontrol(Biobuf *fd, int dt, int trans)
    270 {
    271 	if(dt < 0 && trans < 0)
    272 		return;
    273 
    274 	/* Comment extension and label and block size*/
    275 	Bputc(fd, 0x21);
    276 	Bputc(fd, 0xF9);
    277 	Bputc(fd, 0x04);
    278 
    279 	/* Disposal method and other flags (none) */
    280 	if(trans >= 0)
    281 		Bputc(fd, 0x01);
    282 	else
    283 		Bputc(fd, 0x00);
    284 
    285 	/* Delay time, in centisec (argument is millisec for sanity) */
    286 	if(dt < 0)
    287 		dt = 0;
    288 	else if(dt < 10)
    289 		dt = 1;
    290 	else
    291 		dt = (dt+5)/10;
    292 	put2(fd, dt);
    293 
    294 	/* Transparency index */
    295 	if(trans < 0)
    296 		trans = 0;
    297 	Bputc(fd, trans);
    298 
    299 	/* Block terminator */
    300 	Bputc(fd, 0x00);
    301 }
    302 
    303 /*
    304  * Write image descriptor
    305  */
    306 static
    307 void
    308 writedescriptor(Biobuf *fd, Rectangle r)
    309 {
    310 	/* Image Separator */
    311 	Bputc(fd, 0x2C);
    312 
    313 	/* Left, top, width, height */
    314 	put2(fd, r.min.x-mainrect.min.x);
    315 	put2(fd, r.min.y-mainrect.min.y);
    316 	put2(fd, Dx(r));
    317 	put2(fd, Dy(r));
    318 	/* no special processing */
    319 	Bputc(fd, 0);
    320 }
    321 
    322 /*
    323  * Write data
    324  */
    325 static
    326 char*
    327 writedata(Biobuf *fd, Image *image, Memimage *memimage)
    328 {
    329 	char *err;
    330 	uchar *data;
    331 	int ndata, depth;
    332 	Rectangle r;
    333 
    334 	if(memimage != nil){
    335 		r = memimage->r;
    336 		depth = memimage->depth;
    337 	}else{
    338 		r = image->r;
    339 		depth = image->depth;
    340 	}
    341 
    342 	/* LZW Minimum code size */
    343 	if(depth == 1)
    344 		Bputc(fd, 2);
    345 	else
    346 		Bputc(fd, depth);
    347 
    348 	/*
    349 	 * Read image data into memory
    350 	 * potentially one extra byte on each end of each scan line
    351 	 */
    352 	ndata = Dy(r)*(2+(Dx(r)>>(3-drawlog2[depth])));
    353 	data = gifmalloc(ndata);
    354 	if(memimage != nil)
    355 		ndata = unloadmemimage(memimage, r, data, ndata);
    356 	else
    357 		ndata = unloadimage(image, r, data, ndata);
    358 	if(ndata < 0){
    359 		err = gifmalloc(ERRMAX);
    360 		snprint(err, ERRMAX, "WriteGIF: %r");
    361 		free(data);
    362 		return err;
    363 	}
    364 
    365 	/* Encode and emit the data */
    366 	encode(fd, r, depth, data, ndata);
    367 	free(data);
    368 
    369 	/*  Block Terminator */
    370 	Bputc(fd, 0);
    371 	return nil;
    372 }
    373 
    374 /*
    375  * Write trailer
    376  */
    377 void
    378 endgif(Biobuf *fd)
    379 {
    380 	Bputc(fd, 0x3B);
    381 	Bflush(fd);
    382 }
    383 
    384 void
    385 memendgif(Biobuf *fd)
    386 {
    387 	endgif(fd);
    388 }
    389 
    390 /*
    391  * Put n bits of c into output at io.buf[i];
    392  */
    393 static
    394 void
    395 output(IO *io, int c, int n)
    396 {
    397 	if(c < 0){
    398 		if(io->nbits != 0)
    399 			io->buf[io->i++] = io->sreg;
    400 		Bputc(io->fd, io->i);
    401 		Bwrite(io->fd, io->buf, io->i);
    402 		io->nbits = 0;
    403 		return;
    404 	}
    405 
    406 	if(io->nbits+n >= 31){
    407 		fprint(2, "panic: WriteGIF sr overflow\n");
    408 		exits("WriteGIF panic");
    409 	}
    410 	io->sreg |= c<<io->nbits;
    411 	io->nbits += n;
    412 
    413 	while(io->nbits >= 8){
    414 		io->buf[io->i++] = io->sreg;
    415 		io->sreg >>= 8;
    416 		io->nbits -= 8;
    417 	}
    418 
    419 	if(io->i >= 255){
    420 		Bputc(io->fd, 255);
    421 		Bwrite(io->fd, io->buf, 255);
    422 		memmove(io->buf, io->buf+255, io->i-255);
    423 		io->i -= 255;
    424 	}
    425 }
    426 
    427 /*
    428  * LZW encoder
    429  */
    430 static
    431 void
    432 encode(Biobuf *fd, Rectangle r, int depth, uchar *data, uint ndata)
    433 {
    434 	int i, c, h, csize, prefix, first, sreg, nbits, bitsperpixel;
    435 	int CTM, EOD, codesize, ld0, datai, x, ld, pm;
    436 	int nentry, maxentry, early;
    437 	Entry *e, *oe;
    438 	IO *io;
    439 	Entry **hash;
    440 
    441 	first = 1;
    442 	ld = drawlog2[depth];
    443 	/* ldepth 0 must generate codesize 2 with values 0 and 1 (see the spec.) */
    444 	ld0 = ld;
    445 	if(ld0 == 0)
    446 		ld0 = 1;
    447 	codesize = (1<<ld0);
    448 	CTM = 1<<codesize;
    449 	EOD = CTM+1;
    450 
    451 	io = gifmalloc(sizeof(IO));
    452 	io->fd = fd;
    453 	sreg = 0;
    454 	nbits = 0;
    455 	bitsperpixel = 1<<ld;
    456 	pm = (1<<bitsperpixel)-1;
    457 
    458 	datai = 0;
    459 	x = r.min.x;
    460 	hash = gifmalloc(Nhash*sizeof(Entry*));
    461 
    462 Init:
    463 	memset(hash, 0, Nhash*sizeof(Entry*));
    464 	csize = codesize+1;
    465 	nentry = EOD+1;
    466 	maxentry = (1<<csize);
    467 	for(i = 0; i<nentry; i++){
    468 		e = &tbl[i];
    469 		h = (e->prefix<<24) | (e->exten<<8);
    470 		h %= Nhash;
    471 		if(h < 0)
    472 			h += Nhash;
    473 		e->next = hash[h];
    474 		hash[h] = e;
    475 	}
    476 	prefix = -1;
    477 	if(first)
    478 		output(io, CTM, csize);
    479 	first = 0;
    480 
    481 	/*
    482 	 * Scan over pixels.  Because of partially filled bytes on ends of scan lines,
    483 	 * which must be ignored in the data stream passed to GIF, this is more
    484 	 * complex than we'd like.
    485 	 */
    486 Next:
    487 	for(;;){
    488 		if(ld != 3){
    489 			/* beginning of scan line is difficult; prime the shift register */
    490 			if(x == r.min.x){
    491 				if(datai == ndata)
    492 					break;
    493 				sreg = data[datai++];
    494 				nbits = 8-((x&(7>>ld))<<ld);
    495 			}
    496 			x++;
    497 			if(x == r.max.x)
    498 				x = r.min.x;
    499 		}
    500 		if(nbits == 0){
    501 			if(datai == ndata)
    502 				break;
    503 			sreg = data[datai++];
    504 			nbits = 8;
    505 		}
    506 		nbits -= bitsperpixel;
    507 		c = sreg>>nbits & pm;
    508 		h = prefix<<24 | c<<8;
    509 		h %= Nhash;
    510 		if(h < 0)
    511 			h += Nhash;
    512 		oe = nil;
    513 		for(e = hash[h]; e!=nil; e=e->next){
    514 			if(e->prefix == prefix && e->exten == c){
    515 				if(oe != nil){
    516 					oe->next = e->next;
    517 					e->next = hash[h];
    518 					hash[h] = e;
    519 				}
    520 				prefix = e->index;
    521 				goto Next;
    522 			}
    523 			oe = e;
    524 		}
    525 
    526 		output(io, prefix, csize);
    527 		early = 0; /* peculiar tiff feature here for reference */
    528 		if(nentry == maxentry-early){
    529 			if(csize == 12){
    530 				nbits += bitsperpixel;	/* unget pixel */
    531 				x--;
    532 				if(ld != 3 && x == r.min.x)
    533 					datai--;
    534 				output(io, CTM, csize);
    535 				goto Init;
    536 			}
    537 			csize++;
    538 			maxentry = (1<<csize);
    539 		}
    540 
    541 		e = &tbl[nentry];
    542 		e->prefix = prefix;
    543 		e->exten = c;
    544 		e->next = hash[h];
    545 		hash[h] = e;
    546 
    547 		prefix = c;
    548 		nentry++;
    549 	}
    550 
    551 	output(io, prefix, csize);
    552 	output(io, EOD, csize);
    553 	output(io, -1, csize);
    554 	free(io);
    555 	free(hash);
    556 }
    557 
    558 static
    559 void*
    560 gifmalloc(ulong sz)
    561 {
    562 	void *v;
    563 	v = malloc(sz);
    564 	if(v == nil) {
    565 		fprint(2, "WriteGIF: out of memory allocating %ld\n", sz);
    566 abort();
    567 		exits("mem");
    568 	}
    569 	memset(v, 0, sz);
    570 	return v;
    571 }