| /* |
| * "$Id: image-gif.c 7720 2008-07-11 22:46:21Z mike $" |
| * |
| * GIF image routines for CUPS. |
| * |
| * Copyright 2007-2011 by Apple Inc. |
| * Copyright 1993-2007 by Easy Software Products. |
| * |
| * These coded instructions, statements, and computer programs are the |
| * property of Apple Inc. and are protected by Federal copyright |
| * law. Distribution and use rights are outlined in the file "LICENSE.txt" |
| * which should have been included with this file. If this file is |
| * file is missing or damaged, see the license at "http://www.cups.org/". |
| * |
| * This file is subject to the Apple OS-Developed Software exception. |
| * |
| * Contents: |
| * |
| * _cupsImageReadGIF() - Read a GIF image file. |
| * gif_get_block() - Read a GIF data block... |
| * gif_get_code() - Get a LZW code from the file... |
| * gif_read_cmap() - Read the colormap from a GIF file... |
| * gif_read_image() - Read a GIF image stream... |
| * gif_read_lzw() - Read a byte from the LZW stream... |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include "image-private.h" |
| |
| |
| /* |
| * GIF definitions... |
| */ |
| |
| #define GIF_INTERLACE 0x40 |
| #define GIF_COLORMAP 0x80 |
| #define GIF_MAX_BITS 12 |
| |
| typedef cups_ib_t gif_cmap_t[256][4]; |
| typedef short gif_table_t[4096]; |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| static int gif_eof = 0; /* Did we hit EOF? */ |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static int gif_get_block(FILE *fp, unsigned char *buffer); |
| static int gif_get_code (FILE *fp, int code_size, int first_time); |
| static int gif_read_cmap(FILE *fp, int ncolors, gif_cmap_t cmap, |
| int *gray); |
| static int gif_read_image(FILE *fp, cups_image_t *img, gif_cmap_t cmap, |
| int interlace); |
| static int gif_read_lzw(FILE *fp, int first_time, int input_code_size); |
| |
| |
| /* |
| * '_cupsImageReadGIF()' - Read a GIF image file. |
| */ |
| |
| int /* O - Read status */ |
| _cupsImageReadGIF( |
| cups_image_t *img, /* IO - cupsImage */ |
| FILE *fp, /* I - cupsImage file */ |
| cups_icspace_t primary, /* I - Primary choice for colorspace */ |
| cups_icspace_t secondary, /* I - Secondary choice for colorspace */ |
| int saturation, /* I - Color saturation (%) */ |
| int hue, /* I - Color hue (degrees) */ |
| const cups_ib_t *lut) /* I - Lookup table for gamma/brightness */ |
| { |
| unsigned char buf[1024]; /* Input buffer */ |
| gif_cmap_t cmap; /* Colormap */ |
| int i, /* Looping var */ |
| bpp, /* Bytes per pixel */ |
| gray, /* Grayscale image? */ |
| ncolors, /* Bits per pixel */ |
| transparent; /* Transparent color index */ |
| |
| |
| /* |
| * GIF files are either grayscale or RGB - no CMYK... |
| */ |
| |
| if (primary == CUPS_IMAGE_RGB_CMYK) |
| primary = CUPS_IMAGE_RGB; |
| |
| /* |
| * Read the header; we already know it is a GIF file... |
| */ |
| |
| fread(buf, 13, 1, fp); |
| |
| img->xsize = (buf[7] << 8) | buf[6]; |
| img->ysize = (buf[9] << 8) | buf[8]; |
| ncolors = 2 << (buf[10] & 0x07); |
| gray = primary == CUPS_IMAGE_BLACK || primary == CUPS_IMAGE_WHITE; |
| |
| if (buf[10] & GIF_COLORMAP) |
| if (gif_read_cmap(fp, ncolors, cmap, &gray)) |
| { |
| fclose(fp); |
| return (-1); |
| } |
| |
| transparent = -1; |
| |
| for (;;) |
| { |
| switch (getc(fp)) |
| { |
| case ';' : /* End of image */ |
| fclose(fp); |
| return (-1); /* Early end of file */ |
| |
| case '!' : /* Extension record */ |
| buf[0] = getc(fp); |
| if (buf[0] == 0xf9) /* Graphic Control Extension */ |
| { |
| gif_get_block(fp, buf); |
| if (buf[0] & 1) /* Get transparent color index */ |
| transparent = buf[3]; |
| } |
| |
| while (gif_get_block(fp, buf) != 0); |
| break; |
| |
| case ',' : /* cupsImage data */ |
| fread(buf, 9, 1, fp); |
| |
| if (buf[8] & GIF_COLORMAP) |
| { |
| ncolors = 2 << (buf[8] & 0x07); |
| gray = primary == CUPS_IMAGE_BLACK || primary == CUPS_IMAGE_WHITE; |
| |
| if (gif_read_cmap(fp, ncolors, cmap, &gray)) |
| { |
| fclose(fp); |
| return (-1); |
| } |
| } |
| |
| if (transparent >= 0) |
| { |
| /* |
| * Make transparent color white... |
| */ |
| |
| cmap[transparent][0] = 255; |
| cmap[transparent][1] = 255; |
| cmap[transparent][2] = 255; |
| } |
| |
| if (gray) |
| { |
| switch (secondary) |
| { |
| case CUPS_IMAGE_CMYK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageWhiteToCMYK(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_CMY : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageWhiteToCMY(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_BLACK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageWhiteToBlack(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_WHITE : |
| break; |
| case CUPS_IMAGE_RGB : |
| case CUPS_IMAGE_RGB_CMYK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageWhiteToRGB(cmap[i], cmap[i], 1); |
| break; |
| } |
| |
| img->colorspace = secondary; |
| } |
| else |
| { |
| if (hue != 0 || saturation != 100) |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBAdjust(cmap[i], 1, saturation, hue); |
| |
| switch (primary) |
| { |
| case CUPS_IMAGE_CMYK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBToCMYK(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_CMY : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBToCMY(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_BLACK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBToBlack(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_WHITE : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBToWhite(cmap[i], cmap[i], 1); |
| break; |
| case CUPS_IMAGE_RGB : |
| case CUPS_IMAGE_RGB_CMYK : |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageRGBToRGB(cmap[i], cmap[i], 1); |
| break; |
| } |
| |
| img->colorspace = primary; |
| } |
| |
| if (lut) |
| { |
| bpp = cupsImageGetDepth(img); |
| |
| for (i = ncolors - 1; i >= 0; i --) |
| cupsImageLut(cmap[i], bpp, lut); |
| } |
| |
| img->xsize = (buf[5] << 8) | buf[4]; |
| img->ysize = (buf[7] << 8) | buf[6]; |
| |
| /* |
| * Check the dimensions of the image; since the dimensions are |
| * a 16-bit integer we just need to check for 0... |
| */ |
| |
| if (img->xsize == 0 || img->ysize == 0) |
| { |
| fprintf(stderr, "DEBUG: Bad GIF image dimensions: %dx%d\n", |
| img->xsize, img->ysize); |
| fclose(fp); |
| return (1); |
| } |
| |
| i = gif_read_image(fp, img, cmap, buf[8] & GIF_INTERLACE); |
| fclose(fp); |
| return (i); |
| } |
| } |
| } |
| |
| |
| /* |
| * 'gif_get_block()' - Read a GIF data block... |
| */ |
| |
| static int /* O - Number characters read */ |
| gif_get_block(FILE *fp, /* I - File to read from */ |
| unsigned char *buf) /* I - Input buffer */ |
| { |
| int count; /* Number of character to read */ |
| |
| |
| /* |
| * Read the count byte followed by the data from the file... |
| */ |
| |
| if ((count = getc(fp)) == EOF) |
| { |
| gif_eof = 1; |
| return (-1); |
| } |
| else if (count == 0) |
| gif_eof = 1; |
| else if (fread(buf, 1, count, fp) < count) |
| { |
| gif_eof = 1; |
| return (-1); |
| } |
| else |
| gif_eof = 0; |
| |
| return (count); |
| } |
| |
| |
| /* |
| * 'gif_get_code()' - Get a LZW code from the file... |
| */ |
| |
| static int /* O - LZW code */ |
| gif_get_code(FILE *fp, /* I - File to read from */ |
| int code_size, /* I - Size of code in bits */ |
| int first_time) /* I - 1 = first time, 0 = not first time */ |
| { |
| unsigned i, j, /* Looping vars */ |
| ret; /* Return value */ |
| int count; /* Number of bytes read */ |
| static unsigned char buf[280]; /* Input buffer */ |
| static unsigned curbit, /* Current bit */ |
| lastbit, /* Last bit in buffer */ |
| done, /* Done with this buffer? */ |
| last_byte; /* Last byte in buffer */ |
| static const unsigned char bits[8] = /* Bit masks for codes */ |
| { |
| 0x01, 0x02, 0x04, 0x08, |
| 0x10, 0x20, 0x40, 0x80 |
| }; |
| |
| |
| if (first_time) |
| { |
| /* |
| * Just initialize the input buffer... |
| */ |
| |
| curbit = 0; |
| lastbit = 0; |
| last_byte = 0; |
| done = 0; |
| |
| return (0); |
| } |
| |
| if ((curbit + code_size) >= lastbit) |
| { |
| /* |
| * Don't have enough bits to hold the code... |
| */ |
| |
| if (done) |
| return (-1); /* Sorry, no more... */ |
| |
| /* |
| * Move last two bytes to front of buffer... |
| */ |
| |
| if (last_byte > 1) |
| { |
| buf[0] = buf[last_byte - 2]; |
| buf[1] = buf[last_byte - 1]; |
| last_byte = 2; |
| } |
| else if (last_byte == 1) |
| { |
| buf[0] = buf[last_byte - 1]; |
| last_byte = 1; |
| } |
| |
| /* |
| * Read in another buffer... |
| */ |
| |
| if ((count = gif_get_block (fp, buf + last_byte)) <= 0) |
| { |
| /* |
| * Whoops, no more data! |
| */ |
| |
| done = 1; |
| return (-1); |
| } |
| |
| /* |
| * Update buffer state... |
| */ |
| |
| curbit = (curbit - lastbit) + 8 * last_byte; |
| last_byte += count; |
| lastbit = last_byte * 8; |
| } |
| |
| for (ret = 0, i = curbit + code_size - 1, j = code_size; |
| j > 0; |
| i --, j --) |
| ret = (ret << 1) | ((buf[i / 8] & bits[i & 7]) != 0); |
| |
| curbit += code_size; |
| |
| return ret; |
| } |
| |
| |
| /* |
| * 'gif_read_cmap()' - Read the colormap from a GIF file... |
| */ |
| |
| static int /* O - -1 on error, 0 on success */ |
| gif_read_cmap(FILE *fp, /* I - File to read from */ |
| int ncolors, /* I - Number of colors in file */ |
| gif_cmap_t cmap, /* O - Colormap information */ |
| int *gray) /* IO - Is the image grayscale? */ |
| { |
| int i; /* Looping var */ |
| |
| |
| /* |
| * Read the colormap... |
| */ |
| |
| for (i = 0; i < ncolors; i ++) |
| if (fread(cmap[i], 3, 1, fp) < 1) |
| return (-1); |
| |
| /* |
| * Check to see if the colormap is a grayscale ramp... |
| */ |
| |
| for (i = 0; i < ncolors; i ++) |
| if (cmap[i][0] != cmap[i][1] || cmap[i][1] != cmap[i][2]) |
| break; |
| |
| if (i == ncolors) |
| { |
| *gray = 1; |
| return (0); |
| } |
| |
| /* |
| * If this needs to be a grayscale image, convert the RGB values to |
| * luminance values... |
| */ |
| |
| if (*gray) |
| for (i = 0; i < ncolors; i ++) |
| cmap[i][0] = (cmap[i][0] * 31 + cmap[i][1] * 61 + cmap[i][2] * 8) / 100; |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'gif_read_image()' - Read a GIF image stream... |
| */ |
| |
| static int /* I - 0 = success, -1 = failure */ |
| gif_read_image(FILE *fp, /* I - Input file */ |
| cups_image_t *img, /* I - cupsImage pointer */ |
| gif_cmap_t cmap, /* I - Colormap */ |
| int interlace) /* I - Non-zero = interlaced image */ |
| { |
| unsigned char code_size; /* Code size */ |
| cups_ib_t *pixels, /* Pixel buffer */ |
| *temp; /* Current pixel */ |
| int xpos, /* Current X position */ |
| ypos, /* Current Y position */ |
| pass; /* Current pass */ |
| int pixel; /* Current pixel */ |
| int bpp; /* Bytes per pixel */ |
| static const int xpasses[4] = /* X interleaving */ |
| { 8, 8, 4, 2 }, |
| ypasses[5] = /* Y interleaving */ |
| { 0, 4, 2, 1, 999999 }; |
| |
| |
| bpp = cupsImageGetDepth(img); |
| pixels = calloc(bpp, img->xsize); |
| xpos = 0; |
| ypos = 0; |
| pass = 0; |
| code_size = getc(fp); |
| |
| if (!pixels) |
| return (-1); |
| |
| if (code_size > GIF_MAX_BITS || gif_read_lzw(fp, 1, code_size) < 0) |
| { |
| free(pixels); |
| return (-1); |
| } |
| |
| temp = pixels; |
| while ((pixel = gif_read_lzw(fp, 0, code_size)) >= 0) |
| { |
| switch (bpp) |
| { |
| case 4 : |
| temp[3] = cmap[pixel][3]; |
| case 3 : |
| temp[2] = cmap[pixel][2]; |
| case 2 : |
| temp[1] = cmap[pixel][1]; |
| default : |
| temp[0] = cmap[pixel][0]; |
| } |
| |
| xpos ++; |
| temp += bpp; |
| if (xpos == img->xsize) |
| { |
| _cupsImagePutRow(img, 0, ypos, img->xsize, pixels); |
| |
| xpos = 0; |
| temp = pixels; |
| |
| if (interlace) |
| { |
| ypos += xpasses[pass]; |
| |
| if (ypos >= img->ysize) |
| { |
| pass ++; |
| |
| ypos = ypasses[pass]; |
| } |
| } |
| else |
| ypos ++; |
| } |
| |
| if (ypos >= img->ysize) |
| break; |
| } |
| |
| free(pixels); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'gif_read_lzw()' - Read a byte from the LZW stream... |
| */ |
| |
| static int /* I - Byte from stream */ |
| gif_read_lzw(FILE *fp, /* I - File to read from */ |
| int first_time, /* I - 1 = first time, 0 = not first time */ |
| int input_code_size) /* I - Code size in bits */ |
| { |
| int i, /* Looping var */ |
| code, /* Current code */ |
| incode; /* Input code */ |
| static short fresh = 0, /* 1 = empty buffers */ |
| code_size, /* Current code size */ |
| set_code_size, /* Initial code size set */ |
| max_code, /* Maximum code used */ |
| max_code_size, /* Maximum code size */ |
| firstcode, /* First code read */ |
| oldcode, /* Last code read */ |
| clear_code, /* Clear code for LZW input */ |
| end_code, /* End code for LZW input */ |
| *stack = NULL, /* Output stack */ |
| *sp; /* Current stack pointer */ |
| static gif_table_t *table = NULL; /* String table */ |
| |
| |
| if (first_time) |
| { |
| /* |
| * Setup LZW state... |
| */ |
| |
| set_code_size = input_code_size; |
| code_size = set_code_size + 1; |
| clear_code = 1 << set_code_size; |
| end_code = clear_code + 1; |
| max_code_size = 2 * clear_code; |
| max_code = clear_code + 2; |
| |
| /* |
| * Allocate memory for buffers... |
| */ |
| |
| if (table == NULL) |
| table = calloc(2, sizeof(gif_table_t)); |
| |
| if (table == NULL) |
| return (-1); |
| |
| if (stack == NULL) |
| stack = calloc(8192, sizeof(short)); |
| |
| if (stack == NULL) |
| return (-1); |
| |
| /* |
| * Initialize input buffers... |
| */ |
| |
| gif_get_code(fp, 0, 1); |
| |
| /* |
| * Wipe the decompressor table... |
| */ |
| |
| fresh = 1; |
| |
| for (i = 0; i < clear_code; i ++) |
| { |
| table[0][i] = 0; |
| table[1][i] = i; |
| } |
| |
| for (; i < 4096; i ++) |
| table[0][i] = table[1][0] = 0; |
| |
| sp = stack; |
| |
| return (0); |
| } |
| else if (fresh) |
| { |
| fresh = 0; |
| |
| do |
| firstcode = oldcode = gif_get_code(fp, code_size, 0); |
| while (firstcode == clear_code); |
| |
| return (firstcode); |
| } |
| else if (!table) |
| return (0); |
| |
| if (sp > stack) |
| return (*--sp); |
| |
| while ((code = gif_get_code (fp, code_size, 0)) >= 0) |
| { |
| if (code == clear_code) |
| { |
| for (i = 0; i < clear_code; i ++) |
| { |
| table[0][i] = 0; |
| table[1][i] = i; |
| } |
| |
| for (; i < 4096; i ++) |
| table[0][i] = table[1][i] = 0; |
| |
| code_size = set_code_size + 1; |
| max_code_size = 2 * clear_code; |
| max_code = clear_code + 2; |
| |
| sp = stack; |
| |
| firstcode = oldcode = gif_get_code(fp, code_size, 0); |
| |
| return (firstcode); |
| } |
| else if (code == end_code) |
| { |
| unsigned char buf[260]; |
| |
| |
| if (!gif_eof) |
| while (gif_get_block(fp, buf) > 0); |
| |
| return (-2); |
| } |
| |
| incode = code; |
| |
| if (code >= max_code) |
| { |
| *sp++ = firstcode; |
| code = oldcode; |
| } |
| |
| while (code >= clear_code) |
| { |
| *sp++ = table[1][code]; |
| if (code == table[0][code]) |
| return (255); |
| |
| code = table[0][code]; |
| } |
| |
| *sp++ = firstcode = table[1][code]; |
| code = max_code; |
| |
| if (code < 4096) |
| { |
| table[0][code] = oldcode; |
| table[1][code] = firstcode; |
| max_code ++; |
| |
| if (max_code >= max_code_size && max_code_size < 4096) |
| { |
| max_code_size *= 2; |
| code_size ++; |
| } |
| } |
| |
| oldcode = incode; |
| |
| if (sp > stack) |
| return (*--sp); |
| } |
| |
| return (code); |
| } |
| |
| |
| /* |
| * End of "$Id: image-gif.c 7720 2008-07-11 22:46:21Z mike $". |
| */ |