[comp.sources.x] v06i002: Xloadimage, Patch3, Part 02/02

argv%turnpike@Sun.COM (Dan Heller) (02/28/90)

Submitted-by: Jim Frost <jimf@saber.com>
Posting-number: Volume 6, Issue 2
Archive-name: xldimage/patch3.2-2
Patch-To: xldimage: Volume 5, Issue 27-28,30

This is part 2 of a 2 part patch to xloadimage to bring it from
patchlevel 02 to patchlevel 03.

There are many bug fixes and improvements in patchlevel 03, including
a new loader for GIF images.  This version should work on most X
servers, including those with depths of odd values such as 2 or 4 (eg
386/ix servers).  See the README file for more information.

Part 1 contains a patch file which should be applied to a virgin
patchlevel 02 source directory.  Save it in the file "patch.03" and
apply via "patch < patch.03".

Part 2, which follows, contains a shar file with the new GIF loader
and associated header files.  Save it in the file "patch.03.shar" and
unpack it via "sh patch.03.shar".

If you do not have xloadimage or do not have it updated to patchlevel
02, you can get the original source (distributed at patchlevel 01) and
the 02 patch from the comp.sources.x archives, or you can get the full
distribution from expo.lcs.mit.edu in /contrib/xloadimage.1.03.tar.Z
via anonymous ftp.  The original source and 02 patch are also on expo
but have been moved to /oldcontrib.

Feel free to email any questions or comments.

Enjoy,

jim frost
saber software
jimf@saber.com

-- patch.03.shar: cut here --
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# gif.c gif.h kljcpyrght.h

echo x - gif.c
cat > "gif.c" << '//E*O*F gif.c//'
/* gif.c:
 *
 * adapted from code by kirk johnson (tuna@athena.mit.edu).  most of this
 * code is unchanged. -- jim frost 12.31.89
 *
 * gifin.c
 * kirk johnson
 * november 1989
 *
 * routines for reading GIF files
 *
 * Copyright 1989 Kirk L. Johnson (see the included file
 * "kljcpyrght.h" for complete copyright information)
 */

#include "xloadimage.h"
#include "gif.h"
#include "kljcpyrght.h"

/****
 **
 ** local #defines
 **
 ****/

#define PUSH_PIXEL(p)                                       \
{                                                           \
  if (pstk_idx == PSTK_SIZE)                                \
    gifin_fatal("pixel stack overflow in PUSH_PIXEL()");    \
  else                                                      \
    pstk[pstk_idx++] = (p);                                 \
}

/****
 **
 ** local variables
 **
 ****/

static int interlace_start[4]= { /* start line for interlacing */
  0, 4, 2, 1
};

static int interlace_rate[4]= { /* rate at which we accelerate vertically */
  8, 8, 4, 2
};

static BYTE file_open  = 0;     /* status flags */
static BYTE image_open = 0;

static ZFILE *ins;              /* input stream */

static int  root_size;          /* root code size */
static int  clr_code;           /* clear code */
static int  eoi_code;           /* end of information code */
static int  code_size;          /* current code size */
static int  code_mask;          /* current code mask */
static int  prev_code;          /* previous code */

/*
 * NOTE: a long is assumed to be at least 32 bits wide
 */
static long work_data;          /* working bit buffer */
static int  work_bits;          /* working bit count */

static BYTE buf[256];           /* byte buffer */
static int  buf_cnt;            /* byte count */
static int  buf_idx;            /* buffer index */

static int table_size;          /* string table size */
static int prefix[STAB_SIZE];   /* string table : prefixes */
static int extnsn[STAB_SIZE];   /* string table : extensions */

static BYTE pstk[PSTK_SIZE];    /* pixel stack */
static int  pstk_idx;           /* pixel stack pointer */

/****
 **
 ** global variables
 **
 ****/

static int  gifin_rast_width;          /* raster width */
static int  gifin_rast_height;         /* raster height */
static BYTE gifin_g_cmap_flag;         /* global colormap flag */
static int  gifin_g_pixel_bits;        /* bits per pixel, global colormap */
static int  gifin_g_ncolors;           /* number of colors, global colormap */
static BYTE gifin_g_cmap[3][256];      /* global colormap */
static int  gifin_bg_color;            /* background color index */
static int  gifin_color_bits;          /* bits of color resolution */

static int  gifin_img_left;            /* image position on raster */
static int  gifin_img_top;             /* image position on raster */
static int  gifin_img_width;           /* image width */
static int  gifin_img_height;          /* image height */
static BYTE gifin_l_cmap_flag;         /* local colormap flag */
static int  gifin_l_pixel_bits;        /* bits per pixel, local colormap */
static int  gifin_l_ncolors;           /* number of colors, local colormap */
static BYTE gifin_l_cmap[3][256];      /* local colormap */
static BYTE gifin_interlace_flag;      /* interlace image format flag */

/*
 * open a GIF file, using s as the input stream
 */

static int gifin_open_file(s)
     ZFILE *s;
{
  /* make sure there isn't already a file open */
  if (file_open)
    return GIFIN_ERR_FAO;

  /* remember that we've got this file open */
  file_open = 1;
  ins       = s;

  /* check GIF signature */
  if (zread(ins, buf, GIF_SIG_LEN) != GIF_SIG_LEN)
    return GIFIN_ERR_EOF;

  buf[GIF_SIG_LEN] = '\0';
  if (strcmp((char *) buf, GIF_SIG) != 0)
    return GIFIN_ERR_BAD_SIG;

  /* read screen descriptor */
  if (zread(ins, buf, GIF_SD_SIZE) != GIF_SD_SIZE)
    return GIFIN_ERR_EOF;

  /* decode screen descriptor */
  gifin_rast_width   = (buf[1] << 8) + buf[0];
  gifin_rast_height  = (buf[3] << 8) + buf[2];
  gifin_g_cmap_flag  = (buf[4] & 0x80) ? 1 : 0;
  gifin_color_bits   = ((buf[4] & 0x70) >> 4) + 1;
  gifin_g_pixel_bits = (buf[4] & 0x07) + 1;
  gifin_bg_color     = buf[5];

  if (buf[6] != 0)
    return GIFIN_ERR_BAD_SD;

  /* load global colormap */
  if (gifin_g_cmap_flag)
  {
    gifin_g_ncolors = (1 << gifin_g_pixel_bits);

    if (gifin_load_cmap(gifin_g_cmap, gifin_g_ncolors) != GIFIN_SUCCESS)
      return GIFIN_ERR_EOF;
  }
  else
  {
    gifin_g_ncolors = 0;
  }

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * open next GIF image in the input stream; returns GIFIN_SUCCESS if
 * successful. if there are no more images, returns GIFIN_DONE. (might
 * also return various GIFIN_ERR codes.)
 */

static int gifin_open_image()
{
  int i;
  int separator;

  /* make sure there's a file open */
  if (!file_open)
    return GIFIN_ERR_NFO;

  /* make sure there isn't already an image open */
  if (image_open)
    return GIFIN_ERR_IAO;

  /* remember that we've got this image open */
  image_open = 1;

  /* skip over any extension blocks */
  do
  {
    separator = zgetc(ins);
    if (separator == GIF_EXTENSION)
    {
      if (gifin_skip_extension() != GIFIN_SUCCESS)
        return GIFIN_ERR_EOF;
    }
  }
  while (separator == GIF_EXTENSION);

  /* check for end of file marker */
  if (separator == GIF_TERMINATOR)
    return GIFIN_DONE;

  /* make sure we've got an image separator */
  if (separator != GIF_SEPARATOR)
    return GIFIN_ERR_BAD_SEP;

  /* read image descriptor */
  if (zread(ins, buf, GIF_ID_SIZE) != GIF_ID_SIZE)
    return GIFIN_ERR_EOF;

  /* decode image descriptor */
  gifin_img_left       = (buf[1] << 8) + buf[0];
  gifin_img_top        = (buf[3] << 8) + buf[2];
  gifin_img_width      = (buf[5] << 8) + buf[4];
  gifin_img_height     = (buf[7] << 8) + buf[6];
  gifin_l_cmap_flag    = (buf[8] & 0x80) ? 1 : 0;
  gifin_interlace_flag = (buf[8] & 0x40) ? 1 : 0;
  gifin_l_pixel_bits   = (buf[8] & 0x07) + 1;

  /* load local colormap */
  if (gifin_l_cmap_flag)
  {
    gifin_l_ncolors = (1 << gifin_l_pixel_bits);

    if (gifin_load_cmap(gifin_l_cmap, gifin_l_ncolors) != GIFIN_SUCCESS)
      return GIFIN_ERR_EOF;
  }
  else
  {
    gifin_l_ncolors = 0;
  }

  /* initialize raster data stream decoder */
  root_size = zgetc(ins);
  clr_code  = 1 << root_size;
  eoi_code  = clr_code + 1;
  code_size = root_size + 1;
  code_mask = (1 << code_size) - 1;
  work_bits = 0;
  work_data = 0;
  buf_cnt   = 0;
  buf_idx   = 0;

  /* initialize string table */
  for (i=0; i<STAB_SIZE; i++)
  {
    prefix[i] = NULL_CODE;
    extnsn[i] = i;
  }

  /* initialize pixel stack */
  pstk_idx = 0;

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * try to read next pixel from the raster, return result in *pel
 */

static int gifin_get_pixel(pel)
     int *pel;
{
  int  code;
  int  first;
  int  place;

  /* decode until there are some pixels on the pixel stack */
  while (pstk_idx == 0)
  {
    /* load bytes until we have enough bits for another code */
    while (work_bits < code_size)
    {
      if (buf_idx == buf_cnt)
      {
        /* read a new data block */
        if (gifin_read_data_block() != GIFIN_SUCCESS)
          return GIFIN_ERR_EOF;

        if (buf_cnt == 0)
          return GIFIN_ERR_EOD;
      }

      work_data |= ((long) buf[buf_idx++]) << work_bits;
      work_bits += 8;
    }

    /* get the next code */
    code        = work_data & code_mask;
    work_data >>= code_size;
    work_bits  -= code_size;

    /* interpret the code */
    if (code == clr_code)
    {
      /* reset decoder stream */
      code_size  = root_size + 1;
      code_mask  = (1 << code_size) - 1;
      prev_code  = NULL_CODE;
      table_size = eoi_code + 1;
    }
    else if (code == eoi_code)
    {
      /* Ooops! no more pixels */
      return GIFIN_ERR_EOF;
    }
    else if (prev_code == NULL_CODE)
    {
      gifin_push_string(code);
      prev_code = code;
    }
    else
    {
      if (code < table_size)
      {
        first = gifin_push_string(code);
      }
      else
      {
        place = pstk_idx;
        PUSH_PIXEL(NULL_CODE);
        first = gifin_push_string(prev_code);
        pstk[place] = first;
      }

      gifin_add_string(prev_code, first);
      prev_code = code;
    }
  }

  /* pop a pixel off the pixel stack */
  *pel = (int) pstk[--pstk_idx];

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * close an open GIF image
 */

static int gifin_close_image()
{
  /* make sure there's an image open */
  if (!image_open)
    return GIFIN_ERR_NIO;

  /* skip any remaining raster data */
  do
  {
    if (gifin_read_data_block() != GIFIN_SUCCESS)
      return GIFIN_ERR_EOF;
  }
  while (buf_cnt > 0);

  /* mark image as closed */
  image_open = 0;

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * close an open GIF file
 */

static int gifin_close_file()
{
  /* make sure there's a file open */
  if (!file_open)
    return GIFIN_ERR_NFO;

  /* mark file (and image) as closed */
  file_open  = 0;
  image_open = 0;

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * load a colormap from the input stream
 */

static int gifin_load_cmap(cmap, ncolors)
     BYTE cmap[3][256];
     int  ncolors;
{
  int i;

  for (i=0; i<ncolors; i++)
  {
    if (zread(ins, buf, 3) != 3)
      return GIFIN_ERR_EOF;
    
    cmap[GIF_RED][i] = buf[GIF_RED];
    cmap[GIF_GRN][i] = buf[GIF_GRN];
    cmap[GIF_BLU][i] = buf[GIF_BLU];
  }

  /* done! */
  return GIFIN_SUCCESS;
}
 
/*
 * skip an extension block in the input stream
 */

static int gifin_skip_extension()
{
  int function;

  /* get the extension function byte */
  function = zgetc(ins);

  /* skip any remaining raster data */
  do
  {
    if (gifin_read_data_block() != GIFIN_SUCCESS)
      return GIFIN_ERR_EOF;
  }
  while (buf_cnt > 0);

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * read a new data block from the input stream
 */

static int gifin_read_data_block()
{
  /* read the data block header */
  buf_cnt = zgetc(ins);

  /* read the data block body */
  if (zread(ins, buf, buf_cnt) != buf_cnt)
    return GIFIN_ERR_EOF;

  buf_idx = 0;

  /* done! */
  return GIFIN_SUCCESS;
}

/*
 * push a string (denoted by a code) onto the pixel stack
 * (returns the code of the first pixel in the string)
 */

static int gifin_push_string(code)
     int code;
{
  int rslt;

  while (prefix[code] != NULL_CODE)
  {
    PUSH_PIXEL(extnsn[code]);
    code = prefix[code];
  }

  PUSH_PIXEL(extnsn[code]);
  rslt = extnsn[code];

  return rslt;
}

/*
 * add a new string to the string table
 */

static gifin_add_string(p, e)
     int p;
     int e;
{
  prefix[table_size] = p;
  extnsn[table_size] = e;

  if ((table_size == code_mask) && (code_size < 12))
  {
    code_size += 1;
    code_mask  = (1 << code_size) - 1;
  }

  table_size += 1;
}

/*
 * semi-graceful fatal error mechanism
 */

static gifin_fatal(msg)
     char *msg;
{
  printf("Error reading GIF file: %s\n", msg);
  exit(0);
}

/* these are the routines added for interfacing to xloadimage
 */

/* tell someone what the image we're loading is.  this could be a little more
 * descriptive but I don't care
 */

static void tellAboutImage(name)
     char *name;
{
  printf("%s is a %dx%d %sGIF image with %d colors\n", name,
	 gifin_img_width, gifin_img_height,
	 (gifin_interlace_flag ? "interlaced " : ""),
	 (gifin_l_cmap_flag ? gifin_l_ncolors : gifin_g_ncolors));
}

Image *gifLoad(fullname, name, verbose)
     char         *fullname, *name;
     unsigned int  verbose;
{ ZFILE *zf;
  Image *image;
  int    x, y, pixel, pass, yrate, scanlen;
  byte  *pixptr, *pixline;

  if (! (zf= zopen(fullname)))
    return(NULL);
  if ((gifin_open_file(zf) != GIFIN_SUCCESS) || /* read GIF header */
      (gifin_open_image() != GIFIN_SUCCESS)) {  /* read image header */
    gifin_close_file();
    zclose(zf);
    return(NULL);
  }
  if (verbose)
    tellAboutImage(name);

  image= newRGBImage(gifin_img_width, gifin_img_height, (gifin_l_cmap_flag ?
							 gifin_l_pixel_bits :
							 gifin_g_pixel_bits));
  for (x= 0; x < gifin_g_ncolors; x++) {
    image->rgb.red[x]= gifin_g_cmap[GIF_RED][x] << 8;
    image->rgb.green[x]= gifin_g_cmap[GIF_GRN][x] << 8;
    image->rgb.blue[x]= gifin_g_cmap[GIF_BLU][x] << 8;
  }
  image->rgb.used= gifin_g_ncolors;

  /* if image has a local colormap, override global colormap
   */

  if (gifin_l_cmap_flag) {
    for (x= 0; x < image->rgb.size; x++) {
      image->rgb.red[x]= gifin_g_cmap[GIF_RED][x] << 8;
      image->rgb.green[x]= gifin_g_cmap[GIF_GRN][x] << 8;
      image->rgb.blue[x]= gifin_g_cmap[GIF_BLU][x] << 8;
    }
    image->rgb.used= gifin_l_ncolors;
  }

  /* interlaced image -- futz with the vertical trace.  i wish i knew what
   * kind of drugs the GIF people were on when they decided that they
   * needed to support interlacing.
   */

  if (gifin_interlace_flag) {
    scanlen= image->height * image->pixlen;

    /* interlacing takes four passes to read, each starting at a different
     * vertical point.
     */

    for (pass= 0; pass < 4; pass++) {
      y= interlace_start[pass];
      scanlen= image->width * image->pixlen * interlace_rate[pass];
      pixline= image->data + (y * image->width * image->pixlen);
      while (y < gifin_img_height) {
	pixptr= pixline;
	for (x= 0; x < gifin_img_width; x++) {
	  if (gifin_get_pixel(&pixel) != GIFIN_SUCCESS) {
	    printf("%s: Short read within image data\n", fullname);
	    exit(0);
	  }
	  valToMem(pixel, pixptr, image->pixlen);
	  pixptr += image->pixlen;
	}
	y += interlace_rate[pass];
	pixline += scanlen;
      }
    }
  }

  /* not an interlaced image, just read in sequentially
   */

  else {
    pixptr= image->data;
    for (y= 0; y < gifin_img_height; y++)
      for (x= 0; x < gifin_img_width; x++) {
	if (gifin_get_pixel(&pixel) != GIFIN_SUCCESS) {
	  printf("%s: Short read within image data\n", fullname);
	  exit(0);
	}
	valToMem(pixel, pixptr, image->pixlen);
	pixptr += image->pixlen;
      }
  }
  gifin_close_file();
  zclose(zf);
  image->title= dupString(name);
  return(image);
}

unsigned int gifIdent(fullname, name)
     char *fullname, *name;
{ ZFILE        *zf;
  unsigned int  ret;

  if (! (zf= zopen(fullname)))
    return(0);
  if ((gifin_open_file(zf) == GIFIN_SUCCESS) &&
      (gifin_open_image() == GIFIN_SUCCESS)) {
    tellAboutImage(name);
    ret= 1;
  }
  else
    ret= 0;
  gifin_close_file();
  zclose(zf);
  return(ret);
}
//E*O*F gif.c//

echo x - gif.h
cat > "gif.h" << '//E*O*F gif.h//'
/* gif.h:
 *
 * gifin.h
 * kirk johnson
 * november 1989
 * external interface to gifin.c
 *
 * Copyright 1989 Kirk L. Johnson (see the included file
 * "kljcpyrght.h" for complete copyright information)
 */

/*
 * gifin return codes
 */
#define GIFIN_SUCCESS       0   /* success */
#define GIFIN_DONE          1   /* no more images */

#define GIFIN_ERR_BAD_SD   -1   /* bad screen descriptor */
#define GIFIN_ERR_BAD_SEP  -2   /* bad image separator */
#define GIFIN_ERR_BAD_SIG  -3   /* bad signature */
#define GIFIN_ERR_EOD      -4   /* unexpected end of raster data */
#define GIFIN_ERR_EOF      -5   /* unexpected end of input stream */
#define GIFIN_ERR_FAO      -6   /* file already open */
#define GIFIN_ERR_IAO      -7   /* image already open */
#define GIFIN_ERR_NFO      -8   /* no file open */
#define GIFIN_ERR_NIO      -9   /* no image open */

/*
 * colormap indices 
 */

#define GIF_RED  0
#define GIF_GRN  1
#define GIF_BLU  2

/*
 * typedef BYTE for convenience
 */

typedef unsigned char BYTE;

static int gifin_open_file();
static int gifin_open_image();
static int gifin_get_pixel();
static int gifin_close_image();
static int gifin_close_file();
static int gifin_load_cmap();
static int gifin_skip_extension();
static int gifin_read_data_block();
static int gifin_push_string();
static int gifin_add_string();
static int gifin_fatal();

/* #defines, typedefs, and such
 */

#define GIF_SIG      "GIF87a"
#define GIF_SIG_LEN  6          /* GIF signature length */
#define GIF_SD_SIZE  7          /* GIF screen descriptor size */
#define GIF_ID_SIZE  9          /* GIF image descriptor size */

#define GIF_SEPARATOR   ','     /* GIF image separator */
#define GIF_EXTENSION   '!'     /* GIF extension block marker */
#define GIF_TERMINATOR  ';'     /* GIF terminator */

#define STAB_SIZE  4096         /* string table size */
#define PSTK_SIZE  1024         /* pixel stack size */

#define NULL_CODE  -1           /* string table null code */
//E*O*F gif.h//

echo x - kljcpyrght.h
cat > "kljcpyrght.h" << '//E*O*F kljcpyrght.h//'
#ifndef _KLJ_COPYRIGHT_

/****
  Copyright 1989 Kirk L. Johnson

  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. The
  author makes no representations about the suitability of this
  software for any purpose. It is provided "as is" without express
  or implied warranty.

  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
  IN NO EVENT SHALL THE AUTHOR 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.
****/

static char *KLJCopyright = "Copyright 1989 Kirk L. Johnson";
#define _KLJ_COPYRIGHT_
#endif
//E*O*F kljcpyrght.h//

exit 0

dan
-----------------------------------------------------------
		    O'Reilly && Associates
		argv@sun.com / argv@ora.com
	   632 Petaluma Ave, Sebastopol, CA 95472 
     800-338-NUTS, in CA: 800-533-NUTS, FAX 707-829-0104
    Opinions expressed reflect those of the author only.