/*  c.jrdbmp

    BMP decode routines for FYEO2

    by Rick Murray 2004

    Last modified: Friday, 5th March 2004
*/




#include "jinclude.h"
#include "Erreur.h"
#include "kernel.h"




#define	ReadOK(file,buffer,len)	(JFREAD(file,buffer,len) == ((size_t) (len)))



static int             current_row = 0;
static int             file_eof = 0;
static char            eofmsg[256] = "";
static char            *linebuffer = NULL;



static JSAMPARRAY colormap;
#define NUMCOLOURS      3       // # of colours
#define CM_RED          0       // colour component numbers
#define CM_GREEN        1
#define CM_BLUE         2


// the following are defined in jhbmp.c
extern char bmp_header[64];
extern int  bmp_bpp;
extern int  bmp_start;
extern int  bmp_size;
extern int  BMP_PullWord(int);




void load_colourmap (compress_info_ptr cinfo, int cmaplen, JSAMPARRAY cmap)
{
   // Read colour map.
   // colourmap follows header...

   int i, blue, green, red, junk;

   // Get colourmap start (i.e. after header...)
   i = BMP_PullWord(14); // header size
   i += 14;              // plus initial header
   fseek(cinfo->input_file, (long int)i, SEEK_SET);

   // We *SHOULD* really double-check that the BPP isn't 24; but since we know our
   // code does this, we don't need to spend time with the superfluous testing...

   for (i = 0; i < cmaplen; i++)
   {
      // Format is:
      //
      //   Byte  BLUE value
      //   Byte  GREEN value
      //   Byte  RED value
      //   Byte  unused (possibly for transparency (etc) in later BMPs?)
      //
      // Note the colour order is reversed.

      blue  = getc(cinfo->input_file);
      green = getc(cinfo->input_file);
      red   = getc(cinfo->input_file);
      junk  = getc(cinfo->input_file);

      cmap[CM_RED][i]   = red;
      cmap[CM_GREEN][i] = green;
      cmap[CM_BLUE][i]  = blue;
   }

   return;
}



METHODDEF void get_onebpp_row (compress_info_ptr cinfo, JSAMPARRAY pixel_row)
{
   // 1bpp (colour mapped (!))

   register JSAMPROW ptr0, ptr1, ptr2;
   register int  pix;
   register long col, pwid, pixwidth;
   int filepos, rowpos, linepos;

   // work out our actual position (BMPs are stored 'upside down')
   rowpos = (int)((cinfo->image_height - current_row) - 1);
   linepos = (int)(cinfo->image_width >> 3);
   pix = (linepos % 4);
   if (pix != 0)
      linepos = linepos + (4 - pix);
   filepos = (bmp_start + ( rowpos * linepos ));
   if (filepos > file_eof)
   {
      sprintf(eofmsg, "BMP file overrun while seeking %dbpp pixel data (line %d (%d), at offset %d) - has this file been truncated?", 1, current_row, rowpos, filepos);
      ERREXIT(cinfo->emethods, eofmsg);
   }
   fseek(cinfo->input_file, (long int)filepos, SEEK_SET);


   // set pointers
   ptr0 = pixel_row[0];
   ptr1 = pixel_row[1];
   ptr2 = pixel_row[2];

   // read a line
   pwid = ((cinfo->image_width >> 3) + 1); // overshoot, use pixwidth to trap...
   pixwidth = cinfo->image_width;

   for (col = pwid; col > 0; col--)
   {
      register int pixa;
      pix = getc(cinfo->input_file);

      // This is amazingly inefficient! It is 4am, so gimme a break!

      // bit 7
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 7) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 6
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 6) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 5
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 5) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 4
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 4) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 3
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 3) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 2
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 2) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 1
      if ( pixwidth-- > 0)
      {
         pixa = ((pix >> 1) & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }

      // bit 0
      if ( pixwidth-- > 0)
      {
         pixa = ( pix       & 1);
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }
   }

   // increment row count
   current_row++;

   return;
}



METHODDEF void get_fourbpp_row (compress_info_ptr cinfo, JSAMPARRAY pixel_row)
{
   // 4bpp (colour mapped)

   register JSAMPROW ptr0, ptr1, ptr2;
   register int  pix;
   register long col, pwid, pixwidth;
   int filepos, rowpos, linepos;

   // work out our actual position (BMPs are stored 'upside down')
   // There is something 'weird' about the four-bit storage that means some BMPs require the +1
   // while other BMPs will overrun with the +1. Go figure...
   rowpos = (int)((cinfo->image_height - current_row) - 1);
   linepos = (int)((cinfo->image_width >> 1) + 1);
   pix = (linepos % 4);
   if (pix != 0)
      linepos = linepos + (4 - pix);
   filepos = (bmp_start + ( rowpos * linepos ));
   if (filepos > file_eof)
   {
      // try again WITHOUT the correction!
      rowpos = (int)((cinfo->image_height - current_row) - 1);
      linepos = (int)(cinfo->image_width >> 1);
      pix = (linepos % 4);
      if (pix != 0)
         linepos = linepos + (4 - pix);
      filepos = (bmp_start + ( rowpos * linepos ));
      if (filepos > file_eof)
      {
         // if still fails, give up
         sprintf(eofmsg, "BMP file overrun while seeking %dbpp pixel data (line %d (%d), at offset %d) - has this file been truncated?", 4, current_row, rowpos, filepos);
         ERREXIT(cinfo->emethods, eofmsg);
      }
   }
   fseek(cinfo->input_file, (long int)filepos, SEEK_SET);

   // set pointers
   ptr0 = pixel_row[0];
   ptr1 = pixel_row[1];
   ptr2 = pixel_row[2];

   // read a line
   pwid = (cinfo->image_width >> 1) + 1;
   pixwidth = cinfo->image_width;

   for (col = pwid; col > 0; col--)
   {
      register int pixa, pixb;
      pix = getc(cinfo->input_file);

      pixa = ( pix       & 15);
      pixb = ((pix >> 4) & 15);

      // note that the order is reversed...

      if ( pixwidth-- > 0)
      {
         *ptr0++ = colormap[0][pixb];
         *ptr1++ = colormap[1][pixb];
         *ptr2++ = colormap[2][pixb];
      }

      if ( pixwidth-- > 0)
      {
         *ptr0++ = colormap[0][pixa];
         *ptr1++ = colormap[1][pixa];
         *ptr2++ = colormap[2][pixa];
      }
   }

   // increment row count
   current_row++;

   return;
}



METHODDEF void get_eightbpp_row (compress_info_ptr cinfo, JSAMPARRAY pixel_row)
{
   // 8bpp (colour mapped)
   register JSAMPROW ptr0, ptr1, ptr2;
   register int  pix, linepos;
   register long col;
   int filepos, rowpos;

   // work out our actual position (BMPs are stored 'upside down')
   rowpos = (int)((cinfo->image_height - current_row) - 1);
   linepos = (int)cinfo->image_width;
   pix = (linepos % 4);
   if (pix != 0)
      linepos = linepos + (4 - pix);
   filepos = (bmp_start + ( rowpos * linepos ));
   if (filepos > file_eof)
   {
      sprintf(eofmsg, "BMP file overrun while seeking %dbpp pixel data (line %d (%d), at offset %d) - has this file been truncated?", 8, current_row, rowpos, filepos);
      ERREXIT(cinfo->emethods, eofmsg);
   }
   fseek(cinfo->input_file, (long int)filepos, SEEK_SET);

   // set pointers
   ptr0 = pixel_row[0];
   ptr1 = pixel_row[1];
   ptr2 = pixel_row[2];

   // read a line (load a line at a time for a BIG speed boost)
   fread( (void *)linebuffer, 1, (size_t)cinfo->image_width, cinfo->input_file);

   linepos = 0;
   for (col = cinfo->image_width; col > 0; col--)
   {
      pix = linebuffer[linepos++];
      *ptr0++ = colormap[0][pix];
      *ptr1++ = colormap[1][pix];
      *ptr2++ = colormap[2][pix];
   }

   // increment row count
   current_row++;

   return;
}



METHODDEF void get_twofourbpp_row (compress_info_ptr cinfo, JSAMPARRAY pixel_row)
{
   // 24bpp
   register JSAMPROW ptr0, ptr1, ptr2;
   register long col;
   int filepos, rowpos;
   register int linepos;

   // work out our actual position (BMPs are stored 'upside down')
   rowpos = (int)((cinfo->image_height - current_row) - 1);
   linepos = (int)(cinfo->image_width * 3);
   col = (int)(linepos % 4);
   if ((int)col != 0)
      linepos = linepos + (4 - (int)col);
   filepos = (bmp_start + ( rowpos * linepos ));
   if (filepos > file_eof)
   {
      sprintf(eofmsg, "BMP file overrun while seeking %dbpp pixel data (line %d (%d), at offset %d) - has this file been truncated?", 24, current_row, rowpos, filepos);
      ERREXIT(cinfo->emethods, eofmsg);
   }
   fseek(cinfo->input_file, (long int)filepos, SEEK_SET);

   // set pointers
   ptr0 = pixel_row[0];
   ptr1 = pixel_row[1];
   ptr2 = pixel_row[2];

   // read a line (load a line at a time for a BIG speed boost)
   fread( (void *)linebuffer, 1, (size_t)(cinfo->image_width * 3), cinfo->input_file);

   linepos = 0;
   for (col = cinfo->image_width; col > 0; col--)
   {
      *ptr2++ = linebuffer[linepos++];
      *ptr1++ = linebuffer[linepos++];
      *ptr0++ = linebuffer[linepos++];
   }

   // increment row count
   current_row++;

   return;
}



/*
 * Read the file header; return image size and component count.
 */

METHODDEF void input_init (compress_info_ptr cinfo)
{
   unsigned int b, w, h, v, p;

   // as we enter, we're at the start of the file...
   if (getc(cinfo->input_file) != 'B')
      ERREXIT(cinfo->emethods, "Not a BMP file (first byte not 'B')");

   // don't bother re-reading the header, we can get the one we've just read...
   w = BMP_PullWord(18); // width
   h = BMP_PullWord(22); // height
   b =(BMP_PullWord(28) & 0xFFFF); // bpp

   if (w <= 0 || h <= 0 || b <= 0) // error check
     ERREXIT(cinfo->emethods, "Not a BMP file (w/h/bpp < 0)");

   // further validation
   v =(BMP_PullWord(26) & 0xFFFF);
   if (v != 1)
      ERREXIT(cinfo->emethods, "Unsupported BMP type (colour planes not '1')");

   v = BMP_PullWord(30);
   if (v != 0)
      ERREXIT(cinfo->emethods, "Unsupported BMP type (image is compressed)");

   // load palette
   if ( b < 24 )
   {
      p = 1 << b;

      colormap = (*cinfo->emethods->alloc_small_sarray)
                 ((long)p, (long)NUMCOLOURS);

      load_colourmap(cinfo, p, colormap);
   }

   switch (b)
   {
      case  1 : // 1bpp
                cinfo->methods->get_input_row = get_onebpp_row;
                break;

      case  4 : // 4bpp
                cinfo->methods->get_input_row = get_fourbpp_row;
                break;

      case  8 : // 8bpp
                linebuffer = (char *) (*cinfo->emethods->alloc_small)
			              ( (size_t)w); // width for line buffer...
                cinfo->methods->get_input_row = get_eightbpp_row;
                break;

      case 24 : // 24bpp
                linebuffer = (char *) (*cinfo->emethods->alloc_small)
		       	              ( (size_t)(w * 3) ); // width * 3 for line buffer...
                cinfo->methods->get_input_row = get_twofourbpp_row;
                break;

      default : ERREXIT(cinfo->emethods, "Not a BMP file (invalid bpp)");
                break;
   }

   cinfo->input_components = b;
   cinfo->in_color_space = CS_RGB;
   cinfo->image_width = w;
   cinfo->image_height = h;
   current_row = 0;
   cinfo->data_precision = 8; // toujours je dis  huit  pour la prcision...
   TRACEMS3(cinfo->emethods, 1, "%ux%ux%u BMP image", w, h, b);

   // get EOF
   fseek(cinfo->input_file, 0, SEEK_END);
   file_eof = (int)ftell(cinfo->input_file);

   // go to start of image...
   fseek(cinfo->input_file, (long int)bmp_start, SEEK_SET);

   return;
}


/*
 * Finish up at the end of the file.
 */

METHODDEF void input_term (compress_info_ptr cinfo)
{
  /* no work (we let free_all release the workspace) */
}


/*
 * The method selection routine for BMP format input.
 */

GLOBAL void jselrbmp (compress_info_ptr cinfo)
{
   cinfo->methods->input_init = input_init;
   /* cinfo->methods->get_input_row           <-- this set by input_init */
   cinfo->methods->input_term = input_term;

   return;
}

