/*
 * "$Id: image-bmp.c 8938 2009-12-18 23:52:01Z mike $"
 *
 *   BMP image routines for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 2007-2009 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:
 *
 *   _cupsImageReadBMP() - Read a BMP image file.
 *   read_word()         - Read a 16-bit unsigned integer.
 *   read_dword()        - Read a 32-bit unsigned integer.
 *   read_long()         - Read a 32-bit signed integer.
 */

/*
 * Include necessary headers...
 */

#include "image-private.h"


/*
 * Constants for the bitmap compression...
 */

#ifndef WIN32
#  define BI_RGB	0		/* No compression - straight BGR data */
#  define BI_RLE8	1		/* 8-bit run-length compression */
#  define BI_RLE4	2		/* 4-bit run-length compression */
#  define BI_BITFIELDS	3		/* RGB bitmap with RGB masks */
#endif

/*
 * Local functions...
 */

static unsigned short	read_word(FILE *fp);
static unsigned int	read_dword(FILE *fp);
static int		read_long(FILE *fp);


/*
 * '_cupsImageReadBMP()' - Read a BMP image file.
 */

int					/* O - Read status */
_cupsImageReadBMP(
    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 */
{
  int		offset,			/* Offset to bitmap data */
		info_size,		/* Size of info header */
		planes,			/* Number of planes (always 1) */
		depth,			/* Depth of image (bits) */
		compression,		/* Type of compression */
		image_size,		/* Size of image in bytes */
		colors_used,		/* Number of colors used */
		colors_important,	/* Number of important colors */
		bpp,			/* Bytes per pixel */
		x, y,			/* Looping vars */
		color,			/* Color of RLE pixel */
		count,			/* Number of times to repeat */
		temp,			/* Temporary color */
		align;			/* Alignment bytes */
  cups_ib_t	bit,			/* Bit in image */
		byte;			/* Byte in image */
  cups_ib_t	*in,			/* Input pixels */
		*out,			/* Output pixels */
		*ptr;			/* Pointer into pixels */
  cups_ib_t	colormap[256][4];	/* Colormap */


  (void)secondary;

 /*
  * Get the header...
  */

  getc(fp);		/* Skip "BM" sync chars */
  getc(fp);
  read_dword(fp);	/* Skip size */
  read_word(fp);	/* Skip reserved stuff */
  read_word(fp);
  offset = read_dword(fp);

  fprintf(stderr, "DEBUG: offset = %d\n", offset);

  if (offset < 0)
  {
    fprintf(stderr, "DEBUG: Bad BMP offset %d\n", offset);
    fclose(fp);
    return (1);
  }

 /*
  * Then the bitmap information...
  */

  info_size        = read_dword(fp);
  img->xsize       = read_long(fp);
  img->ysize       = read_long(fp);
  planes           = read_word(fp);
  depth            = read_word(fp);
  compression      = read_dword(fp);
  image_size       = read_dword(fp);
  img->xppi        = read_long(fp) * 0.0254 + 0.5;
  img->yppi        = read_long(fp) * 0.0254 + 0.5;
  colors_used      = read_dword(fp);
  colors_important = read_dword(fp);

  if (img->xsize == 0 || img->xsize > CUPS_IMAGE_MAX_WIDTH ||
      img->ysize == 0 || img->ysize > CUPS_IMAGE_MAX_HEIGHT ||
      (depth != 1 && depth != 4 && depth != 8 && depth != 24))
  {
    fprintf(stderr, "DEBUG: Bad BMP dimensions %ux%ux%d\n",
            img->xsize, img->ysize, depth);
    fclose(fp);
    return (1);
  }

  if (colors_used < 0 || colors_used > 256)
  {
    fprintf(stderr, "DEBUG: Bad BMP colormap size %d\n", colors_used);
    fclose(fp);
    return (1);
  }

  if (img->xppi == 0 || img->yppi == 0)
  {
    fprintf(stderr, "DEBUG: Bad BMP resolution %dx%d PPI.\n",
            img->xppi, img->yppi);
    img->xppi = img->yppi = 128;
  }

 /*
  * Make sure the resolution info is valid...
  */

  fprintf(stderr, "info_size = %d, xsize = %d, ysize = %d, planes = %d, depth = %d\n",
          info_size, img->xsize, img->ysize, planes, depth);
  fprintf(stderr, "compression = %d, image_size = %d, xppi = %d, yppi = %d\n",
          compression, image_size, img->xppi, img->yppi);
  fprintf(stderr, "colors_used = %d, colors_important = %d\n", colors_used,
          colors_important);

  if (info_size > 40)
    for (info_size -= 40; info_size > 0; info_size --)
      getc(fp);

 /*
  * Get colormap...
  */

  if (colors_used == 0 && depth <= 8)
    colors_used = 1 << depth;

  if (colors_used > 0)
    fread(colormap, colors_used, 4, fp);
  else
    memset(colormap, 0, sizeof(colormap));

 /*
  * Setup image and buffers...
  */

  img->colorspace = (primary == CUPS_IMAGE_RGB_CMYK) ? CUPS_IMAGE_RGB : primary;

  cupsImageSetMaxTiles(img, 0);

  bpp = cupsImageGetDepth(img);

  if ((in = malloc(img->xsize * 3)) == NULL)
  {
    fputs("DEBUG: Unable to allocate memory!\n", stderr);
    fclose(fp);
    return (1);
  }

  if ((out = malloc(img->xsize * bpp)) == NULL)
  {
    fputs("DEBUG: Unable to allocate memory!\n", stderr);
    free(in);
    fclose(fp);
    return (1);
  }

 /*
  * Read the image data...
  */

  color = 0;
  count = 0;
  align = 0;

  for (y = img->ysize - 1; y >= 0; y --)
  {
    ptr = in;

    switch (depth)
    {
      case 1 : /* Bitmap */
          for (x = img->xsize, bit = 128, byte = 0; x > 0; x --)
	  {
	    if (bit == 128)
	      byte = getc(fp);

	    if (byte & bit)
	    {
	      *ptr++ = colormap[1][2];
	      *ptr++ = colormap[1][1];
	      *ptr++ = colormap[1][0];
	    }
	    else
	    {
	      *ptr++ = colormap[0][2];
	      *ptr++ = colormap[0][1];
	      *ptr++ = colormap[0][0];
	    }

	    if (bit > 1)
	      bit >>= 1;
	    else
	      bit = 128;
	  }

         /*
	  * Read remaining bytes to align to 32 bits...
	  */

	  for (temp = (img->xsize + 7) / 8; temp & 3; temp ++)
	    getc(fp);
          break;

      case 4 : /* 16-color */
          for (x = img->xsize, bit = 0xf0, temp = 0; x > 0; x --)
	  {
	   /*
	    * Get a new count as needed...
	    */

            if (compression != BI_RLE4 && count == 0)
	    {
	      count = 2;
	      color = -1;
            }

	    if (count == 0)
	    {
	      while (align > 0)
	      {
	        align --;
		getc(fp);
              }

	      if ((count = getc(fp)) == 0)
	      {
		if ((count = getc(fp)) == 0)
		{
		 /*
		  * End of line...
		  */

                  x ++;
		  continue;
		}
		else if (count == 1)
		{
		 /*
		  * End of image...
		  */

		  break;
		}
		else if (count == 2)
		{
		 /*
		  * Delta...
		  */

		  count = getc(fp) * getc(fp) * img->xsize;
		  color = 0;
		}
		else
		{
		 /*
		  * Absolute...
		  */

		  color = -1;
		  align = ((4 - (count & 3)) / 2) & 1;
		}
	      }
	      else
	        color = getc(fp);
            }

           /*
	    * Get a new color as needed...
	    */

	    count --;

            if (bit == 0xf0)
	    {
              if (color < 0)
		temp = getc(fp);
	      else
		temp = color;

             /*
	      * Copy the color value...
	      */

	      *ptr++ = colormap[temp >> 4][2];
	      *ptr++ = colormap[temp >> 4][1];
	      *ptr++ = colormap[temp >> 4][0];
	      bit    = 0x0f;
            }
	    else
	    {
             /*
	      * Copy the color value...
	      */

	      *ptr++ = colormap[temp & 15][2];
	      *ptr++ = colormap[temp & 15][1];
	      *ptr++ = colormap[temp & 15][0];
	      bit    = 0xf0;
	    }
	  }
          break;

      case 8 : /* 256-color */
          for (x = img->xsize; x > 0; x --)
	  {
	   /*
	    * Get a new count as needed...
	    */

            if (compression != BI_RLE8)
	    {
	      count = 1;
	      color = -1;
            }

	    if (count == 0)
	    {
	      while (align > 0)
	      {
	        align --;
		getc(fp);
              }

	      if ((count = getc(fp)) == 0)
	      {
		if ((count = getc(fp)) == 0)
		{
		 /*
		  * End of line...
		  */

                  x ++;
		  continue;
		}
		else if (count == 1)
		{
		 /*
		  * End of image...
		  */

		  break;
		}
		else if (count == 2)
		{
		 /*
		  * Delta...
		  */

		  count = getc(fp) * getc(fp) * img->xsize;
		  color = 0;
		}
		else
		{
		 /*
		  * Absolute...
		  */

		  color = -1;
		  align = (2 - (count & 1)) & 1;
		}
	      }
	      else
	        color = getc(fp);
            }

           /*
	    * Get a new color as needed...
	    */

            if (color < 0)
	      temp = getc(fp);
	    else
	      temp = color;

            count --;

           /*
	    * Copy the color value...
	    */

	    *ptr++ = colormap[temp][2];
	    *ptr++ = colormap[temp][1];
	    *ptr++ = colormap[temp][0];
	  }
          break;

      case 24 : /* 24-bit RGB */
          for (x = img->xsize; x > 0; x --, ptr += 3)
	  {
	    ptr[2] = getc(fp);
	    ptr[1] = getc(fp);
	    ptr[0] = getc(fp);
	  }

         /*
	  * Read remaining bytes to align to 32 bits...
	  */

	  for (temp = img->xsize * 3; temp & 3; temp ++)
	    getc(fp);
          break;
    }

    if (saturation != 100 || hue != 0)
      cupsImageRGBAdjust(in, img->xsize, saturation, hue);

    switch (img->colorspace)
    {
      default :
	  break;

      case CUPS_IMAGE_WHITE :
	  cupsImageRGBToWhite(in, out, img->xsize);
	  break;

      case CUPS_IMAGE_RGB :
	  cupsImageRGBToRGB(in, out, img->xsize);
	  break;

      case CUPS_IMAGE_BLACK :
	  cupsImageRGBToBlack(in, out, img->xsize);
	  break;

      case CUPS_IMAGE_CMY :
	  cupsImageRGBToCMY(in, out, img->xsize);
	  break;

      case CUPS_IMAGE_CMYK :
	  cupsImageRGBToCMYK(in, out, img->xsize);
	  break;
    }

    if (lut)
      cupsImageLut(out, img->xsize * bpp, lut);

    _cupsImagePutRow(img, 0, y, img->xsize, out);
  }

  fclose(fp);
  free(in);
  free(out);

  return (0);
}


/*
 * 'read_word()' - Read a 16-bit unsigned integer.
 */

static unsigned short     /* O - 16-bit unsigned integer */
read_word(FILE *fp)       /* I - File to read from */
{
  unsigned char b0, b1; /* Bytes from file */

  b0 = getc(fp);
  b1 = getc(fp);

  return ((b1 << 8) | b0);
}


/*
 * 'read_dword()' - Read a 32-bit unsigned integer.
 */

static unsigned int               /* O - 32-bit unsigned integer */
read_dword(FILE *fp)              /* I - File to read from */
{
  unsigned char b0, b1, b2, b3; /* Bytes from file */

  b0 = getc(fp);
  b1 = getc(fp);
  b2 = getc(fp);
  b3 = getc(fp);

  return ((((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}


/*
 * 'read_long()' - Read a 32-bit signed integer.
 */

static int                        /* O - 32-bit signed integer */
read_long(FILE *fp)               /* I - File to read from */
{
  unsigned char b0, b1, b2, b3; /* Bytes from file */

  b0 = getc(fp);
  b1 = getc(fp);
  b2 = getc(fp);
  b3 = getc(fp);

  return ((int)(((((b3 << 8) | b2) << 8) | b1) << 8) | b0);
}


/*
 * End of "$Id: image-bmp.c 8938 2009-12-18 23:52:01Z mike $".
 */
