text: /*                        G I F 2 F B . C
   * BRL-CAD
   *
   * Copyright (c) 2004-2021 United States Government as represented by
   * the U.S. Army Research Laboratory.
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public License
   * version 2.1 as published by the Free Software Foundation.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this file; see the file named COPYING for more
   * information.
   *
   */
  /** @file gif2fb.c
   *
   * Gif to Frame Buffer
   *
   * Gif-fb takes a GIF file and writes it to a frame buffer.
   * GIF files contain one or more 1, 2, 4, or 8 bit deep pictures
   * with one or more color maps. Gif-fb will select the correct
   * color map and display the first picture on the frame-buffer.
   *
   * Entry:
   *	FB_FILE		name of the frame buffer to use.
   *	-i		invert color map.
   *	name		name of the file to display.
   *
   * Exit:
   *	the named gif file has been displayed
   *
   */
  
  #include "common.h"
  
  #include <stdlib.h>
  #include <string.h>
  
  #include "bio.h"
  
  #include "bu/app.h"
  #include "bu/getopt.h"
  #include "bu/exit.h"
  #include "vmath.h"
  #include "dm.h"
  
  
  #define LSB 0 /* Least Significant Byte */
  #define MSB 1 /* Most Significant Byte */
  
  struct GIF_head {
      char GH_Magic[6];
      unsigned char GH_ScreenWidth[2];	/* MSB, LSB */
      unsigned char GH_ScreenHeight[2];	/* MSB, LSB */
      unsigned char GH_Flags;
      unsigned char GH_Background;
      unsigned char GH_EOB;
  };
  
  
  struct GIF_Image {
      char IH_Magic;
      unsigned char IH_Left[2];		/* MSB, LSB */
      unsigned char IH_Top[2];		/* MSB, LSB */
      unsigned char IH_Width[2];		/* MSB, LSB */
      unsigned char IH_Height[2];		/* MSB, LSB */
      unsigned char IH_Flags;
  };
  
  
  struct acolor {
      unsigned char red;
      unsigned char green;
      unsigned char blue;
  };
  
  
  #define WORD(x)	(((int)x[MSB]<<8)+(int)x[LSB])
  
  int ScreenWidth, ScreenHeight;
  int GlobalMap;
  int CR;
  int GlobalPixels;
  int Background;
  
  int MinBits;
  int Bits;
  int Fresh=1;
  
  struct acolor GlobalColors[256];
  struct acolor LocalColors[256];
  
  struct GIF_head Header;
  struct GIF_Image Im;
  
  char *framebuffer=NULL;
  
  void
  usage(char **argv)
  {
      fprintf(stderr,"Usage: %s [-H] [-v] [-F frame_buffer] [gif_file]\n", argv[0]);
      fprintf(stderr,"       (stdin used with '<' construct if gif_file not supplied)\n");
  }
  
  int getByte(FILE *inp);
  
  int
  main(int argc, char **argv)
  {
      int i, idx;
      size_t n;
      int maxcolors;
      int code;
      int verbose=0;
      int headers=0;
      int interlaced;
      char *file_name;
  
      unsigned char line[3*2048];
      unsigned char *lp;
  
      int lineNumber, lineInc, lineIdx;
      static int lace[4] = {8, 8, 4, 2};
      static int offs[4] = {0, 4, 2, 1};
  
      struct fb *fbp;
      FILE *fp;
  
      bu_setprogname(argv[0]);
  
      while ((code = bu_getopt(argc, argv, "HvFh?")) != -1) {
  	switch (code) {
  	    case 'H':
  		headers=1;
  		break;
  	    case 'v':
  		verbose=1;
  		break;
  	    case 'F':
  		framebuffer = bu_optarg;
  		break;
  	    default:	/* '?' */
  		usage(argv);
  		return 1;
  	}
      }
  
      if (bu_optind >= argc) {
  	if (isatty(fileno(stdin))) {
  	    usage(argv);
  	    return 1;
  	}
  	fp = stdin;
  	setmode(fileno(stdin), O_BINARY);
      } else {
  	file_name = argv[bu_optind];
  	if ((fp = fopen(file_name, "rb")) == NULL) {
  	    fprintf(stderr,
  		    "%s: cannot open \"%s\" for reading\n", argv[0],
  		    file_name);
  	    usage(argv);
  	    return 1;
  	}
      }
  /*
   * read in the Header and then check for consistency.
   */
      n = fread(&Header, 1, 13, fp);
  
      if (n != 13) {
  	fprintf(stderr, "%s: only %ld bytes in header.\n", argv[0], (long)n);
  	return 1;
      }
  
      ScreenWidth = WORD(Header.GH_ScreenWidth);
      ScreenHeight= WORD(Header.GH_ScreenHeight);
      GlobalMap   = (Header.GH_Flags>>7);
      CR	    = (Header.GH_Flags>>4) & 0x07;
      GlobalPixels= (Header.GH_Flags&0x07) + 1;
      if (headers) {
  	fprintf(stderr, "-w%d -n%d\n", ScreenWidth, ScreenHeight);
  	return 0;
      }
  /*
   * In verbose mode, output a message before checking to allow the
   * "smarter" user look over the header even if the header is barfO.
   */
      if (verbose) {
  	char magic[sizeof(Header.GH_Magic)+1];
  	memcpy(magic, Header.GH_Magic, sizeof(Header.GH_Magic));
  	magic[sizeof(magic)-1] = '\0';
  
  	fprintf(stderr, "Magic=%.6s, -w%d -n%d, M=%d, cr=%d, pixel=%d, bg=%d\n",
  		magic, ScreenWidth, ScreenHeight, GlobalMap,
  		CR, GlobalPixels, Header.GH_Background);
      }
  
      if (Header.GH_EOB) {
  	fprintf(stderr, "%s: missing EOB in header.\n", argv[0]);
  	return 1;
      }
      maxcolors = 1 << GlobalPixels;
  
  /*
   * Read in the Global color map.
   */
      for (i=0;i<maxcolors;i++) {
  	n = fread(&GlobalColors[i], 1, 3, fp);
  	if (n != 3) {
  	    fprintf(stdout, "%s: only read %d global colors.\n",
  		    argv[0], i);
  	    return 1;
  	}
      }
  /*
   * Read in the image header.
   */
      n = fread(&Im, 1, sizeof(Im), fp);
  
      if (n != sizeof(Im)) {
  	fprintf(stderr, "%s: only %ld bytes in image header.\n",
  		argv[0], (long)n);
  	return 1;
      }
      if (verbose) {
  	fprintf(stderr, "Magic=%c, left=%d, top=%d, Width=%d, Height=%d\n",
  		Im.IH_Magic, WORD(Im.IH_Left), WORD(Im.IH_Top), WORD(Im.IH_Width),
  		WORD(Im.IH_Height));
  	fprintf(stderr, "Map=%d, Interlaced=%d, pixel=%d\n",
  		Im.IH_Flags>>7, (Im.IH_Flags>>6)&0x01, Im.IH_Flags&0x03);
      }
  
      if (WORD(Im.IH_Height) < 0 || WORD(Im.IH_Height) > 65535)
  	bu_exit(1, "Bad height info in GIF header\n");
  
      interlaced = (Im.IH_Flags>>6)&0x01;
  
  /*
   * Read the image color map if any.
   */
      if (Im.IH_Flags>>7) {
  	GlobalPixels= (Im.IH_Flags&0x07) + 1;
  	for (i=0;i<maxcolors;i++) {
  	    n = fread(&GlobalColors[i], 1, 3, fp);
  	    if (n != 3) {
  		fprintf(stdout,
  			"%s: only read %d global colors.\n",
  			argv[0], i);
  		return 1;
  	    }
  	}
      }
  
      if (WORD(Im.IH_Width) > 2048) {
  	fprintf(stderr, "%s: Input line greater than internal buffer!\n",
  		argv[0]);
  	return 1;
      }
  
      MinBits = getc(fp) + 1;
  
      if (verbose) {
  	fprintf(stderr, "MinBits=%d\n", MinBits);
      }
  
      if (interlaced) {
  	lineIdx = 0;
  
  	lineNumber = offs[lineIdx];
  	lineInc= lace[lineIdx];
      } else {
  	lineIdx = (sizeof(lace)/sizeof(lace[0]))-1;
  	lineNumber = 0;
  	lineInc = 1;
      }
  /*
   * Open the frame buffer.
   */
      if ((fbp = fb_open(framebuffer, WORD(Im.IH_Width), WORD(Im.IH_Height))) != NULL) {
  	int ih_height = WORD(Im.IH_Height);
  	int ih_width = WORD(Im.IH_Width);
  
  	if (ih_height < 0 || ih_height > 0xffff || ih_width < 0 || ih_width > 0xffff)
  	    bu_exit(1, "Invalid height in GIF Header\n");
  
  	/*
  	 * The speed of this loop can be greatly increased by moving all of
  	 * the WORD macro calls out of the loop.
  	 */
  	for (i=0; i<ih_height;i++) {
  	    int k;
  	    lp = line;
  	    for (k=0;k<ih_width;k++) {
  		idx = getByte(fp);
  		if (idx < 0) {
  		    fb_close(fbp);
  		    bu_exit(1, "Error: Unexpectedly reached end of input. Output may be incomplete.\n");
  		}
  		*lp++ = GlobalColors[idx].red;
  		*lp++ = GlobalColors[idx].green;
  		*lp++ = GlobalColors[idx].blue;
  	    }
  	    fb_write(fbp, 0, ih_height-lineNumber, line, ih_width);
  	    fb_flush(fbp);
  	    lineNumber += lineInc;
  	    if (lineNumber >= ih_height) {
  		++lineIdx;
  		V_MIN(lineIdx, (int)(sizeof(lace)/sizeof(lace[0]))-1);
  		lineInc = lace[lineIdx];
  		lineNumber = offs[lineIdx];
  	    }
  	}
  	fb_close(fbp);
      }
      return 0;
  }
  /* getcode - Get a LWZ "code"
   *
   * getcode returns an LWZ code.  The code size is always less than
   * 12 bits but could be as small as 2 bits.  This implies that reading
   * one code may not "read" anything from a file.
   *
   * Entry:
   *	inp	a FILE pointer to the input stream.
   *
   * Exit:
   *	returns one code.
   *
   * Uses:
   *	Bits		global variable containing the size of a code.
   *	bitsleft	number of bits left in this byte.
   *	count		number of BYTES left in this Block.
   *	lastbits	the left over bits form the last getcode call.
   *
   * Calls:
   * 	none.
   *
   * Method:
   *	while not enough bits for a code do
   *		read another byte (8 bits)
   *	endwhile
   *	extract the code.
   *	clean up the state variables.
   *
   * N.B.!!!	This code depends on the '>>' operator zero filling
   *		from the left.
   */
  int
  getcode(FILE *inp)
  {
      static unsigned int lastbits = 0;
      static int bitsleft = 0;
      static int count=0;
      int code;
  
  
      while (bitsleft < Bits) {
  /*
   * get a new block counter if needed.
   */
  	if (--count <= 0) {
  	    count = (unsigned char) getc(inp);
  	    if (count == 0) return -1;
  	}
  /*
   * stuff another byte into last bits.
   */
  	lastbits |= (unsigned char) getc(inp) << bitsleft;
  	bitsleft += 8;
      }
      code = (1<<Bits)-1;	/* make mask */
      code &= lastbits;	/* extract the code */
  /*
   * clean up the state variables.
   */
      bitsleft -= Bits;
      lastbits = lastbits >> Bits;
      return code;
  }
  
  
  /* getByte	get a byte from the input stream decompressing as we go.
   *
   * getByte uses the somewhat standard LWZ decompress algorithm.  Most
   * of this subroutine is based on "compress.c".  I've added some and
   * deleted others but I do NOT claim that this is original code.
   *
   * Entry:
   *	inp	the input stream point
   *
   * Exit:
   *	returns a "byte".
   *
   * Calls:
   *	getcode		to get the next code from the input stream.
   *
   * Uses:
   *	Bits	current size of the code.
   *	minBits	the min. size of the code.
   *
   * Method:
   *	Being unable to read the papers that everybody else points to,
   *	I had to decipher the compress code.  This is how I thing this
   *	compression algorithm works.
   *	if this is the first time the routine has been called then
   *		initialize the code sizes
   *		set the "tree" so that all "characters" are at the root.
   *	endif
   *	get a new code
   *	if the new code is greater than the current max code then
   *		add last(?) code to the tree.
   *		follow the tree from the leaf to the root outputting
   *		    a "byte" for each node of the tree.
   *	endif
   */
  int getByte(FILE *inp)
  {
      int code, incode;
      static int firstcode, oldcode;
      static int firstTime = 1;
      static int clear_code, end_code;
      static int max_code, next_ent;
  #define PREFIX 0
  #define SUFIX 1
      static int table[2][1<<12];
      static int stack[1<<13], *sp;
  
      int i;
  
      if (firstTime) {
  	firstTime = 0;
  	Bits = MinBits;
  	clear_code = 1 << (Bits-1);
  	end_code = clear_code+1;
  	max_code=clear_code<<1;
  	next_ent = clear_code+2;
  
  	for (i=0;i<clear_code;i++) {
  	    table[PREFIX][i] = 0;
  	    table[SUFIX][i]  = i;
  	}
  
  	sp = stack;
  
  	do {
  	    firstcode=oldcode=getcode(inp);
  	} while (firstcode == clear_code);
  	return firstcode;
      }
  
      if (sp > stack) return *--sp;
  
      while ((code=getcode(inp)) >= 0) {
  	if (code == clear_code) {
  	    for (i=0;i<clear_code;i++) {
  		table[PREFIX][i] = 0;
  		table[SUFIX][i]  = i;
  	    }
  	    Bits = MinBits;
  	    max_code = clear_code<<1;
  	    next_ent = clear_code+2;
  	    sp=stack;
  	    firstcode=oldcode=getcode(inp);
  	    return firstcode;
  	} else if (code == end_code) {
  	    return -1;
  	}
  
  	incode = code;
  
  	if (code >= next_ent) {
  	    *sp++ =firstcode;
  	    code = oldcode;
  	}
  
  	while (code >= clear_code) {
  	    *sp++ = table[SUFIX][code];
  	    code = table[PREFIX][code];
  	}
  
  	*sp++ = firstcode = table[SUFIX][code];
  
  	if ((code=next_ent) < (1<<12)) {
  	    table[PREFIX][code] = oldcode;
  	    table[SUFIX][code]  = firstcode;
  	    next_ent++;
  	    if ((next_ent >= max_code) &&
  		(max_code < (1<<12))) {
  		max_code *= 2;
  		Bits++;
  	    }
  	}
  
  	oldcode = incode;
  
  	if (sp >stack) return *--sp;
      }
      return code;
  }
  
  /*
   * Local Variables:
   * mode: C
   * tab-width: 8
   * indent-tabs-mode: t
   * c-file-style: "stroustrup"
   * End:
   * ex: shiftwidth=4 tabstop=8
   */
  