/***************************************************************************
 * 2005/02/25 Miguel de Benito <nonick AT 8027 DOT org>
 *
 * Extracts info from GIF87a files.
 * WARNING: this should work on GIF89a headers up to the first extended field,
 * so it most probably WON'T WORK with image color tables. I'll just stick
 * with the global one.
 *
 * More info on http://www.fileformat.info/format/gif/
 ***************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>          
#include <unistd.h>         /* read() / write() */
#include <stdarg.h>         /* va_start(), etc. */
#include <stdlib.h>         /* malloc() */

/**
 * GIF header
 */
struct gif_header
{
    u_int8_t signature[3];        /* Header Signature (always "GIF") */
    u_int8_t version[3];          /* GIF format version("87a" or "89a") */
} __attribute__((packed));

/**
 * Logical screen descriptor. This appears once, just after the header,
 * and is followed by the global color table, if there is any.
 */
struct gif_screen_descriptor
{
    u_int16_t s_width;            /* Width of Display Screen in Pixels */
    u_int16_t s_height;           /* Height of Display Screen in Pixels */
    u_int8_t packed;              /* Screen and Color Map Information */
    u_int8_t background_color;    /* Background Color Index */
    u_int8_t aspect_ratio;        /* Pixel Aspect Ratio */
} __attribute__((packed));          


/**
 * Header for each one of the images in the GIF
 */
struct gif_image_descriptor
{
    u_int8_t separator;           /* Image Descriptor identifier */
    u_int16_t left;               /* X position of image on the display */
    u_int16_t top;                /* Y position of image on the display */
    u_int16_t width;              /* Width of the image in pixels */
    u_int16_t height;             /* Height of the image in pixels */
    u_int8_t packed;              /* Image and Color Table Data Information */
} __attribute__((packed));

/**
 * The information found in a Graphics Control block is used to modify the 
 * data in the Graphical Rendering block that immediately follows it. A 
 * Graphics Control block may modify either bitmap or plain-text data. It 
 * must also occur in the GIF stream before the data it modifies, and only 
 * one Graphics Control block may appear per Graphics Rendering block. 
 */
struct gif_graphcontrol_ext
{
    u_int8_t introducer;   /* Extension Introducer (always 21h) */
    u_int8_t label;        /* Graphic Control Label (always F9h) */
    u_int8_t block_size;   /* Size of remaining fields (always 04h) */
    u_int8_t packed;       /* Method of graphics disposal to use */
    u_int16_t delay;       /* Hundredths of seconds to wait   */
    u_int8_t color_index;  /* Transparent Color Index */
    u_int8_t terminator;   /* Block Terminator (always 0) */
} __attribute__((packed));

/**
 * Any number of Plain Text Extension blocks may appear in a GIF file. 
 * To display plain-text data, a grid is described that contains the data. 
 * The height, width, and position of the grid on the display screen are 
 * specified. The size of each cell in the grid is also described, and one 
 * character is displayed per cell. The foreground and background color of 
 * the text are taken from the Global Color Table and are also described in 
 * the Plain Text Extension block. The actual Plain Text data is a simple 
 * string of ASCII characters. 
 */
struct gif_plaintext_ext
{
    u_int8_t Introducer;         /* Extension Introducer (always 21h) */
    u_int8_t Label;              /* Extension Label (always 01h) */
    u_int8_t BlockSize;          /* Size of Extension Block (always 0Ch) */
    u_int16_t TextGridLeft;      /* X position of text grid in pixels */
    u_int16_t TextGridTop;       /* Y position of text grid in pixels */
    u_int16_t TextGridWidth;     /* Width of the text grid in pixels */
    u_int16_t TextGridHeight;    /* Height of the text grid in pixels */
    u_int8_t CellWidth;          /* Width of a grid cell in pixels */
    u_int8_t CellHeight;         /* Height of a grid cell in pixels */
    u_int8_t TextFgColorIndex;   /* Text foreground color index value */
    u_int8_t TextBgColorIndex;   /* Text background color index value */
    u_int8_t *PlainTextData;     /* The Plain Text data */
    u_int8_t Terminator;         /* Block Terminator (always 0) */
} __attribute__((packed));


/**
 * Each color in the color table is defined by three bytes.
 */
struct gif_rgb
{
    u_int8_t red;
    u_int8_t green;
    u_int8_t blue;
} __attribute__((packed));


/** 
 * Bit masks for logical screen descriptor packed bit field.
 *   bits 0-2 number of bits in each color entry minus one  (???)
 *   bit  3   Is the global color table sorted by relevance?
 *   bits 4-6 Number of colors bits (see dump_color_table())
 *   bit  7   Is there a global color table?
 */
#define SCR_CTABLE_SIZE    ((1 << 0) | (1 << 1) | (1 << 2)) 
#define SCR_CTABLE_SORT    ((1 << 3))
#define SCR_CRESOLUTION    ((1 << 4) | (1 << 5) | (1 << 6))
#define SCR_CTABLE_FLAG    ((1 << 7))

/** 
 * Bit masks for logical image descriptor packed bit field.
 *   bit  0   Is there a global color table?
 *   bit  1   Is the image interlaced.
 *   bit  2   Is the global color table sorted by relevance?
 *   bits 3-4 Reserved
 *   bits 5-7 Number of colors bits (see dump_color_table())
 */
#define IMG_CTABLE_FLAG    ((1 << 0))
#define IMG_INTERLACED     ((1 << 1))
#define IMG_CTABLE_SORT    ((1 << 2))
#define IMG_RESERVED       ((1 << 3) | (1 << 4))
#define IMG_CTABLE_SIZE    ((1 << 5) | (1 << 6) | (1 << 7))


/**
 * This otherwise unnecessary declaration is needed for __attribute__ 
 * to work. This __attribute__ tells the compiler that this is a 
 * printf-like function
 */
void error(char *fmt, ...) __attribute__((format(printf, 1, 2)));

/**
 *
 */
void error(char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
        printf ("\nERROR: ");
        vprintf (fmt, args);
        printf ("\n\nUsage:\n\tgif [filename.gif] [output.pal]\n");
        printf ("\nWARNING: this is incomplete work. It may not work for you!\n");
    va_end(args);
    
    exit(1);
} 

/**
 *
 */
int get_screen_info(int fd)
{
    struct gif_header hdr;
    struct gif_screen_descriptor dsc;
    
    if (! read(fd, &hdr, sizeof(struct gif_header)))
        error ("Error reading gif header. Tryed to read %d bytes.", 
               sizeof(struct gif_header));
    
    if (! read(fd, &dsc, sizeof(struct gif_screen_descriptor) ))
        error ("Error reading gif header. Tryed to read %d bytes.", 
               sizeof(struct gif_screen_descriptor));
    
    u_int16_t color_table_size, color_resolution, pixel_aspect_ratio;
    
    color_table_size = (u_int16_t)(1 << ((dsc.packed & SCR_CTABLE_SIZE) + 1));
    color_resolution = (u_int16_t)(1 << ((dsc.packed & SCR_CRESOLUTION) + 1));
    pixel_aspect_ratio = (dsc.aspect_ratio + 15) / 64;  
    
    printf ("Signature: %c%c%c\nVersion: %c%c%c\n", 
            hdr.signature[0], hdr.signature[1], hdr.signature[2],
            hdr.version[0], hdr.version[1], hdr.version[2]);
    printf ("Screen width: %d\nScreen height: %d\n", dsc.s_width, dsc.s_height);           
    printf ("Color table size: %d\nColor resolution: %d\n", 
            color_table_size, color_resolution);
    printf ("Color table is sorted: %s\n", 
            (dsc.packed & SCR_CTABLE_SORT) ? "yes" : "no");
    printf ("There is a global color table: %s\n", 
            (dsc.packed & SCR_CTABLE_FLAG) ? "yes" : "no");
    printf ("Background color index: %d\n", dsc.background_color);
    printf ("Pixel aspect ratio: %d\n", pixel_aspect_ratio);
    
    return (dsc.packed & SCR_CTABLE_FLAG) ? color_table_size : 0;
}


/**
 *
 */
void get_image_info(int fd)
{
    struct gif_image_descriptor img;
    
    u_int16_t ctable_entry_size;
    
    if (! read(fd, &img, sizeof(struct gif_image_descriptor) ))
        error ("Error reading image info.");
    
    ctable_entry_size = (u_int16_t)(1 << ((img.packed & IMG_CTABLE_SIZE)+ 1));
    
    printf ("-------------Image info-------------\n");
    printf ("There is an image color table: %s\n", 
            (img.packed & IMG_CTABLE_FLAG) ? "yes" : "no");
    printf ("Image is interlaced: %s\n", 
            (img.packed & IMG_INTERLACED) ? "yes" : "no");
    printf ("Color table is sorted: %s\n", 
            (img.packed & IMG_CTABLE_SORT) ? "yes" : "no");
    printf ("Color table entry size: %d\n", ctable_entry_size);
    
    return;
}

/**
 *
 */
int dump_color_table(int fd, u_int16_t numcolors)
{
    u_int16_t i;
    size_t size = sizeof(struct gif_rgb) * numcolors;
    struct gif_rgb *pal;
    
    if ((pal = (struct gif_rgb *)malloc(size)) == NULL)
        error ("Not enough memory?? I tryed to allocate %d bytes", size);
                
    if (! read(fd, pal, size))
        error ("Error reading gif color table. Not enough input while trying to read %d bytes.", size);
    
    for (i = 0; i < numcolors ;i++)
      printf ("Color %03d: #%02X%02X%02X\n", i, pal[i].red, pal[i].green, pal[i].blue);
    
    return i;
}

/**
 *
 */
int main (int argc, char **argv)
{
    int fd;
    u_int16_t numcolors;
    
    if (argc < 2)
        error ("No gif file specified.");
      
    /*  
    if (argc < 3)
        error ("No ouput file specified.");
    */
    if ((fd = open(argv[1], O_RDONLY)) == -1)
        error ("Could not open input file %s for reading.", argv[1]);
    /*    
    if ((fd = open(argv[2], O_WRONLY | O_CREAT)) == -1)
        error ("Could not open output file %s for writing.", argv[2]);
    */    
    if (! (numcolors = get_screen_info(fd)))
        error("This image lacks a global color table.");
    else
        dump_color_table(fd, numcolors);
    
    get_image_info(fd);
    
    return 0;
}
