
/*
 * $XConsortium$
 *
 * Copyright 1989 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:  Davor Matic, MIT X Consortium
 *           Thomas Wu, MIT Fishbowl
 */

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Intrinsic.h>
#include "gif.h"

#define MAXCMD 128

ReadGifFile(dpy, scr, file_name, image, colors, ncolors)
     Display *dpy;
     int scr;
     String file_name;
     XImage **image;
     XColor **colors;
     int *ncolors;
{
  Bool pipeline = False;
  FILE *file;
  char shell_cmd[MAXCMD];
  int status;

  if (strlen(file_name) > 1 &&
      !strcmp(file_name + strlen(file_name) - 2, ".Z")) {
    (void) sprintf(shell_cmd, "zcat %s", file_name);
    if((file = popen(shell_cmd, "r")) == NULL)
      return RG_ZCAT_ERROR;
    pipeline = True;
  }
  else if (strcmp(file_name, "-")) {
    /*
     * Open the output file.
     */
    file = fopen(file_name, "r");
    if (!file)
      return RG_FILE_NOT_FOUND;
  }
  else {
    file = stdin;
  }
  status = ReadGifDescriptor(dpy, scr, file, image, colors, ncolors);
  if(pipeline)
    (void) pclose(file);
  else if(file != stdin)
    (void) fclose(file);
  return status;
}

ReadGifDescriptor(dpy, scr, file, image, colors, ncolors)
     Display *dpy;
     int scr;
     FILE *file;
     XImage **image;
     XColor **colors;
     int *ncolors;
{
  Bool has_colormap, interlaced;
  Byte *image_buffer;
  ByteStream stream;
  GifHeader header;
  ImageSpec image_spec;
  RawColor raw_color;
  RawGifHeader raw_header;
  RawImageSpec raw_image_spec;
  int status = RG_SUCCESS;
  int bits_per_pixel, colormap_size, code_size, initial_code_size;
  int code;
  int eof_code, clear_code, free_code, max_code, cur_code, old_code, in_code;
  int fin_char, first_free;
  int out_count = 0;
  int bit_mask, read_mask;
  int bit_offset = 0;
  int i, x = 0, y = 0;
  int pass = 0;
  int prefix[4096], suffix[4096], out_code[1025];

  if(fread((char *) &raw_header, sizeof(RawGifHeader), 1, file) != 1)
    return RG_BAD_HEADER;

  if(strncmp(raw_header.id, GIF_ID, strlen(GIF_ID)))
    return RG_NOT_GIF_FILE;

  if(raw_header.null_byte)
    return RG_CORRUPT;

  ConvertHeader(&raw_header, &header);

  has_colormap = ((header.colormap_info & COLORMAPMASK) ? True : False);

  bits_per_pixel = (header.colormap_info & 7) + 1;
  *ncolors = colormap_size = 1 << bits_per_pixel;
  bit_mask = colormap_size - 1;

  if(has_colormap) {
    *colors = (XColor *) XtMalloc((unsigned) *ncolors * sizeof(XColor));
    for(i = 0; i < *ncolors; ++i) {
      if(fread((char *) &raw_color, sizeof(RawColor), 1, file) != 1)
	return RG_BAD_COLORTABLE;
      (*colors)[i].red = raw_color.Red << 8;
      (*colors)[i].green = raw_color.Green << 8;
      (*colors)[i].blue = raw_color.Blue << 8;
      (*colors)[i].pixel = i;
    }
  }
  else {
    if(*ncolors == 0)
      *ncolors = 256;
    *colors = (XColor *)XtMalloc((unsigned) *ncolors * sizeof(XColor));
    for(i = 0; i < *ncolors; ++i) {
      (*colors)[i].red = (*colors)[i].green = (*colors)[i].blue = i << 8;
      (*colors)[i].pixel = i;
    }
  }

  if(fread((char *) &raw_image_spec, sizeof(RawImageSpec), 1, file) != 1)
    return RG_BAD_RAW_SPECS;

  if(raw_image_spec.image_separator != IMAGESEP)
    return RG_NO_SEPARATOR;

  ConvertSpec(&raw_image_spec, &image_spec);

  interlaced = ((image_spec.interlace & INTERLACEMASK) ? True : False);

  code_size = image_spec.data_size;
  clear_code = (1 << code_size);
  eof_code = clear_code + 1;
  free_code = first_free = clear_code + 2;

  ++code_size;
  initial_code_size = code_size;
  max_code = (1 << code_size);
  read_mask = max_code - 1;

  InitializeByteStream(file, &stream);

  image_buffer = (Byte *) XtMalloc((unsigned)image_spec.width *
				  image_spec.height);

/* This has to be replaced... */
  *image = XCreateImage(dpy, DefaultVisual(dpy, scr), 8, ZPixmap, 0,
			(char *) image_buffer, image_spec.width,
			image_spec.height, 8, image_spec.width);
/* */

  if(*image == NULL)
    return RG_IMAGE_ERROR;

  code = ReadCode(&stream, &bit_offset, code_size, read_mask);
  while(code != eof_code) {
    if(code == clear_code) {
      code_size = initial_code_size;
      max_code = (1 << code_size);
      read_mask = max_code - 1;
      free_code = first_free;
      cur_code = old_code = code = ReadCode(&stream, &bit_offset, code_size,
					    read_mask);
      if(Corrupt(stream)) {
	status = RG_PART_CORRUPT;
	break;
      }
      fin_char = cur_code & bit_mask;
      add_to_pixel((Byte) fin_char, &pass, &x, &y, *image, interlaced);
    }
    else {
      cur_code = in_code = code;
      if(cur_code >= free_code) {
	cur_code = old_code;
	out_code[out_count++] = fin_char;
      }
      while(cur_code > bit_mask) {
	if(out_count > 1024) {
	  Corrupt(stream) = True;
	  break;
	}
	out_code[out_count++] = suffix[cur_code];
	cur_code = prefix[cur_code];
      }
      if(Corrupt(stream)) {
	status = RG_PART_CORRUPT;
	break;
      }
      fin_char = cur_code & bit_mask;
      out_code[out_count++] = fin_char;
      for(i = out_count - 1; i >= 0; --i)
	add_to_pixel((Byte) out_code[i], &pass, &x, &y, *image, interlaced);
      out_count = 0;
      prefix[free_code] = old_code;
      suffix[free_code] = fin_char;
      old_code = in_code;
      ++free_code;
      if(free_code >= max_code) {
	if(code_size < 12) {
	  ++code_size;
	  max_code <<= 1;
	  read_mask = (1 << code_size) - 1;
	}
      }
    }
    code = ReadCode(&stream, &bit_offset, code_size, read_mask);
  }

  return status;
}

ConvertHeader(raw, cooked)
     RawGifHeader *raw;
     GifHeader *cooked;
{
  String strncpy();

  (void) strncpy(raw->id, cooked->id, 6);
  cooked->width = ConvertToInt(raw->width);
  cooked->height = ConvertToInt(raw->height);
  cooked->colormap_info = raw->colormap_info;
  cooked->background_color = raw->background_color;
}

ConvertSpec(raw, cooked)
     RawImageSpec *raw;
     ImageSpec *cooked;
{
  cooked->left_offset = ConvertToInt(raw->left_offset);
  cooked->top_offset = ConvertToInt(raw->top_offset);
  cooked->width = ConvertToInt(raw->width);
  cooked->height = ConvertToInt(raw->height);
  cooked->interlace = raw->interlace;
  cooked->data_size = raw->data_size;
}

InitializeByteStream(fp, f)
     FILE *fp;
     ByteStream *f;
{
  f->file_ptr = fp;
  f->corrupt = False;
  f->current_byte_offset = -2;
  f->bytes_left = getc(fp) - 1;
  NextNextByte(f) = getc(fp);
}

ReadCode(f, bit_offset, size, mask)
     ByteStream *f;
     int *bit_offset, size, mask;
{
  int raw_code, byte_offset;

  byte_offset = *bit_offset / 8;
  FixFilePosition(f, byte_offset);
  raw_code = CurrentByte(f) + (NextByte(f) << 8);
  if(size >= 8)
    raw_code += NextNextByte(f) << 16;
  raw_code >>= (*bit_offset % 8);
  *bit_offset += size;
  return (raw_code & mask);
}

add_to_pixel(index, pass, xc, yc, image, interlaced)
     Byte index;
     int *pass;
     int *xc, *yc;
     XImage *image;
     Bool interlaced;
{
  if(*yc < image->height)
    *(image->data + *yc * image->bytes_per_line + *xc) = index;

  if(++*xc == image->width) {
    *xc = 0;
    if(!interlaced)
      ++*yc;
    else {
      switch(*pass) {
      case 0:
	*yc += 8;
	if(*yc >= image->height) {
	  ++*pass;
	  *yc = 4;
	}
	break;
      case 1:
	*yc += 8;
	if(*yc >= image->height) {
	  ++*pass;
	  *yc = 2;
	}
	break;
      case 2:
	*yc += 4;
	if(*yc >= image->height) {
	  ++*pass;
	  *yc = 1;
	}
	break;
      case 3:
	*yc += 2;
	break;
      }
    }
  }
}
