/* GIFSCAN - scans through a GIF file and reports all parameters */

#ifndef lint
static char *SCCSid = "@(#)gifscn.c v1.2 changed 5/16/91 13:35:03";
#endif

#include <stdio.h>
#include <stdlib.h>

/* global values */
int Quiet = 0;                  /* don't print detail */
int Debug = 0;                  /* print debug/trace info */
extern int optind, opterr;
extern char *optarg;

/************************************************************************/

/* COLORMAP - reads color information in from the GIF file and displays */
/*            it in a user selected method.  This display may be in :   */
/*            hexidecimal (default), percentage, or decimal.  User      */
/*            selects output method by placing a switch (-d, -p, -h)    */
/*            between the program name and GIF filename at request time.*/

colormap(out, dev, times, file_byte_cnt)
FILE *dev;				  
int times;
int out;
int *file_byte_cnt;

#define DECIMAL 1
#define PERCENT 2
#define HEXIDEC 3
#define MAX 255

{
    unsigned int red;
    unsigned int green;
    unsigned int blue;

    int print_cnt;
    int i;

            /* Start of procedure */

    if (!Quiet) {
        if (out == DECIMAL)
            printf ("\nColor definitions in decimal   (index #, R, G, B)\n");

        if (out == PERCENT)
            printf ("\nColor definitions by percentage   (index #, R, G, B)\n");

        if (out == HEXIDEC)
            printf ("\nColor definitions in hexidecimal    (index #, R, G, B)\n");
    }

            /* read and print the color definitions */

    print_cnt = 0;

    for (i = 0; (i < times); i++)
    {
        red = getc(dev);
        green = getc(dev);
        blue = getc(dev);

        *file_byte_cnt += 3;

        if (!Quiet) switch (out)
        {
            case DECIMAL :
                printf ("%3d - %3d %3d %3d  ", i, red, green, blue);
                break;

            case PERCENT :
                red = (red * 100) / MAX;
                green = (green * 100) / MAX;
                blue = (blue * 100) / MAX;
                printf ("%3d - %3d %3d %3d  ", i, red, green, blue);
                break;

            case HEXIDEC :
                printf ("%3d - %2x %2x %2x   ", i, red, green, blue);
                break;
        }
  
        print_cnt++;

        if (!Quiet && print_cnt == 4)
        {
            printf ("\n");
            print_cnt = 0;
        }
    }
    if (!Quiet && (times % 4) != 0)
        printf ("\n");
}

/************************************************************************/

/* GETBYTES - routine to retrieve two bytes of information from the GIF */
/*            file and then shift them into correct byte order.  The    */
/*            information is stored in Least Significant Byte order.    */

unsigned int getbytes(dev, file_byte_cnt)

FILE *dev;
int *file_byte_cnt;

{
    int byte1;
    int byte2;
    int result;

            /* read bytes and shift over */

    byte1 = getc(dev);
    byte2 = getc(dev);

    *file_byte_cnt += 2;

    result = (byte2 << 8) | byte1;

    return result;

}

/***********************************************************************/

/* IMAGEINF - routine to read the GIF image information and display it */
/*            to the user's screen in an orderly fasion.  If there are */
/*            multiple images then IMAGEINF will be called to display  */
/*            multiple screens.                                        */

imageinf(dev, bits_to_use, color_style, file_byte_cnt)
FILE *dev;
int bits_to_use;
int color_style;
int *file_byte_cnt;

{
    int byte1;
    int byte2;
    int byte3;
    int image_left;
    int image_top;
    int data_byte_cnt;
    int bits_per_pix;
    int color_res;
    int colors;
    int i;
    int local;
    int unexpected;

    unsigned int width;
    unsigned int height;

    unsigned long bytetot;
    unsigned long possbytes;

            /* determine the image left value */

    image_left = getbytes(dev, file_byte_cnt);

            /* determine the image top value */

    image_top = getbytes(dev, file_byte_cnt);
    printf ("\nImage Left:     %5d\t\tImage Top:     %5d", image_left,
                                                           image_top);

            /* determine the image width */

    width = getbytes(dev, file_byte_cnt);

            /* determine the image height */

    height = getbytes(dev, file_byte_cnt);
    printf ("\nImage Width:    %5d\t\tImage Height:  %5d", width, height);

            /* check for interlaced image */

    byte1 = getc(dev);
    (*file_byte_cnt)++;

    byte2 = byte1 & 0x40;
    if (byte2 == 0x40)
        printf ("\nInterlaced: Yes");
    else
        printf ("\nInterlaced: No");

            /* check for a local map */

    byte2 = byte1 & 0x80;
    if (byte2 == 0x80)
    {
        local = 1;
        printf ("\nLocal Color Map: Yes");
    }
    else
    {
        local = 0;
        printf ("\nLocal Color Map: No");
    }

            /* check for the 3 zero bits */

    byte2 = byte1 & 0x38;
    if (byte2 != 0)
        printf ("\n? -- Reserved zero bits in image not zeros.\n");

            /* determine the # of color bits in local map */

    bits_per_pix = byte1 & 0x07;
    bits_per_pix++;
    colors = 1 << bits_per_pix;

    if (local == 1)
    {
        bits_to_use = bits_per_pix;
        printf ("\nBits per pixel: %5d\t\t# colors :     %5d", bits_per_pix,
                                                               colors);
        colormap (color_style, dev, colors, file_byte_cnt);
    }

            /* retrieve the code size */

    byte1 = getc(dev);
    (*file_byte_cnt)++;

    if ((byte1 < 2) || (byte1 > 8))
    {
        printf ("\n? -- Code size %d at start of image");
        printf ("\n     is out of range (2-8).\n");
    }
    else
        printf ("\nLZW min code size (bits):  %3d", byte1);

            /* tally up the total bytes and read past each data block */

    bytetot = 0;
    possbytes = 0;

    while ((data_byte_cnt = getc(dev)) > 0)
    {
        (*file_byte_cnt)++;
        bytetot = bytetot + data_byte_cnt;
        if (!Quiet) printf("\n Record size %d",data_byte_cnt);
        for (i = 0; (i < data_byte_cnt); i++)
        {
            byte2 = getc(dev);
            (*file_byte_cnt)++;
            if (byte2 == EOF)
            {
                (*file_byte_cnt)--;
                printf ("\n? -- EOF reached inside image data block.\n");
                printf ("\n     Located %d bytes into file.",*file_byte_cnt);
                exit (2);
            }
        }
    }

    (*file_byte_cnt)++;
					
    possbytes = (unsigned long) width * height;
    i = 8 / bits_to_use;
    possbytes = possbytes / i;
    printf ("\nTotal number of bytes in image:  %ld out of possible  %ld\n",
                                                       bytetot, possbytes);

    if (data_byte_cnt == EOF)
    {
        (*file_byte_cnt)--;
        printf ("\n? -- EOF reached before zero byte count");
        printf ("\n     of image was read.\n");
        printf ("\n     Located %d bytes into file.",*file_byte_cnt);
        exit (3);
    }
}

/************************************************************************/

/* EXTNINFO - routine to read the GIF file for extension data and       */
/*            display it to the screen in an orderly fasion.  This      */
/*            extension information may be located before, between, or  */
/*            after any of the image data.                              */

extninfo(dev, file_byte_cnt)
FILE *dev;
int *file_byte_cnt;

{
    int byte1;
    int byte2;
    int i;
    int data_byte_cnt;

    unsigned long bytetot;

            /* retrieve the function code */

    byte1 = getc(dev);
    (*file_byte_cnt)++;
    printf ("\nGIF extension code %d located at byte %d", byte1,
                                                          *file_byte_cnt);

            /* tally up the total bytes and read past each data block */

    bytetot = 0;

    while ((data_byte_cnt = getc(dev)) > 0)
    {				
        (*file_byte_cnt)++;
        bytetot = bytetot + data_byte_cnt;
        for (i = 0; (i < data_byte_cnt); i++)
        {
            byte2 = getc(dev);
            (*file_byte_cnt)++;
            if (byte2 == EOF)
            {
                (*file_byte_cnt)--;
                printf ("\n? -- EOF was reached inside extension data block.\n");
                printf ("\n     Located %d bytes into file.",*file_byte_cnt);
                exit (2);
            }
        }
    }

    (*file_byte_cnt)++;
    printf ("\nTotal number of bytes in extension:  %ld\n", bytetot);

    if (data_byte_cnt == EOF)
    {
        (*file_byte_cnt)--;
        printf ("\n? -- EOF was reached before zero byte count");
        printf ("\n     of extension was read.\n");
        printf ("\n     Located %d bytes into file.",*file_byte_cnt);
        exit (3);
    }
}

/************************************************************************/

/* CHKUNEXP - routine to check for any unexpected nonzero data found    */
/*            within the GIF file.  This routine will help determine    */
/*            where the unexpected data may reside in the file.         */

chkunexp (unexpected, determiner)
int *unexpected;
int determiner;

{

            /* Determine place in the GIF file */

    if (determiner > 0)
    {
        printf ("\n? -- %d bytes of unexpected data found before",
                                                      *unexpected);
        printf ("\n     image %d.\n", determiner);
    }
    else if (determiner == -1)
    {
        printf ("\n? -- %d bytes of unexpected data found before",
                                                      *unexpected);
        printf ("\n     GIF file terminator.\n");
    }
    else if (determiner == -2)
    {
        printf ("\n? -- %d bytes of unexpected data found after",
                                                      *unexpected);
        printf ("\n     GIF file terminator.\n");
    }
    else
    {
        printf ("\n? -- %d bytes of unexpected data found at",
                                                      *unexpected);
        printf ("\n     or after expected GIF terminator byte.\n");
    }

            /* Zero out the unexpected variable for */
            /* the next group that may be encountered */

    *unexpected = 0;
}

/************************************************************************/

/* MAIN - the main routine reads the GIF file for the global GIF        */
/*        information which it displays to the user's screen.  This     */
/*        routine also determines which subroutine to call and which    */
/*        information to process.                                       */


main(argc, argv)
char *argv[];
int argc;

#define DECIMAL 1
#define PERCENT 2
#define HEXIDEC 3

{
    char filename[15];
    char version[7];
    char style[5];

    int byte1;
    int byte2;
    int byte3;
    int image_left;
    int image_top;
    int data_byte_cnt;
    int image_cnt;
    int bits_per_pix;
    int bits_to_use;
    int color_res;
    int colors;
    int i;
    int globl;
    int end_gif_fnd;
    int unexpected;
    int color_style;
    int switch_present;
    int file_byte_cnt;

    unsigned int width;
    unsigned int height;

    FILE *in;

            /* Start of Processing */

            /* If just one argument then display the message */

    if (argc == 1)
    {
        printf ("\nUSAGE:    gifscan  color_opt  filename\n");
        printf ("\ncolor_opt   specifies how color lookup table");
        printf ("\n            definitions are displayed");
        printf ("\n  -h  :     output as hexidecimal (0 - FF) (default)");
        printf ("\n  -d  :     output as decimal (0 - 255)");
        printf ("\n  -p  :     output as percentage (0 - 100)\n");
        exit (0);
    }

    color_style = 0;
    switch_present = 0;

    while ((i = getopt(argc, argv, "dhpqD")) != EOF) {
        switch (i) {
        case 'd': /* decimal */
            color_style = DECIMAL;
            break;
        case 'p': /* percent */
            color_style = PERCENT;
            break;
        case 'h': /* hexadecimal */
            color_style = HEXIDEC;
            break;
        case 'q': /* set quiet flag */
            Quiet = 1;
            break;
        case 'D': /* debug (undoc) */
            Debug = 1;
            break;
        default:
            exit(1);
        }
    }

            /* Check for GIF filename */

    if (argc == optind) {
        fprintf(stderr, "Missing filename(s)\nType gifscn for help\n");
        exit(1);
    }
    else {
        if (Debug) fprintf(stderr, "Filename: %s\n", argv[optind]);
        /* copy the filename and open it */
        strcpy(filename, argv[optind]);
        in = fopen(filename, "rb");
        if (in == NULL) {
        	fprintf(stderr, "Can't open file %s\n", filename);
        	exit(1);
        }
    }

    image_cnt = 0;
    end_gif_fnd = 0;
    unexpected = 0;
    file_byte_cnt = 0;

            /* get version from file */

    if ((version[0] = getc(in)) == 0x47)
    {
        for (i = 1; (i < 6); i++)
            version[i] = getc(in);
        version[6] = '\0';
        printf ("\nVersion: %s", version);
    }
    else
    {
        printf ("\n? -- NOT a GIF file\n");
        exit(1);
    }

    file_byte_cnt += 6;

            /* determine screen width */

    width = getbytes(in, &file_byte_cnt);

            /* determine screen height */

    height = getbytes(in, &file_byte_cnt);
    printf ("\nScreen Width:      %5d\tScreen Height:     %5d", width,
                                                                height);

            /* check for a Global Map */

    byte1 = getc(in);
    file_byte_cnt++;

    byte2 = byte1 & 0x80;
    if (byte2 == 0x80)
    {
        printf ("\nGlobal Color Map: Yes");
        globl = 1;
    }
    else
    {
        printf ("\nGlobal Color Map: No");
        globl = 0;
    }

            /* Check for the 0 bit */

    byte2 = byte1 & 0x08;
    if (byte2 != 0)
        printf ("\n? -- Reserved zero bit is not zero.\n");

            /* determine the color resolution */

    byte2 = byte1 & 0x70;
    color_res = byte2 >> 4;
 
            /* get the background index */

    byte3 = getc(in);
    file_byte_cnt++;
    printf ("\nColor resolution:  %5d\tBackground index:  %5d",
                                                     ++color_res,
                                                          byte3);

            /* determine the bits per pixel */

    bits_per_pix = byte1 & 0x07;
    bits_per_pix++;
    bits_to_use = bits_per_pix;

            /* determine # of colors in global map */

    colors = 1 << bits_per_pix;
    printf ("\nBits per pixel:    %5d\t# colors:          %5d\n", bits_per_pix,
                                                                colors);

            /* check for the 0 byte */

    byte1 = getc(in);
    file_byte_cnt++;
    if (byte1 != 0)
        printf ("\n? -- Reserved byte after Background index is not zero.\n");

    if (globl == 1)
        colormap (color_style, in, colors, &file_byte_cnt);

            /* check for the zero byte count, a new image, or */
            /* the end marker for the gif file */

    while ((byte1 = getc(in)) != EOF)
    {
        file_byte_cnt++;
        if (byte1 == ',')
        {
            image_cnt++;
            if (unexpected != 0)
                chkunexp (&unexpected, image_cnt);
            printf ("\nImage # %2d separator located at byte %d.",
                                             image_cnt, file_byte_cnt);

            imageinf (in, bits_to_use, color_style, &file_byte_cnt);
        }

        else if (byte1 == '!')

            /* Extension data found */

            extninfo (in, &file_byte_cnt);

        else if (byte1 == ';')

            /* GIF terminator located, check for any */
            /* unexpected data found before terminator */

        {
            if (unexpected != 0)
                chkunexp (&unexpected, -1);
            end_gif_fnd = 1;
            printf ("\nGIF terminator located at byte %d.\n",
                                                 file_byte_cnt);
        }

        else
            unexpected++;
    }

            /* EOF has been reached - check last bytes read */

    if (end_gif_fnd == 0)
    {
        printf ("\n? -- GIF file terminator ';' was not found.");
        printf ("\n     Expected at byte %d in file.\n",file_byte_cnt);
    }
    if ((unexpected != 0) && (end_gif_fnd == 0))
        chkunexp (&unexpected, -3);
    else if (unexpected != 0)
        chkunexp (&unexpected, -2);
}
