/*
** our include
*/

#include "getbmp.h"

/*
** easy access of a char-array as a "structure" (IJG sources v5.0b)
*/

#define UCH(x) \
  ((int) (x))

#define GET_2B(array,offset) \
  ((unsigned int) UCH(array[offset]) + \
   (((unsigned int) UCH(array[offset+1])) << 8))

#define GET_4B(array,offset) \
  ((ULONG) UCH(array[offset]) + \
   (((ULONG) UCH(array[offset+1])) << 8) + \
   (((ULONG) UCH(array[offset+2])) << 16) + \
   (((ULONG) UCH(array[offset+3])) << 24))

/*
** get dt attribute(s)
*/

ULONG set_dt_attrs(Object *obj, ULONG data, ...)
{
  return (SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)&data));
}

/*
** get dt attribute(s)
*/

ULONG get_dt_attrs(Object *obj, ULONG data, ...)
{
  return (GetDTAttrsA(obj,(struct TagItem *)&data));
}

/*
** expand every bit to a byte
*/

VOID convert_1bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  UBYTE *p=outbuf,c;
  int i,j;

  for(i=width/8; i>0; i--)
  {
    for(c=*pixelbuf++,j=7; j>=0; j--)
    {
      *p++ = ((c & 0x80) ? 1 : 0);
      c <<= 1;
    }
  }
  if ((i=width%8))
  {
    for(c=*pixelbuf,j=7; i>0; j--,i--)
    {
      *p++ = ((c & 0x80) ? 1 : 0);
      c <<= 1;
    }
  }
}

/*
** expand every 4 bits to a byte
*/

VOID convert_4bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  UBYTE *p=outbuf,c;
  int i;

  for(i=width/2; i>0; i--)
  {
    c = *pixelbuf++;
    *p++ = (c >> 4); /* ((c & 0xf0) >> 4); */
    *p++ = (c & 0xf);
  }
  if (width & 1)
  {
    c = *pixelbuf; *p = (c >> 4); /* ((c & 0xf0) >> 4); */
  }
}

/*
** perform a simple copy (outbuf is WritePixelLine8() aware..)
*/

VOID convert_8bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  CopyMem(pixelbuf,outbuf,width);
}

/*
** create the bitmap
*/

int decode_bmp_picture(Object *obj, APTR pool, BPTR fh, struct BitMapHeader *bmhd, ULONG title)
{
  register VOID (*convert)(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width) REG(a3);
  struct BitMap *tbm,*bm;
  struct RastPort trp,rp;
  UBYTE *albuff,*buffer,*p;
  ULONG modeid,padwidth,width,height,depth,large;
  int line,index,success=FALSE;

  width = bmhd->bmh_Width; height = bmhd->bmh_Height; depth = bmhd->bmh_Depth;

  if ((bm=AllocBitMap(width,height,depth,BMF_CLEAR|BMF_DISPLAYABLE,NULL)) != NULL)
  {
    InitRastPort(&rp); rp.BitMap = bm;

    if ((tbm=AllocBitMap(width,1,depth,BMF_CLEAR,NULL)) != NULL)
    {
      InitRastPort(&trp); trp.BitMap = tbm;

      if ((albuff=AllocPooled(pool,((width+15)&~15))) != NULL)
      {
        padwidth = ((((width * depth) + 31) & ~31) / 8);

        large = padwidth * height;

        if ((buffer=AllocPooled(pool,large)) != NULL)
        {
          if (Read(fh,buffer,large) != large)
          {
            buffer=NULL;
          }
        }
        else
        {
          buffer=AllocPooled(pool,padwidth); large=0;
        }

        if (buffer != NULL)
        {
          convert = convert_1bit;
          if (depth != 1)
          {
            convert = convert_4bit;
            if (depth != 4)
              convert = convert_8bit;
          }

          for (line=(height-1),index=0; line>=0; index++,line--)
          {
            if (large)
              p=&buffer[index*padwidth];
            else
              if (Read(fh,(p=buffer),padwidth) != padwidth)
                goto error;
            (*convert)(p,albuff,width);
            WritePixelLine8(&rp,0,line,width,albuff,&trp);
          }

          modeid = LORES_KEY;
          if ((UWORD)width >= 640)
            modeid = HIRES;
          if ((UWORD)height >= 400)
            modeid |= LACE;

          set_dt_attrs(obj,
                       DTA_ObjName,      title,
                       DTA_NominalHoriz, width,
                       DTA_NominalVert,  height,
                       PDTA_BitMap,      bm,
                       PDTA_ModeID,      modeid,
                       TAG_DONE);

          success = TRUE;

        }

      }
error:
      FreeBitMap(tbm);
    }

    if (!success)
      FreeBitMap(bm);
  }

  return success;
}

/*
** extract all required information from the bmp header
*/

int read_bmp_header(Object *obj, APTR pool, BPTR fh, struct BitMapHeader *bmhd)
{
  UBYTE bmpfileheader[14+64],*coltab,*cmap;

  #define bmpinfoheader (bmpfileheader+14)

  LONG *cregs;
  LONG biCompression;
  LONG biClrUsed;
  LONG biPlanes;
  LONG bfOffBits;
  LONG headerSize;
  LONG bPad;
  LONG cmapentrysize;
  LONG coltabsize;
  int i;

  if (Read(fh,bmpfileheader,18) != 18)
    return FALSE;

  if (((UWORD)GET_2B(bmpfileheader,0)) != 0x4D42)
    return FALSE;

  bfOffBits  = (LONG) GET_4B(bmpfileheader,10);
  headerSize = (LONG) GET_4B(bmpinfoheader, 0);

  if (headerSize < 12 || headerSize > 64)
    return FALSE;

  if (Read(fh,bmpinfoheader+4,headerSize-4) != (headerSize-4))
    return FALSE;

  switch(headerSize)
  {
    case 12: /* OS/2 1.x */
      bmhd->bmh_Width  = (WORD) GET_2B(bmpinfoheader, 4);
      bmhd->bmh_Height = (WORD) GET_2B(bmpinfoheader, 6);
      biPlanes         =        GET_2B(bmpinfoheader, 8);
      bmhd->bmh_Depth  = (WORD) GET_2B(bmpinfoheader,10);
      biCompression    =        0;
      biClrUsed        =        0;
      switch (bmhd->bmh_Depth)
      {
        case 1:
        case 4:
        case 8:
          cmapentrysize = 3;
          break;
        case 24:
          cmapentrysize = 0;
          break;
        default:
          cmapentrysize = 0;
          break;
      }
      break;
    case 40: /* Windows 3.x or OS/2 2.x */
    case 64:
      bmhd->bmh_Width  = (WORD) GET_4B(bmpinfoheader, 4);
      bmhd->bmh_Height = (WORD) GET_4B(bmpinfoheader, 8);
      biPlanes         =        GET_2B(bmpinfoheader,12);
      bmhd->bmh_Depth  = (WORD) GET_2B(bmpinfoheader,14);
      biCompression    =        GET_4B(bmpinfoheader,16);
      biClrUsed        =        GET_4B(bmpinfoheader,32);
      switch (bmhd->bmh_Depth)
      {
        case 1:
        case 4:
        case 8:
          cmapentrysize = 4;
          break;
        case 24:
          cmapentrysize = 0;
          break;
        default:
          cmapentrysize = 0;
          break;
      }
      break;
    default:
      cmapentrysize = 0;
      break;
  }

  bmhd->bmh_PageWidth  = bmhd->bmh_Width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height;

  if (biPlanes != 1 || biCompression != 0 || cmapentrysize == 0)
    return FALSE;

  if (biClrUsed <= 0)
    biClrUsed = 1L << bmhd->bmh_Depth;

  if (biClrUsed > 256)
    return FALSE;

  (void)set_dt_attrs(obj, PDTA_NumColors,(ULONG)biClrUsed, TAG_DONE);

  if ((get_dt_attrs(obj, PDTA_ColorRegisters,(ULONG)&cmap,PDTA_CRegs,(ULONG)&cregs, TAG_DONE)) != 2)
    return FALSE;

  coltabsize = biClrUsed * cmapentrysize;

  if ((coltab=AllocPooled(pool,coltabsize)) == NULL)
    return FALSE;

  if (Read(fh,coltab,coltabsize) != coltabsize)
    return FALSE;

  /*
  ** do not use "struct ColorRegister" for cmap since with the amiga port
  ** of gcc a sizeof() for this structure is four and not three.. oh well
  */

  for(i=0; i<biClrUsed; cmap+=3,cregs+=3,coltab+=cmapentrysize,i++)
  {
    UBYTE r,g,b;

    r = coltab[2];
    cmap[0] = r; cregs[0] = r << 24;
    g = coltab[1];
    cmap[1] = g; cregs[1] = g << 24;
    b = coltab[0];
    cmap[2] = b; cregs[2] = b << 24;
  }

  bPad = bfOffBits - (14 + headerSize + coltabsize);

  for(;bPad>0;)
  {
    LONG size = (bPad < sizeof(bmpfileheader) ? bPad : sizeof(bmpfileheader));
    if (Read(fh,&bmpfileheader[0],size) != size)
      break;
    bPad -= size;
  }

  return (bPad == 0 ? TRUE : FALSE);
}

/*
** load the picture
*/

int GetBMP(Class *cl, Object *obj, struct TagItem *attrs)
{
  struct BitMapHeader *bmhd;
  LONG ioerr;
  APTR pool;
  BPTR fh;
  int success=FALSE;

  if ((pool=CreatePool(MEMF_ANY,1024,1024)) != NULL)
  {
    if ((get_dt_attrs(obj, DTA_Handle,&fh,PDTA_BitMapHeader,&bmhd, TAG_DONE) == 2) && fh)
      for(;;)
      {
        ioerr=ERROR_OBJECT_WRONG_TYPE;
        if ((success=read_bmp_header(obj,pool,fh,bmhd)) != FALSE)
        {
          if ((success=decode_bmp_picture(obj,pool,fh,bmhd,GetTagData(DTA_Name,NULL,attrs))) != FALSE)
            break;
          ioerr=ERROR_NO_FREE_STORE;
        }
        SetIoErr(ioerr); break;
      }
    DeletePool(pool);
  }
  return success;
}
