/*
 * This software is copyrighted as noted below.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is
 * preserved on all copies.
 *
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the author, who may or may not act on them as he desires.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 */
/* +------------------------------------------------------------------+ */
/* | Copyright 1989, David Koblas.                                    | */
/* |   You may copy this file in whole or in part as long as you      | */
/* |   don't try to make money off it, or pretend that you wrote it.  | */
/* +------------------------------------------------------------------+ */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include "rle.h"

#ifndef lint
static char rcsid[] = "$Id: giftorle.c,v 3.0.1.4 1992/04/30 13:56:24 spencer Exp spencer $";
#endif
/*
giftorle()		Tag the file.
*/

#define	MAXCOLORMAPSIZE		256

#define	TRUE	1
#define	FALSE	0

#define CM_RED		0
#define CM_GREEN	1
#define CM_BLUE		2

#define	MAX_LWZ_BITS		12

#define	ReadOK(file,buffer,len)	(fread(buffer,len,1,file)!=0)
#define	EasyFail(str,status)	{fprintf(stderr,str);return(status);}
#define	HardFail(str,status)	{fprintf(stderr,str);exit  (status);}

#define LM_to_uint(a,b)			(((b)<<8)|(a))

int ReadGIF();
int ReadColorMap(), IgnoreExtention(), GetCode(), LWZReadByte();
int ReadRaster();

static rle_map out_map[3*(1<<8)];

CONST_DECL char *MY_NAME = "giftorle";

FILE *outfile;

struct {
	unsigned int		Width;
	unsigned int		Height;
	unsigned char		ColorMap[3][MAXCOLORMAPSIZE];
	unsigned int		BitPixel;
	unsigned int		ColorResolution;
	unsigned int		Background;
} Screen;

static int output_colormap = FALSE;

void
main(argc,argv)
int	argc;
char	**argv;
{
    int	oflag = 0, nfname = 0;
    char *outfname = NULL, **infname = NULL;

    MY_NAME = cmd_name( argv );

    if ( scanargs( argc, argv, "% c%- o%-outfile.rle!s infile.gif%*s",
		   &output_colormap, &oflag, &outfname,
		   &nfname, &infname ) == 0 )
		   exit( 1 );

    outfile = rle_open_f( MY_NAME, outfname, "w" );
    /* Questionable practice.  Modifies default values for headers. */
    rle_addhist( argv, (rle_hdr *)0, &rle_dflt_hdr );

    /* Always read at least one (standard input). */
    if ( nfname == 0 )
	(void)ReadGIF( NULL );

    while ( nfname-- > 0 )
	(void)ReadGIF( *infname++ );
    exit( 0 );
}

int
ReadGIF(filename)
char	*filename;
{
    unsigned char	buf[16];
    unsigned char	c;
    unsigned char	LocalColorMap[3][MAXCOLORMAPSIZE];
    FILE			*fd;
    int				use_global_colormap;
    int				bit_pixel;
    int				count=0;

    fd = rle_open_f( MY_NAME, filename, "r" );

    if (! ReadOK(fd,buf,6))
	EasyFail("error reading magic number\n",TRUE);
    if (strncmp((char *)buf,"GIF87a",6)!=0)
	EasyFail("bad magic number (version mismatch?)\n",TRUE);
    if (! ReadOK(fd,buf,7))
	EasyFail("error reading screen descriptor\n",TRUE);
    Screen.Width           = LM_to_uint(buf[0],buf[1]);
    Screen.Height          = LM_to_uint(buf[2],buf[3]);
    Screen.BitPixel        = 2<<(buf[4]&0x07);
    Screen.ColorResolution = (((buf[4]&0x70)>>3)+1);
    Screen.Background      = buf[5];
    if ((buf[4]&0x80)==0x80) {
	if (ReadColorMap(fd,Screen.BitPixel,Screen.ColorMap))
	    return (TRUE);
    }

    while (1) {
	if (! ReadOK(fd,&c,1))
	    EasyFail("No image data -- EOF\n",TRUE);
	if (c == ';')
	    return FALSE;
	if (c == '!') {
	    if (! ReadOK(fd,&c,1))
		EasyFail("No extention function code -- EOF\n",TRUE);
	    if (IgnoreExtention(fd))
		return(TRUE);
	    continue;
	}
	if (c != ',') {
	    fprintf(stderr,"Bogus character ignoring '%c'\n",c);
	    continue;
	}
/*
	if (count == 1)
	    HardFail("This file contains more than one image! FAILING\n",1);
*/
	count++;

	if (! ReadOK(fd,buf,9))
	    EasyFail("Couldn't read left/top/width/height\n",TRUE);
	if ((buf[8]&0x80)==0x80)
	    use_global_colormap = FALSE ;
	else
	    use_global_colormap = TRUE ;

	bit_pixel = 1<<((buf[8]&0x07)+1);

	if (! use_global_colormap) {
	    if (ReadColorMap(fd,bit_pixel,LocalColorMap))
		return TRUE;
	}

	if (ReadRaster((buf[8]&0x40)==0x40, fd,LM_to_uint(buf[4],buf[5]),
		       LM_to_uint(buf[6],buf[7]),
		       use_global_colormap?Screen.ColorMap:LocalColorMap))
		return TRUE;
    }
}

int
ReadColorMap(fd,number,buffer)
FILE			*fd;
int				number;
unsigned char	buffer[3][MAXCOLORMAPSIZE];
{
    int				i;
    unsigned char	rgb[3];

    for (i=0;i<number;i++) {
	if (! ReadOK(fd,rgb,sizeof(rgb)))
	    EasyFail("Bogus colormap\n",TRUE);
	buffer[CM_RED][i] = rgb[0] ;
	buffer[CM_GREEN][i] = rgb[1] ;
	buffer[CM_BLUE][i] = rgb[2] ;
    }
    return FALSE;
}

int
IgnoreExtention(fd)
FILE	*fd;
{
    static char		buf[256];
    unsigned char	c;

    while (1) {
	if (! ReadOK(fd,&c,1))
	    EasyFail("EOF in extention\n",TRUE);
	if (c == 0)
	    return FALSE;
	if (!ReadOK(fd,buf,(int) c))
	    EasyFail("EOF in extention\n",TRUE);
    }
}

int
GetCode(fd, code_size, flag)
FILE	*fd;
int		code_size;
int		flag;
{
    static unsigned char	buf[280];
    static int				curbit,lastbit,done,last_byte;
    int						i, j, ret;
    unsigned char			count;

    if (flag) {
	curbit = 0;
	lastbit = 0;
	done = FALSE;
	return 0;
    }

    if ( (curbit+code_size) >= lastbit) {
	if (done) {
	    if (curbit>=lastbit)
		EasyFail("Ran off the end of my bits\n",-1);
	}
	buf[0] = buf[last_byte-2];
	buf[1] = buf[last_byte-1];
	if (! ReadOK(fd,&count,1)) {
	    EasyFail("Error in getting buffer size\n",-1);
	}
	if (count == 0) {
	    done = TRUE;
	} else if (! ReadOK(fd,&buf[2],count))
	    EasyFail("Error in getting buffer\n",-1);
	last_byte = 2 + count;
	curbit = (curbit - lastbit) + 16;
	lastbit = (2+count)*8 ;
    }

    ret = 0;
    for( i = curbit, j = 0; j < code_size; i++, j++ )
	ret |= ((buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;

    curbit += code_size;

    return ret;
}

int
LWZReadByte(fd,flag,input_code_size)
FILE	*fd;
int	flag;
int	input_code_size;
{
    static int	fresh=FALSE;
    int		code,incode;
    static int	code_size,set_code_size;
    static int	max_code,max_code_size;
    static int	firstcode,oldcode;
    static int	clear_code,end_code;
    static int	table[2][(1<< MAX_LWZ_BITS)];
    static int	stack[(1<<(MAX_LWZ_BITS))*2],*sp;
    register int i;

    if (flag) {
	set_code_size = input_code_size;
	code_size = set_code_size+1;
	clear_code = 1 << set_code_size ;
	end_code = clear_code + 1;
	max_code_size = 2*clear_code;
	max_code = clear_code+2;

	GetCode(fd,NULL,TRUE);

	fresh=TRUE;

	for (i=0;i<clear_code;i++) {
	    table[0][i] = 0;
	    table[1][i] = i;
	}
	for (;i<(1<<MAX_LWZ_BITS);i++)
	    table[0][i] = table[1][0] = 0;

	sp=stack;

	return 0;
    } else if (fresh) {
	fresh = FALSE;
	do {
	    firstcode=oldcode=
		GetCode(fd, code_size, FALSE);
	} while (firstcode == clear_code);
	return firstcode;
    }

    if (sp > stack)
	return *--sp;

    while ((code=GetCode(fd,code_size,FALSE))>=0) {
	if (code == clear_code) {
	    for (i=0;i<clear_code;i++) {
		table[0][i] = 0;
		table[1][i] = i;
	    }
	    for (;i<(1<<MAX_LWZ_BITS);i++)
		table[0][i] = table[1][i] = 0;
	    code_size = set_code_size+1;
	    max_code_size = 2*clear_code;
	    max_code = clear_code+2;
	    sp=stack;
	    firstcode=oldcode=
		GetCode(fd,code_size,FALSE);
	    return firstcode;
	} else if (code == end_code) {
	    unsigned char		count;
	    unsigned char		junk;

	    while (ReadOK(fd,&count,1) && (count!=0))
		while (count-->0 && ReadOK(fd,&junk,1));
	    if (count!=0)
		EasyFail("missing EOD in data stream (common occurance)\n",-3);
	    return -2;
	}

	incode = code;

	if (code >= max_code) {
	    *sp++ = firstcode;
	    code = oldcode;
	}

	while (code >= clear_code) {
	    *sp++ = table[1][code];
	    if (code == table[0][code])
		EasyFail("Circular table entry BIG ERROR\n",-1);
	    code = table[0][code];
	}

	*sp++ = firstcode = table[1][code];

	if ((code=max_code)<(1<<MAX_LWZ_BITS)) {
	    table[0][code] = oldcode;
	    table[1][code] = firstcode;
	    max_code++;
	    if ((max_code >= max_code_size) &&
		(max_code_size < (1<<MAX_LWZ_BITS))) {
		max_code_size *= 2;
		code_size++;
	    }
	}

	oldcode = incode;

	if (sp > stack)
	    return *--sp;
    }
    return code;
}

int
ReadRaster(interlace,fd,len,height,cmap)
int interlace;
FILE	*fd;
int	len,height;
char	cmap[3][MAXCOLORMAPSIZE];
{
    unsigned char	c;
    int			v;
    int			xpos=0;
    rle_pixel	      **scanline[3];
    rle_pixel	       *ptr[3] ;
    rle_hdr		hdr;
    int			i,j;
    int			ypos=0, pass=interlace ? 0 : 4;


    for (j=0;j<(output_colormap?1:3);j++) {
	if ((scanline[j]=(rle_pixel **)
	     malloc(height*sizeof(rle_pixel *)))==NULL)
	    EasyFail("Unable to malloc space for pixels #1\n",1);
	for (i=0;i<height;i++) {
	    if ((scanline[j][i]=(rle_pixel *)
		 malloc(len*sizeof(rle_pixel)))==NULL) {
		for (;j>=0;j--)
		    for (;i>=0;i--)
			if (scanline[j][i]!=NULL) free(scanline[i]);
		EasyFail("Unable to malloc space for pixels #2\n",1);
	    }
	}
    }

    hdr = *rle_hdr_init( (rle_hdr *)NULL );
    rle_names( &hdr, "giftorle", NULL, 0 );

    if (output_colormap) {
	hdr.ncolors =  1;
	hdr.ncmap = 3;
	hdr.cmaplen = 8;
	hdr.cmap = out_map;
	for (i=0;i<(1<<8);i++) {
	    out_map[i+(0<<8)] = cmap[CM_RED][i] << 8;
	    out_map[i+(1<<8)] = cmap[CM_GREEN][i] << 8;
	    out_map[i+(2<<8)] = cmap[CM_BLUE][i] << 8;
	}
    } else {
	RLE_SET_BIT(hdr, RLE_RED);
	RLE_SET_BIT(hdr, RLE_GREEN);
	RLE_SET_BIT(hdr, RLE_BLUE);
	hdr.ncolors =  3;
    }
    hdr.rle_file = outfile ;
    hdr.xmax = len - 1;
    hdr.ymax = height - 1;

    rle_put_setup(&hdr);

    if (! ReadOK(fd,&c,1))
	EasyFail("Bogus image data -- EOF\n",TRUE);
    if (LWZReadByte(fd,TRUE,c)<0)
	return TRUE;

    while ((v=LWZReadByte(fd,FALSE,c))>=0) {
	if (output_colormap) {
	    scanline[RLE_RED][ypos][xpos]   = v ;
	} else {
	    scanline[RLE_RED][ypos][xpos]   = cmap[CM_RED][v] ;
	    scanline[RLE_GREEN][ypos][xpos] = cmap[CM_GREEN][v] ;
	    scanline[RLE_BLUE][ypos][xpos]  = cmap[CM_BLUE][v] ;
	}
	xpos++;
	if (xpos==len) {
	    xpos = 0;
	    switch (pass) {
	    case 0:
	    case 1:
		ypos += 8; break;
	    case 2:
		ypos += 4; break;
	    case 3:
		ypos += 2; break;
	    case 4:		/* Not interlaced */
		ypos++; break;
	    default:
		fprintf( stderr, "%s: Data past end of image.\n", hdr.cmd );
		break;
	    }
	    if (ypos >= height) {
		pass++;
		switch (pass) {
		case 1:
		    ypos = 4; break;
		case 2:
		    ypos = 2; break;
		case 3:
		    ypos = 1; break;
		default:		/* Shouldn't happen. */
		    ypos = 0; break;
		}
	    }
	}
    }

    for ( i = height - 1; i >= 0; i-- ) {
	ptr[0] = scanline[RLE_RED][i] ;
	if (! output_colormap) {
	    ptr[1] = scanline[RLE_GREEN][i] ;
	    ptr[2] = scanline[RLE_BLUE][i] ;
	}
	rle_putrow(ptr,len,&hdr);
    }

    rle_puteof(&hdr);

    for (i=0;i<height;i++) {
	if (scanline[0][i]!=NULL) free(scanline[0][i]);
	if (output_colormap) continue;

	if (scanline[1][i]!=NULL) free(scanline[1][i]);
	if (scanline[2][i]!=NULL) free(scanline[2][i]);
    }
    free(scanline[0]);
    if (! output_colormap) {
	free(scanline[1]);
	free(scanline[2]);
    }

    if (v == -2)
	return FALSE;
    return TRUE;
}

