/* This code is ripped from Autotrace-0.29. Small modifications by pts. */

/* input-bmp.ci: reads any bitmap I could get for testing */

#ifdef __GNUC__
#ifndef __clang__
#pragma implementation
#endif
#endif

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

/* #include "types.h" */
#include "at_bitmap.h"
/* #include "message.h" */
/* #include "xstd.h" */
/* #include "input-bmp.h" */

#define MAXCOLORS       256
/* #define Image                long */

#define BitSet(byte, bit)  (((byte) & (bit)) == (bit))

#define ReadOK(file,buffer,len)  (fread(buffer, len, 1, file) != 0)

struct Bitmap_File_Head_Struct
{
 char            zzMagic[2];  /* 00 "BM" */
 unsigned long   bfSize;      /* 02 */
 unsigned short  zzHotX;      /* 06 */
 unsigned short  zzHotY;      /* 08 */
 unsigned long   bfOffs;      /* 0A */
 unsigned long   biSize;      /* 0E */
} Bitmap_File_Head;

struct Bitmap_Head_Struct
{
 unsigned long   biWidth;     /* 12 */
 unsigned long   biHeight;    /* 16 */
 unsigned short  biPlanes;    /* 1A */
 unsigned short  biBitCnt;    /* 1C */
 unsigned long   biCompr;     /* 1E */
 unsigned long   biSizeIm;    /* 22 */
 unsigned long   biXPels;     /* 26 */
 unsigned long   biYPels;     /* 2A */
 unsigned long   biClrUsed;   /* 2E */
 unsigned long   biClrImp;    /* 32 */
                       /* 36 */
} Bitmap_Head;

static long        ToL           (unsigned char *);
static short       ToS           (unsigned char *);
static int         ReadColorMap  (FILE *,
                                  unsigned char[256][3],
                                  int,
                                  int,
                                  int *);
static unsigned char        *ReadImage     (FILE *,
                                  int,
                                  int,
                                  unsigned char[256][3],
                                  int,
                                  int,
                                  int,
                                  int);

#if PTS_SAM2P
bitmap_type bmp_load_image (FILE* filename)
#else
bitmap_type bmp_load_image (at_string filename)
#endif
{
 FILE *fd;
 unsigned char buffer[64];
 int ColormapSize, rowbytes, Maps=0, Grey;
 unsigned char ColorMap[256][3];
 bitmap_type image;

 #if PTS_SAM2P /**** pts ****/
   fd=filename;
 #else
   fd = fopen (filename, "rb");

   if (!fd)
       FATAL1 ("Can't open \"%s\"\n", filename);
 #endif

 /* It is a File. Now is it a Bitmap? Read the shortest possible header.*/

 if (!ReadOK(fd, buffer, 18) || (strncmp((const char *)buffer,"BM",2)))
 #if PTS_SAM2P /**** pts ****/
     FATALP ("BMP: Not a valid BMP file");
 #else
     FATAL ("Not a valid BMP file %s\n");
 #endif

 /* bring them to the right byteorder. Not too nice, but it should work */

 Bitmap_File_Head.bfSize    = ToL (&buffer[0x02]);
 Bitmap_File_Head.zzHotX    = ToS (&buffer[0x06]);
 Bitmap_File_Head.zzHotY    = ToS (&buffer[0x08]);
 Bitmap_File_Head.bfOffs    = ToL (&buffer[0x0a]);
 Bitmap_File_Head.biSize    = ToL (&buffer[0x0e]);

 /* What kind of bitmap is it? */

 if (Bitmap_File_Head.biSize == 12) /* OS/2 1.x ? */
   {
     if (!ReadOK (fd, buffer, 8))
         FATALP ("BMP: Error reading BMP file header #1");

     Bitmap_Head.biWidth    = ToS (&buffer[0x00]);   /* 12 */
     Bitmap_Head.biHeight   = ToS (&buffer[0x02]);   /* 14 */
     Bitmap_Head.biPlanes   = ToS (&buffer[0x04]);   /* 16 */
     Bitmap_Head.biBitCnt   = ToS (&buffer[0x06]);   /* 18 */
         Bitmap_Head.biCompr = 0;
         Bitmap_Head.biSizeIm = 0;
         Bitmap_Head.biXPels = Bitmap_Head.biYPels = 0;
         Bitmap_Head.biClrUsed = 0;
     Maps = 3;
   }
  else if (Bitmap_File_Head.biSize == 40) /* Windows 3.x */
   {
     if (!ReadOK (fd, buffer, Bitmap_File_Head.biSize - 4))
         FATALP ("BMP: Error reading BMP file header #2");

     Bitmap_Head.biWidth   =ToL (&buffer[0x00]);       /* 12 */
     Bitmap_Head.biHeight  =ToL (&buffer[0x04]);       /* 16 */
     Bitmap_Head.biPlanes  =ToS (&buffer[0x08]);       /* 1A */
     Bitmap_Head.biBitCnt  =ToS (&buffer[0x0A]);       /* 1C */
     Bitmap_Head.biCompr   =ToL (&buffer[0x0C]);       /* 1E */
     Bitmap_Head.biSizeIm  =ToL (&buffer[0x10]);       /* 22 */
     Bitmap_Head.biXPels   =ToL (&buffer[0x14]);       /* 26 */
     Bitmap_Head.biYPels   =ToL (&buffer[0x18]);       /* 2A */
     Bitmap_Head.biClrUsed =ToL (&buffer[0x1C]);       /* 2E */
     Bitmap_Head.biClrImp  =ToL (&buffer[0x20]);       /* 32 */
                                                       /* 36 */
     Maps = 4;
   }
 else if (Bitmap_File_Head.biSize <= 64) /* Probably OS/2 2.x */
   {
     if (!ReadOK (fd, buffer, Bitmap_File_Head.biSize - 4))
         FATALP ("BMP: Error reading BMP file header #3");

     Bitmap_Head.biWidth   =ToL (&buffer[0x00]);       /* 12 */
     Bitmap_Head.biHeight  =ToL (&buffer[0x04]);       /* 16 */
     Bitmap_Head.biPlanes  =ToS (&buffer[0x08]);       /* 1A */
     Bitmap_Head.biBitCnt  =ToS (&buffer[0x0A]);       /* 1C */
     Bitmap_Head.biCompr   =ToL (&buffer[0x0C]);       /* 1E */
     Bitmap_Head.biSizeIm  =ToL (&buffer[0x10]);       /* 22 */
     Bitmap_Head.biXPels   =ToL (&buffer[0x14]);       /* 26 */
     Bitmap_Head.biYPels   =ToL (&buffer[0x18]);       /* 2A */
     Bitmap_Head.biClrUsed =ToL (&buffer[0x1C]);       /* 2E */
     Bitmap_Head.biClrImp  =ToL (&buffer[0x20]);       /* 32 */
                                                       /* 36 */
     Maps = 3;
   }
 else
     FATALP ("BMP: Error reading BMP file header #4");

 /* Valid options 1, 4, 8, 16, 24, 32 */
 /* 16 is awful, we should probably shoot whoever invented it */

 /* There should be some colors used! */

 ColormapSize = (Bitmap_File_Head.bfOffs - Bitmap_File_Head.biSize - 14) / Maps;

 if ((Bitmap_Head.biClrUsed == 0) && (Bitmap_Head.biBitCnt <= 8))
   Bitmap_Head.biClrUsed = ColormapSize;

 /* Sanity checks */

 if (Bitmap_Head.biHeight == 0 || Bitmap_Head.biWidth == 0)
     FATALP ("BMP: Error reading BMP file header #5");

 if (Bitmap_Head.biPlanes != 1)
     FATALP ("BMP: Error reading BMP file header #6");

 if (ColormapSize > 256 || Bitmap_Head.biClrUsed > 256)
     FATALP ("BMP: Error reading BMP file header #7");

 /* Windows and OS/2 declare filler so that rows are a multiple of
  * word length (32 bits == 4 bytes)
  */

 rowbytes= ( (Bitmap_Head.biWidth * Bitmap_Head.biBitCnt - 1) / 32) * 4 + 4;

#ifdef DEBUG
 printf("\nSize: %u, Colors: %u, Bits: %u, Width: %u, Height: %u, Comp: %u, Zeile: %u\n",
         Bitmap_File_Head.bfSize,Bitmap_Head.biClrUsed,Bitmap_Head.biBitCnt,Bitmap_Head.biWidth,
         Bitmap_Head.biHeight, Bitmap_Head.biCompr, rowbytes);
#endif

 /* Get the Colormap */

 if (ReadColorMap (fd, ColorMap, ColormapSize, Maps, &Grey) == -1)
     FATALP ("BMP: Cannot read the colormap");

#ifdef DEBUG
 printf("Colormap read\n");
#endif

 /* Get the Image and return the ID or -1 on error*/
 image.bitmap = ReadImage (fd,
                       Bitmap_Head.biWidth,
                       Bitmap_Head.biHeight,
                       ColorMap,
                       Bitmap_Head.biBitCnt,
                       Bitmap_Head.biCompr,
                       rowbytes,
                       Grey);
 BITMAP_WIDTH (image) = (at_dimen_t) Bitmap_Head.biWidth;
 BITMAP_HEIGHT (image) = (at_dimen_t) Bitmap_Head.biHeight;
 BITMAP_PLANES (image) = Grey ? 1 : 3;

 return (image);
}

static int
ReadColorMap (FILE   *fd,
             unsigned char  buffer[256][3],
             int    number,
             int    size,
             int   *grey)
{
 int i;
 unsigned char rgb[4];

 *grey=(number>2);
 for (i = 0; i < number ; i++)
   {
     if (!ReadOK (fd, rgb, size))
         FATALP ("BMP: Bad colormap");

     /* Bitmap save the colors in another order! But change only once! */

     buffer[i][0] = rgb[2];
     buffer[i][1] = rgb[1];
     buffer[i][2] = rgb[0];
     *grey = ((*grey) && (rgb[0]==rgb[1]) && (rgb[1]==rgb[2]));
   }
 return 0;
}

static unsigned char*
ReadImage (FILE   *fd,
          int    width,
          int    height,
          unsigned char  cmap[256][3],
          int    bpp,
          int    compression,
          int    rowbytes,
          int    grey)
{
 unsigned char v,howmuch;
 int xpos = 0, ypos = 0;
 unsigned char *image;
 unsigned char *temp, *buffer;
 long rowstride, channels;
 unsigned short rgb;
 int i, j;

 if (bpp >= 16) /* color image */
   {
     XMALLOCT (image, unsigned char*, width * height * 3 * sizeof (unsigned char));
     channels = 3;
   }
 else if (grey) /* grey image */
   {
     XMALLOCT (image, unsigned char*, width * height * 1 * sizeof (unsigned char));
         channels = 1;
       }
 else /* indexed image */
       {
     XMALLOCT (image, unsigned char*, width * height * 1 * sizeof (unsigned char));
         channels = 1;
       }

 XMALLOCT (buffer, unsigned char*, rowbytes);
 rowstride = width * channels;

 ypos = height - 1;  /* Bitmaps begin in the lower left corner */

 switch (bpp) {

 case 32:
   {
     while (ReadOK (fd, buffer, rowbytes))
       {
         temp = image + (ypos * rowstride);
         for (xpos= 0; xpos < width; ++xpos)
           {
              *(temp++)= buffer[xpos * 4 + 2];
              *(temp++)= buffer[xpos * 4 + 1];
              *(temp++)= buffer[xpos * 4];
           }
         --ypos; /* next line */
       }
   }
       break;

 case 24:
   {
     while (ReadOK (fd, buffer, rowbytes))
       {
         temp = image + (ypos * rowstride);
         for (xpos= 0; xpos < width; ++xpos)
           {
              *(temp++)= buffer[xpos * 3 + 2];
              *(temp++)= buffer[xpos * 3 + 1];
              *(temp++)= buffer[xpos * 3];
           }
         --ypos; /* next line */
       }
       }
   break;

 case 16:
   {
     while (ReadOK (fd, buffer, rowbytes))
       {
         temp = image + (ypos * rowstride);
         for (xpos= 0; xpos < width; ++xpos)
           {
              rgb= ToS(&buffer[xpos * 2]);
              *(temp++)= (unsigned char)(((rgb >> 10) & 0x1f) * 8);
              *(temp++)= (unsigned char)(((rgb >> 5)  & 0x1f) * 8);
              *(temp++)= (unsigned char)(((rgb)       & 0x1f) * 8);
           }
         --ypos; /* next line */
       }
   }
       break;

 case 8:
 case 4:
 case 1:
   {
     if (compression == 0)
         {
           while (ReadOK (fd, &v, 1))
             {
               for (i = 1; (i <= (8 / bpp)) && (xpos < width); i++, xpos++)
                 {
                   temp = (unsigned char*) (image + (ypos * rowstride) + (xpos * channels));
                   *temp= (unsigned char)(( v & ( ((1<<bpp)-1) << (8-(i*bpp)) ) ) >> (8-(i*bpp)));
                 }
               if (xpos == width)
                 {
                   (void) ReadOK (fd, buffer, rowbytes - 1 -
                                  (width * bpp - 1) / 8);
                   ypos--;
                   xpos = 0;

                 }
               if (ypos < 0)
                 break;
             }
           break;
         }
       else
         {
           while (ypos >= 0 && xpos <= width)
             {
               (void) ReadOK (fd, buffer, 2);
               if ((unsigned char) buffer[0] != 0)
                 /* Count + Color - record */
                 {
                   for (j = 0; ((unsigned char) j < (unsigned char) buffer[0]) && (xpos < width);)
                     {
#ifdef DEBUG2
                       printf("%u %u | ",xpos,width);
#endif
                       for (i = 1;
                            ((i <= (8 / bpp)) &&
                             (xpos < width) &&
                             ((unsigned char) j < (unsigned char) buffer[0]));
                            i++, xpos++, j++)
                         {
                           temp = image + (ypos * rowstride) + (xpos * channels);
                           *temp = (unsigned char) ((buffer[1] & (((1<<bpp)-1) << (8 - (i * bpp)))) >> (8 - (i * bpp)));
                         }
                     }
                 }
               if (((unsigned char) buffer[0] == 0) && ((unsigned char) buffer[1] > 2))
                 /* uncompressed record */
                 {
                   howmuch = buffer[1];
                   for (j = 0; j < howmuch; j += (8 / bpp))
                     {
                       (void) ReadOK (fd, &v, 1);
                       i = 1;
                       while ((i <= (8 / bpp)) && (xpos < width))
                         {
                           temp = image + (ypos * rowstride) + (xpos * channels);
                           *temp = (unsigned char) ((v & (((1<<bpp)-1) << (8-(i*bpp)))) >> (8-(i*bpp)));
                           i++;
                           xpos++;
                         }
                     }

                   if ((howmuch % 2) && (bpp==4))
                     howmuch++;

                   if ((howmuch / (8 / bpp)) % 2)
                     (void) ReadOK (fd, &v, 1);
                   /*if odd(x div (8 div bpp )) then blockread(f,z^,1);*/
                 }
               if (((unsigned char) buffer[0] == 0) && ((unsigned char) buffer[1]==0))
                 /* Line end */
                 {
                   ypos--;
                   xpos = 0;
                 }
               if (((unsigned char) buffer[0]==0) && ((unsigned char) buffer[1]==1))
                 /* Bitmap end */
                 {
                   break;
                 }
               if (((unsigned char) buffer[0]==0) && ((unsigned char) buffer[1]==2))
                 /* Deltarecord */
                 {
                   (void) ReadOK (fd, buffer, 2);
                   xpos += (unsigned char) buffer[0];
                   ypos -= (unsigned char) buffer[1];
                 }
             }
           break;
         }
   }
   break;
 default:
   /* This is very bad, we should not be here */
       ;
 }

 /* fclose (fd); */
 if (bpp <= 8)
   {
     unsigned char *temp2, *temp3;
     unsigned char index;
     temp2 = temp = image;
     XMALLOCT (image, unsigned char*, width * height * 3 * sizeof (unsigned char));
     temp3 = image;
     for (ypos = 0; ypos < height; ypos++)
       {
         for (xpos = 0; xpos < width; xpos++)
            {
              index = *temp2++;
              *temp3++ = cmap[index][0];
                          if (!grey)
                            {
                  *temp3++ = cmap[index][1];
                  *temp3++ = cmap[index][2];
                            }
          }
       }
     XFREE (temp);
 }

 XFREE (buffer);
 return image;
}

#if 0 /**** pts ****/
FILE  *errorfile;
char *prog_name = "bmp";
char *filename;
int   interactive_bmp;
#endif

static long
ToL (unsigned char *puffer)
{
 return (puffer[0] | puffer[1]<<8 | puffer[2]<<16 | puffer[3]<<24);
}

static short
ToS (unsigned char *puffer)
{
 return ((short)(puffer[0] | puffer[1]<<8));
}