/*
** our include
*/

#include "getbmp.h"

/*
** functions for asynchronous file I/O
*/

APTR OpenFFR(BPTR);
LONG FFRGetC(APTR);
LONG FFRRead(APTR,APTR,LONG);
VOID CloseFFR(APTR);

/*
** cybergfx defines for picture.datatype V43
*/

#define RECTFMT_RGB  0
#define RECTFMT_LUT8 3

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

#define UCH(x) \
  ((ULONG) (x))

#define GET_2B(array,offset) \
  ((ULONG) UCH(array[offset]) + \
   (((ULONG) 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))

/*
** compression types for BMPs
*/

enum { BI_RGB=0,BI_RLE8=1,BI_RLE4=2 };

/*
** set 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)
{
  ULONG i,j;
  UBYTE c;

  for(i=width/8; i!=0; i--)
    for(c=*pixelbuf++,j=8; j!=0; j--) {
      *outbuf++ = ((c & 0x80) ? 1 : 0); c <<= 1;
    }
  if ((i=width%8)) {
    c = *pixelbuf;
    do {
      *outbuf++ = ((c & 0x80) ? 1 : 0); c <<= 1;
    } while (--i);
  }
}

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

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

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

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

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

/*
** change color order (BGR -> RGB)
*/

VOID convert_24bit(UBYTE *pixelbuf, UBYTE *outbuf, ULONG width)
{
  UBYTE *p,c;

  p = pixelbuf;
  do {
    c = p[0]; p[0] = p[2]; p[2] = c; p += 3;
  } while (p < &pixelbuf[width*3]);
}

/*
** handle 4bit compression
*/

LONG decompress_4bit(APTR handle, UBYTE *buffer, ULONG width, ULONG height)
{
  LONG xpos,a,b,c,i;
  UBYTE *end;

  end = buffer+MUL(width,height); xpos = 0;

  do {

    if ((c=FFRGetC(handle)) < 0) return c;

    if (c == 0) {

      if ((c=FFRGetC(handle)) < 0) return c;

      if (c == 0) {
        buffer += (width-xpos); height--; xpos = 0;
      }
      else if (!--c) {
        break;
      }
      else if (!--c) {
        a = FFRGetC(handle); xpos += a; buffer += a;
        if ((c=FFRGetC(handle)) < 0) return c;
        height -= c; buffer += MUL(width,c);
      }
      else {
        c += 2; i = 0;
        do {
          a = FFRGetC(handle); b = a & 0x0f; a >>= 4;
          *buffer++ = a; *buffer++ = b;
          i += 2;
        } while (i < c);
        if (((c&3)==1) || ((c&3)==2)) FFRGetC(handle);
        xpos += c; if (i>c) --buffer;
      }
    }
    else {
      i = 0; b = FFRGetC(handle); a = b & 0xf; b >>= 4;
      do {
        *buffer++ = b; *buffer++ = a; i += 2;
      } while (i < c);
      xpos += c; if (i>c) --buffer;
    }
  } while (height && buffer < end);

  return 0;
}

/*
** handle 8bit compression
*/

LONG decompress_8bit(APTR handle, UBYTE *buffer, ULONG width, ULONG height)
{
  LONG xpos,a,c,i;
  UBYTE *end;

  end = buffer+MUL(width,height); xpos = 0;

  do {

    if ((c=FFRGetC(handle)) < 0) return c;

    if (c == 0) {

      if ((c=FFRGetC(handle)) < 0) return c;

      if (c == 0) {
        buffer += (width-xpos); height--; xpos = 0;
      }
      else if (!--c) {
        break;
      }
      else if (!--c) {
        a = FFRGetC(handle); xpos += a; buffer += a;
        if ((c=FFRGetC(handle)) < 0) return c;
        height -= c; buffer += MUL(width,c);
      }
      else {
        c += 2; i = c;
        do {
          *buffer++ = FFRGetC(handle);
        } while (--i);
        if (c&1) FFRGetC(handle);
        xpos += c;
      }
    }
    else {
      i = c; a = FFRGetC(handle);
      do {
        *buffer++ = a;
      } while (--i);
      xpos += c;
    }
  } while (height && buffer < end);

  return 0;
}

/*
** compressed image (4 or 8 bit)
*/

LONG decode_rle_v42(APTR pool, APTR handle, ULONG width, ULONG height, ULONG depth,
                    UBYTE *lbuf, struct RastPort *rp, struct RastPort *trp)
{
  LONG (*decode)(APTR,UBYTE *,ULONG,ULONG);
  UBYTE *buffer;
  LONG ret=ERROR_NO_FREE_STORE;

  if ((buffer=AllocPooled(pool,MUL(width,height)+260)) != NULL) {

    decode = decompress_4bit; if (depth != 4) decode = decompress_8bit;

    if ((ret=(*decode)(handle,buffer,width,height)) == 0)
      do {
        convert_8bit(buffer,lbuf,width);
        WritePixelLine8(rp,0,height-1,width,lbuf,trp);
        buffer+=width;
      } while (--height);
  }

  return ret;
}

/*
** uncompressed image (1, 4 or 8 bit)
*/

LONG decode_normal_v42(APTR pool, APTR handle, ULONG width, ULONG height, ULONG depth,
                       UBYTE *lbuf, struct RastPort *rp, struct RastPort *trp)
{
  VOID (*convert)(UBYTE *,UBYTE *,ULONG);
  ULONG padwidth;
  UBYTE *buffer;
  LONG ret=ERROR_NO_FREE_STORE;

  padwidth = (((MUL(width,depth) + 31) & ~31) / 8);

  if ((buffer=AllocPooled(pool,padwidth)) != NULL) {

    convert = convert_8bit;
    if (depth != 8) {
      convert = convert_4bit;
      if (depth != 4)
        convert = convert_1bit;
    }

    ret = -1;

    do {
      if (FFRRead(handle,buffer,padwidth) != padwidth)
        return ret;
      (*convert)(buffer,lbuf,width);
      WritePixelLine8(rp,0,height-1,width,lbuf,trp);
    } while (--height);

    ret = 0;
  }

  return ret;
}

/*
** convert 24 bit to HAM6/8 (not yet)
*/

LONG decode_truecolor_v42(APTR pool, APTR handle, ULONG width, ULONG height, ULONG depth,
                          UBYTE *lbuf, struct RastPort *rp, struct RastPort *trp)
{
  return ERROR_NOT_IMPLEMENTED;
}

/*
** create the bitmap (V42 interface)
*/

LONG decode_picture_v42(Class *cl, Object *obj, APTR pool, APTR handle, struct BitMapHeader *bmhd, ULONG title)
{
  LONG (*decode)(APTR,APTR,ULONG,ULONG,ULONG,UBYTE *,struct RastPort *,struct RastPort *);
  ULONG modeid,width,height,depth;
  struct BitMap *tbm,*bm;
  struct RastPort trp,rp;
  UBYTE *lbuf;
  LONG ret=ERROR_NO_FREE_STORE;

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

  if (depth > 8) depth = 8;

  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 ((lbuf=AllocPooled(pool,((width+15)&~15))) != NULL) {

        decode = decode_truecolor_v42;
        if (bmhd->bmh_Depth == (UBYTE)depth) {
          decode = decode_normal_v42;
          if (bmhd->bmh_Compression) {
            bmhd->bmh_Compression = 0; decode = decode_rle_v42;
          }
        }

        if ((ret=(*decode)(pool,handle,width,height,depth,lbuf,&rp,&trp)) == 0) {

          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);
        }
      }

      FreeBitMap(tbm);
    }

    if (ret)
      FreeBitMap(bm);
  }

  return ret;
}

/*
** compressed image (4 or 8 bit)
*/

LONG decode_rle_v43(Class *cl, Object *obj, APTR pool, APTR handle, ULONG width, ULONG height, ULONG depth)
{
  LONG (*decode)(APTR,UBYTE *,ULONG,ULONG);
  UBYTE *buffer;
  LONG ret=ERROR_NO_FREE_STORE;

  if ((buffer=AllocPooled(pool,MUL(width,height)+260)) != NULL) {

    decode = decompress_4bit; if (depth != 4) decode = decompress_8bit;

    if ((ret=(*decode)(handle,buffer,width,height)) == 0)
      do {
        DoSuperMethod(cl,obj,
                      DTM_WRITEPIXELARRAY,buffer,RECTFMT_LUT8,width,0,height-1,width,1);
        buffer+=width;
      } while (--height);
  }

  return ret;
}

/*
** uncompressed image (1, 4, 8 or 24 bit)
*/

LONG decode_normal_v43(Class *cl, Object *obj, APTR pool, APTR handle, ULONG width, ULONG height, ULONG depth)
{
  VOID (*convert)(UBYTE *,UBYTE *,ULONG);
  ULONG padwidth,rectfmt;
  UBYTE *buffer,*lbuf;
  LONG ret=ERROR_NO_FREE_STORE;

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

    lbuf = buffer; rectfmt = RECTFMT_RGB; convert = convert_24bit;

    if (depth != 24) {
      convert = convert_8bit;
      if (depth != 8) {
        convert = convert_4bit;
        if (depth != 4) {
          convert = convert_1bit;
        }
      }
      rectfmt = RECTFMT_LUT8; lbuf = AllocPooled(pool,width);
    }

    if (lbuf != NULL) {

      ret = -1;

      do {
        if (FFRRead(handle,buffer,padwidth) != padwidth)
          return ret;
        (*convert)(buffer,lbuf,width);
        DoSuperMethod(cl,obj,
                      DTM_WRITEPIXELARRAY,lbuf,rectfmt,0,0,height-1,width,1);
      } while (--height);

      ret = 0;
    }
  }

  return ret;
}

/*
** decode picture (V43 interface)
*/

LONG decode_picture_v43(Class *cl, Object *obj, APTR pool, APTR handle, struct BitMapHeader *bmhd, ULONG title)
{
  LONG (*decode)(Class *,Object *,APTR,APTR,ULONG,ULONG,ULONG);
  ULONG width,height,depth;
  LONG level,number,ret;

  level = 0; number = 0;

  set_dt_attrs(obj,
               PDTA_SourceMode,  MODE_V43,
               PDTA_ModeID,      0,
               DTA_ObjName,      title,
               DTA_NominalHoriz, width=bmhd->bmh_Width,
               DTA_NominalVert,  height=bmhd->bmh_Height,
               DTA_ErrorLevel,   &level,
               DTA_ErrorNumber,  &number,
               TAG_DONE);

  ret = number;

  if (!level) {
    decode = decode_normal_v43;
    if (bmhd->bmh_Compression)
      {bmhd->bmh_Compression = 0; decode = decode_rle_v43; }
    ret = (*decode)(cl,obj,pool,handle,width,height,bmhd->bmh_Depth);
  }

  return ret;
}

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

LONG read_bmp_header(Object *obj, APTR pool, APTR handle, struct BitMapHeader *bmhd)
{
  UBYTE header[64],*p[2],*coltab,*cmap,r,g,b;
  LONG headerSize;
  LONG bfOffBits;
  LONG biDepth;
  LONG biPlanes;
  LONG biCompression;
  LONG biClrUsed;
  LONG cmapentrysize;
  LONG coltabsize;
  ULONG *cregs;

  if (FFRRead(handle,header,18) != 18)
    return FALSE;

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

  bfOffBits  = GET_4B(header,10);
  headerSize = GET_4B(header,14);

  if (headerSize != 12 && headerSize != 40 && headerSize != 64)
    return FALSE;

  if (FFRRead(handle,header+4,headerSize-4) != (headerSize-4))
    return FALSE;

  if (headerSize == 12) { /* OS/2 1.x */
    bmhd->bmh_Width  = GET_2B(header, 4);
    bmhd->bmh_Height = GET_2B(header, 6);
    biPlanes         = GET_2B(header, 8);
    biDepth          = GET_2B(header,10);
    biCompression    = 0;
    biClrUsed        = 0;
    cmapentrysize    = 3;
  }
  else { /* Windows 3.x or OS/2 2.x */
    bmhd->bmh_Width  = GET_4B(header, 4);
    bmhd->bmh_Height = GET_4B(header, 8);
    biPlanes         = GET_2B(header,12);
    biDepth          = GET_2B(header,14);
    biCompression    = GET_4B(header,16);
    biClrUsed        = GET_4B(header,32);
    cmapentrysize    = 4;
  }

  if (biPlanes != 1)
    return FALSE;

  if (biDepth != 1 && biDepth != 4 && biDepth != 8) {
    if (biDepth == 24)
      cmapentrysize = 0;
    else
      return FALSE;
  }

  if ((bmhd->bmh_PageWidth=bmhd->bmh_Width) == 0 ||
      (bmhd->bmh_PageHeight=bmhd->bmh_Height) == 0)
    return FALSE;

  bmhd->bmh_Depth = biDepth;

  bmhd->bmh_Compression = biCompression;

  if (bmhd->bmh_Compression && ((biDepth == 4 && biCompression != BI_RLE4) ||
                                (biDepth == 8 && biCompression != BI_RLE8)))
    return FALSE;

  if ((coltabsize=cmapentrysize)) {

    if (biClrUsed <= 0)
      biClrUsed = 1L << biDepth;

    if (biClrUsed > 256)
      return FALSE;

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

    if ((get_dt_attrs(obj, PDTA_ColorRegisters,(ULONG)&p[0],PDTA_CRegs,(ULONG)&p[1], TAG_DONE)) != 2)
      return FALSE;

    coltabsize = MUL(biClrUsed,cmapentrysize);

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

    if (FFRRead(handle,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
    */

    cmap = (UBYTE *)p[0]; cregs = (ULONG *)p[1];
    do {
      r = coltab[2]; *cmap++ = r; *cregs++ = r * 0x01010101;
      g = coltab[1]; *cmap++ = g; *cregs++ = g * 0x01010101;
      b = coltab[0]; *cmap++ = b; *cregs++ = b * 0x01010101;
      coltab += cmapentrysize;
    } while (--biClrUsed);
  }

  if ((biClrUsed = bfOffBits - (14 + headerSize + coltabsize)) > 0)
    do
      if (FFRGetC(handle) < 0) break;
    while (--biClrUsed);

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

/*
** load the picture
*/

LONG __stdargs GetBMP(Class *cl, Object *obj, struct TagItem *attrs)
{
  LONG (*decode)(Class *,Object *,APTR,APTR,struct BitMapHeader *,ULONG);
  struct BitMapHeader *bmhd;
  ULONG *methods,id;
  LONG error,success;
  APTR pool,handle;
  BPTR fh;

  success = FALSE; error = ERROR_NO_FREE_STORE;

  if ((pool=CreatePool(MEMF_CLEAR,1024,1024)) != NULL) {

    error = ERROR_OBJECT_WRONG_TYPE;

    if (GetTagData(DTA_SourceType,DTST_FILE,attrs) == DTST_FILE) {

       if ((get_dt_attrs(obj, DTA_Handle,&fh,PDTA_BitMapHeader,&bmhd, TAG_DONE) == 2) && bmhd && fh) {

        if ((handle=OpenFFR(fh)) != NULL) {

          if (read_bmp_header(obj,pool,handle,bmhd) != FALSE) {

            decode = decode_picture_v42;

            if ((methods=GetDTMethods(obj)) != NULL) {

              do
                id=*methods++;
              while ((id != ~0) && (id-=DTM_WRITEPIXELARRAY));

              if (!id) decode = decode_picture_v43;
            }

            if ((error=(*decode)(cl,obj,pool,handle,bmhd,GetTagData(DTA_Name,NULL,attrs))) == 0)
              success = TRUE;
          }
          CloseFFR(handle);
        }
        else
          error = ERROR_NO_FREE_STORE;
      }
    }
    DeletePool(pool);
  }

  if (!success && error > 0) SetIoErr(error);

  return success;
}
