/*
** our include
*/

#include "classbase.h"

/*
** a shortcut :-)
*/

typedef VOID (*FPTR)(UBYTE *,UBYTE *,ULONG);

/*
** structure(s)
*/

struct ReadData {
  ULONG width,
        height,
        depth,
        padwidth,
        mode,
        gray,
        comp,
        fmt,
        ham,
        rmask,rshift,rmax,
        gmask,gshift,gmax,
        bmask,bshift,bmax,
        (*lnum)(ULONG,ULONG);
  LONG  dplus;
  APTR  obj,
        pool,
        bmhd,
        handle;
  FPTR  conv;
  ULONG title;
  union {
    struct RastPort rp[2];
    UBYTE  buf[64];
    ULONG  p[2];
  } u;
};

#define r_fhd 0
#define r_src 1
#define r_lev 0
#define r_num 1
#define r_col 0
#define r_crg 1

struct SaveData {
  ULONG width,
        height,
        depth,
        padwidth,
        numcol,
        fmt,
        (*lnum)(ULONG,ULONG);
  APTR  bm,
        pool,
        handle,
        cmap;
  BYTE  isHAM,
        isEHB,
        pad[2];
  union {
    struct RastPort rp[2];
    ULONG  p[2];
  } u;
};

#define s_bmh 0
#define s_mid 1

struct hamData {
  UBYTE *cmap;
  ULONG  bits,mask,shift;
};

#define HD ((struct hamData *)outbuf)[-1]

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

#if !defined(ALL_IN_ONE)

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

/*
** functions for HAM encoding
*/

VOID CreateHAM6Line(UBYTE *,UBYTE *,UBYTE *,UBYTE *,LONG,ULONG);
VOID CreateHAM8Line(UBYTE *,UBYTE *,UBYTE *,UBYTE *,LONG,ULONG);

#endif

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

#define RECTFMT_RGB  0
#define RECTFMT_LUT8 3

/*
** easy access of a char-array as a "structure"
*/

#if !defined(__GNUC__)

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

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

#define GET_4B(array,offset) \
  ((ULONG) (GET_2B(array,offset) | (GET_2B(array,offset+2)<<16)))

#define PUT_1B(array,offset,value) \
   array[offset] = (UBYTE)((value) & 0xff);

#define PUT_2B(array,offset,value) \
  { UWORD v = value; \
    PUT_1B(array,offset,v); PUT_1B(array,offset+1,v>>=8); }

#define PUT_4B(array,offset,value) \
  { ULONG v = value; \
    PUT_1B(array,offset+0,v>>=0); PUT_1B(array,offset+1,v>>=8); \
    PUT_1B(array,offset+2,v>>=8); PUT_1B(array,offset+3,v>>=8); }

#else

#define GET_2B(array,offset) SWAPW(*(UWORD *)(array+offset))
#define GET_4B(array,offset) SWAPL(*(ULONG *)(array+offset))

#define PUT_1B(array,offset,value) array[offset] = (UBYTE)((value) & 0xff);
#define PUT_2B(array,offset,value) *((UWORD *)(array+offset)) = SWAPW(value)
#define PUT_4B(array,offset,value) *((ULONG *)(array+offset)) = SWAPL(value)

#endif

INLINE UWORD SWAPW(UWORD a) { return (UWORD)((a>>8)+((a&0xff)<<8)); }
INLINE ULONG SWAPL(ULONG a) { return (ULONG)(SWAPW(a>>16)+(SWAPW(a)<<16)); }

/*
** compression types for BMPs
*/

enum { BI_RGB=0,BI_RLE8,BI_RLE4,BI_BITFIELDS,BI_RLE24 };

/*
** redirect library bases
*/

#define SysBase       cb->SysBase
#define DOSBase       cb->DOSBase
#define GfxBase       cb->GfxBase
#define IntuitionBase cb->IntuitionBase
#define DataTypesBase cb->DataTypesBase
#define PictureClass  cb->PictureClass

#if defined(__GNUC__)

/*
** set dt attribute(s)
*/

STATIC ULONG set_dt_attrs(struct ClassBase *cb,Object *obj,Tag tag,...)
{
  return SetDTAttrsA(obj,NULL,NULL,(struct TagItem *)&tag);
}

#define SetDTAttrs(a,b,c,d...) set_dt_attrs(cb,a,d)

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

STATIC ULONG get_dt_attrs(struct ClassBase *cb,Object *obj,Tag tag,...)
{
  return GetDTAttrsA(obj,(struct TagItem *)&tag);
}

#define GetDTAttrs(a...) get_dt_attrs(cb,a)

#endif /* __GNUC__ */

/*
** get and parse user settings then start decoder/encoder
*/

#define PREFSFILE (STRPTR)"classes/datatypes/bmp.prefs"

enum {
  RD_V42MODE=0,RD_GRAY,RD_HAM,SD_TOPDOWN,ARG_LAST
};

#define TEMPLATE "V42MODE/S,GRAY/S,HAM/K/N,TOPDOWN/S"

STATIC LONG startup(struct ClassBase *cb,APTR data,LONG (*start)(struct ClassBase *,LONG *,APTR),APTR pool)
{ LONG prefs[ARG_LAST],result,len;
  struct RDArgs *rdarg;
  UBYTE p[4],*buf;

  /* clear argument vector */
  { ULONG *m=(ULONG *)prefs,l=sizeof(prefs)/sizeof(prefs[0]);
    do;while(*m++=0,--l); }

  /* get && parse prefs file */
  if (rdarg=NULL,GetVar(PREFSFILE,&p[0],1,0) >= 0)
    if ((buf=AllocPooled(pool,len=1+IoErr())) != NULL)
      if ((len=GetVar(PREFSFILE,buf,len,0)) >= 0)
        if (buf[len++]='\n',(rdarg=AllocDosObject(DOS_RDARGS,NULL)) != NULL) {
          rdarg->RDA_Source.CS_Buffer = buf;
          rdarg->RDA_Source.CS_Length = len;
          rdarg->RDA_Flags = RDAF_NOPROMPT;
          ReadArgs((STRPTR)TEMPLATE,&prefs[0],rdarg);
        }
  
  /* start decoder/encoder */
  result = (*start)(cb,&prefs[0],data);

  /* free args */
  if (rdarg) {
    FreeArgs(rdarg); FreeDosObject(DOS_RDARGS,rdarg);
  }

  /* status */
  return result;
}

/*
** line order
*/

STATIC ULONG bottom_up(ULONG height,ULONG y)
{
  return height - y - 1;
}

STATIC ULONG top_down(ULONG height,ULONG y)
{
  return y;
}

/*
** do nothing
*/

STATIC VOID convert_stub(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{
  /* no work */
}

/*
** reduce 24bit data to 8bit grayscale
*/

STATIC VOID grayscale(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ ULONG r,c;

  do {
    c = *pixelbuf++; c *= 5; r = *pixelbuf++; c += 16*r; r = *pixelbuf++; c += 11*r; c >>= 5;
  } while (*outbuf++=c,--width);
}

/*
** RGB -> HAM8 encoding
*/

STATIC VOID RGBtoHAM8(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{
  CreateHAM8Line(pixelbuf+2,pixelbuf+1,pixelbuf+0,outbuf,3,width);
}

/*
** RGB -> HAM6 encoding
*/

STATIC VOID RGBtoHAM6(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{
  CreateHAM6Line(pixelbuf+2,pixelbuf+1,pixelbuf+0,outbuf,3,width);
}

/*
** create 8 bit RGB values
*/

STATIC VOID convert_32bit(ULONG *pixelbuf,UBYTE *outbuf,ULONG width)
{ struct ReadData *rd = ((struct ReadData **)outbuf)[-1];
  UBYTE *out = outbuf;

  do {
    ULONG c,tmp=SWAPL(*pixelbuf++);
    c=rd->rmask; c&=tmp; c>>=rd->rshift; *outbuf++ = (c * 255) / rd->rmax;
    c=rd->gmask; c&=tmp; c>>=rd->gshift; *outbuf++ = (c * 255) / rd->gmax;
    c=rd->bmask; c&=tmp; c>>=rd->bshift; *outbuf++ = (c * 255) / rd->bmax;
  } while (--width);

  (*rd->conv)(out,out,rd->width);
}

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

STATIC VOID convert_24bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{
  do {
    UBYTE c = pixelbuf[0]; pixelbuf[0] = pixelbuf[2]; pixelbuf[2] = c; pixelbuf += 3;
  } while (--width);
}

/*
** create 8 bit RGB values
*/

STATIC VOID convert_16bit(UWORD *pixelbuf,UBYTE *outbuf,ULONG width)
{ struct ReadData *rd = ((struct ReadData **)outbuf)[-1];
  UBYTE *out = outbuf;

  do {
    ULONG c,tmp=SWAPW(*pixelbuf++);
    c=rd->rmask; c&=tmp; c>>=rd->rshift; *outbuf++ = (c * 255) / rd->rmax;
    c=rd->gmask; c&=tmp; c>>=rd->gshift; *outbuf++ = (c * 255) / rd->gmax;
    c=rd->bmask; c&=tmp; c>>=rd->bshift; *outbuf++ = (c * 255) / rd->bmax;
  } while (--width);

  (*rd->conv)(out,out,rd->width);
}

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

STATIC VOID convert_8bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{
  do;while(*outbuf++=*pixelbuf++,--width);
}

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

STATIC VOID decode_4bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ UBYTE c;

  ++width,width/=2;
  do;while(c=*pixelbuf++,*outbuf++=c>>4,*outbuf++=(c&=0xf),--width);
}

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

STATIC VOID decode_1bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ UBYTE mask,c;

  mask = 0;
  do
    if ((mask>>=1) == 0)
      c=*pixelbuf++,mask=0x80;
  while (*outbuf++=c&mask?1:0,--width);
}

/*
** handle 8 bit compression
*/

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

  end = buffer + width * height; xpos = 0; get = (LONG (*)(APTR))FFRGetC;

  do {

    if ((c=(*get)(handle)) < 0) return c;

    if (!c) {

      if ((c=(*get)(handle)) < 0) return c;

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

  return 0;
}

/*
** handle 4 bit compression
*/

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

  end = buffer + width * height; xpos = 0; get = (LONG (*)(APTR))FFRGetC;

  do {

    if ((c=(*get)(handle)) < 0) return c;

    if (!c) {

      if ((c=(*get)(handle)) < 0) return c;

      if (!c) {
        buffer += (width-xpos); height--; xpos = 0;
      }
      else if (!--c) {
        break;
      }
      else if (!--c) {
        a = (*get)(handle); xpos += a; buffer += a;
        if ((c=(*get)(handle)) < 0) return c;
        height -= c; buffer += width * c;
      }
      else {
        c += 2; i = 0;
        do {
          a = (*get)(handle); b = a & 0x0f; a >>= 4;
          *buffer++ = a; *buffer++ = b;
          i += 2;
        } while (i < c);
        if (((c&3) == 1) || ((c&3) == 2)) (*get)(handle);
        xpos += c; if (i > c) --buffer;
      }
    }
    else {
      i = 0; b = (*get)(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;
}

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

INLINE LONG decode_rle_v42(struct ClassBase *cb,struct ReadData *rd,APTR lbuf)
{ LONG (*decode)(APTR,UBYTE *,ULONG,ULONG);
  UBYTE *buffer;
  ULONG height;
  LONG ret;

  if ((buffer=AllocPooled(rd->pool,(height=rd->height)*rd->width+260)) != NULL) {

    decode = decompress_4bit; if (rd->depth != 4) decode = decompress_8bit;

    if ((ret=(*decode)(rd->handle,buffer,rd->width,height)) == 0)
      do {
        convert_8bit(buffer,lbuf,rd->width);
        WritePixelLine8(&rd->u.rp[0],0,--height,rd->width,lbuf,&rd->u.rp[1]);
        buffer += rd->width;
      } while ((ret=height));

    return ret;
  }

  return ERROR_NO_FREE_STORE;
}

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

INLINE LONG decode_normal_v42(struct ClassBase *cb,struct ReadData *rd,UBYTE *lbuf)
{ ULONG padwidth,y;
  UBYTE *buffer;
  FPTR convert;

  if (buffer=lbuf,padwidth=rd->padwidth,convert=grayscale,!rd->gray)
    if (convert=RGBtoHAM6,rd->ham != 6)
      if (convert=RGBtoHAM8,rd->ham != 8)
        if (convert=convert_stub,rd->depth != 8) {
          buffer += rd->width; buffer -= padwidth; buffer += 8;
          if (convert=decode_4bit,rd->depth != 4)
            convert = decode_1bit;
        }

  if (rd->conv=convert,rd->dplus) {
    if (convert=(FPTR)convert_32bit,rd->dplus > 0)
      convert=(FPTR)convert_16bit,buffer+=rd->width;
    { LREG(a0,volatile APTR *ptr)=(APTR *)lbuf; *ptr = rd; lbuf += sizeof(rd); buffer += sizeof(rd); }
  }

  y = 0;
  do {
    if (FFRRead(rd->handle,buffer,padwidth) != padwidth)
      break;
    (*convert)(buffer,lbuf,rd->width);
    WritePixelLine8(&rd->u.rp[0],0,(*rd->lnum)(rd->height,y),rd->width,lbuf,&rd->u.rp[1]);
  } while (++y < rd->height);

  return (LONG)(y-=rd->height);
}

/*
** obtain best modeID
*/

INLINE ULONG GetBestModeID(struct ClassBase *cb,struct ReadData *rd,ULONG w,ULONG h)
{ ULONG id = LORES_KEY;

  if ((UWORD)w > 362)
    id = HIRES;
  if ((UWORD)h > 283)
    id |= LACE;
  if (rd->ham && ModeNotAvailable(id|=HAM))
    id &= ~HIRES;
  return id;
}

/*
** decode picture (V42 interface)
*/

STATIC LONG decode_picture_v42(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ ULONG padwidth,size;
  APTR lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

  InitRastPort(&rd->u.rp[0]);

  if ((rd->u.rp[0].BitMap=AllocBitMap(rd->width,rd->height,rd->depth,BMF_CLEAR|BMF_DISPLAYABLE|BMF_MINPLANES,NULL)) != NULL) {

    InitRastPort(&rd->u.rp[1]);

    if ((rd->u.rp[1].BitMap=AllocBitMap(rd->width,1,rd->depth,BMF_CLEAR|BMF_MINPLANES,NULL)) != NULL) {

      if ((size=rd->width) < (padwidth=rd->padwidth)) {
        if (rd->dplus > 0)
          size += padwidth;
        else
          size = padwidth;
      }

      if ((lbuf=AllocPooled(rd->pool,(size+8+15)&~15)) != NULL) {

        if (rd->comp)
          ret = decode_rle_v42(cb,rd,lbuf);
        else
          ret = decode_normal_v42(cb,rd,lbuf);

        if (WaitBlit(),!ret) {

          SetDTAttrs(rd->obj, NULL, NULL,
                     PDTA_ModeID,      GetBestModeID(cb,rd,rd->width,rd->height),
                     PDTA_BitMap,      rd->u.rp[0].BitMap,
                     DTA_NominalHoriz, rd->width,
                     DTA_NominalVert,  rd->height,
                     DTA_ObjName,      rd->title,
                     TAG_DONE);

          rd->u.rp[0].BitMap = NULL;
        }
      }
      FreeBitMap(rd->u.rp[1].BitMap);
    }
    FreeBitMap(rd->u.rp[0].BitMap);
  }

  return ret;
}

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

INLINE LONG decode_rle_v43(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ LONG (*decode)(APTR,UBYTE *,ULONG,ULONG);
  UBYTE *buffer;
  ULONG height;
  LONG ret;

  if ((buffer=AllocPooled(rd->pool,(height=rd->height)*rd->width+260)) != NULL) {

    decode = decompress_4bit; if (rd->depth != 4) decode = decompress_8bit;

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

    return ret;
  }

  return ERROR_NO_FREE_STORE;
}

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

INLINE LONG decode_normal_v43(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ ULONG padwidth,size,y;
  UBYTE *buffer,*lbuf;
  FPTR convert;

  if ((size=rd->width) < (padwidth=rd->padwidth)) {
    if (rd->dplus > 0)
      size += padwidth;
    else
      size = padwidth;
  }

  if ((lbuf=(buffer=AllocPooled(rd->pool,size+8))) != NULL) {

    if (convert=grayscale,!rd->gray)
      if (convert=convert_stub,!rd->dplus && rd->depth != 8)
        if (convert=convert_24bit,rd->fmt != RECTFMT_RGB) {
          buffer += rd->width; buffer -= padwidth; buffer += 8;
          if (convert=decode_4bit,rd->depth != 4)
            convert = decode_1bit;
        }

    if (rd->conv=convert,rd->dplus) {
      if (convert=(FPTR)convert_32bit,rd->dplus > 0)
        convert=(FPTR)convert_16bit,buffer+=rd->width;
      *(struct ReadData **)lbuf = rd; lbuf += sizeof(rd); buffer += sizeof(rd);
    }

    y = 0;
    do {
      if (FFRRead(rd->handle,buffer,padwidth) != padwidth)
        break;
      (*convert)(buffer,lbuf,rd->width);
      DoSuperMethod(PictureClass,rd->obj,
                    PDTM_WRITEPIXELARRAY,lbuf,rd->fmt,0,0,(*rd->lnum)(rd->height,y),rd->width,1);
    } while (++y < rd->height);

    return (LONG)(y-=rd->height);
  }

  return ERROR_NO_FREE_STORE;
}

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

STATIC LONG decode_picture_v43(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ LONG ret;

  rd->u.p[r_lev] = 0; rd->u.p[r_num] = 0;

  SetDTAttrs(obj, NULL, NULL,
             PDTA_SourceMode,  PMODE_V43,
             PDTA_ModeID,      0,
             DTA_ErrorLevel,   &rd->u.p[r_lev],
             DTA_ErrorNumber,  &rd->u.p[r_num],
             DTA_NominalHoriz, rd->width,
             DTA_NominalVert,  rd->height,
             DTA_ObjName,      rd->title,
             TAG_DONE);

  if (ret=rd->u.p[r_num],!rd->u.p[r_lev]) {
    if (rd->comp)
      ret = decode_rle_v43(cb,obj,rd);
    else
      ret = decode_normal_v43(cb,obj,rd);
  }

  return ret;
}

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

STATIC LONG read_bmp_header(struct ClassBase *cb,LONG *prefs,APTR data)
{ struct ReadData *rd = (struct ReadData *)data;
  ULONG cmapentrysize,*methods,*cregs,tmp,val,r,g,b,c;
  struct BitMapHeader *bmhd;
  UBYTE *coltab,*cmap,*buf;
  LREG(a6,APTR ptr);
  LONG bfOffBits;
  LONG biSize;
  LONG biDepth;
  LONG biCompression;
  LONG biClrUsed;
  WORD biPlanes;

  /* preferences */
  if (rd->gray=prefs[RD_GRAY],!rd->gray && prefs[RD_HAM]) {
    if (val=*(ULONG *)prefs[RD_HAM],val != 6 && val != 8) val = 0;
  } else val = 0;
  rd->ham = val;

  if (val=~0,!prefs[RD_V42MODE] && (methods=GetDTMethods(rd->obj)))
    do;while(((val=*methods++) != ~0) && (val-=PDTM_WRITEPIXELARRAY));
  rd->mode = val;

  /* lets go */
  if (FFRRead(rd->handle,buf=rd->u.buf,18) != 18)
    return FALSE;

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

  bfOffBits = GET_4B(buf,10) - (biSize=GET_4B(buf,14));

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

  { ULONG *m=(ULONG *)buf,l=sizeof(rd->u.buf)/sizeof(*m);
    do;while(*m++=0,--l); }

  if (FFRRead(rd->handle,buf+=4,biSize-=4) != biSize)
    return FALSE;

  if (biSize == 8) { /* OS/2 1.x */
    rd->width     = GET_2B(buf, 0);
    rd->height    = GET_2B(buf, 2);
    biDepth       = GET_2B(buf, 6);
    biCompression = 0;
    biClrUsed     = 0;
    cmapentrysize = 3;
    biPlanes      = GET_2B(buf, 4);
  }
  else { /* Windows 3.x or OS/2 2.x */
    rd->width     = GET_4B(buf, 0);
    rd->height    = GET_4B(buf, 4);
    biDepth       = GET_2B(buf,10);
    biCompression = GET_4B(buf,12);
    biClrUsed     = GET_4B(buf,28);
    cmapentrysize = 4;
    biPlanes      = GET_2B(buf, 8);
  }

  if (biPlanes != 1)
    return FALSE;

  if ((rd->comp=biCompression) &&
      ((biDepth == 4 && biCompression != BI_RLE4) ||
       (biDepth == 8 && biCompression != BI_RLE8) ||
       ((biDepth == 16 || biDepth == 32) && biCompression != BI_BITFIELDS)))
    return FALSE;

  if (ptr=(APTR)bottom_up,(LONG)rd->height < 0) {
    if (biCompression && biCompression != BI_BITFIELDS)
      return FALSE;
    rd->height = -rd->height; ptr=(APTR)top_down;
  }
  rd->lnum = (ULONG (*)())ptr;

  rd->padwidth = (((biDepth * rd->width) + 31) & ~31) / 8;

  if (val=RECTFMT_LUT8,biDepth != 1 && biDepth != 4 && biDepth != 8) {

    if (biDepth != 24) {

      if ((val=0x1f,tmp=5,biDepth!=16) && (val=0xff,tmp=8,biDepth!=32))
        return FALSE;

      rd->bmask=val; val<<=tmp; rd->gmask=val; val<<=tmp; rd->rmask=val;

      if (rd->dplus=24-biDepth,biDepth=24,rd->comp) {

        rd->comp = 0;

        if (FFRRead(rd->handle,buf,12) != 12)
          return FALSE;

        bfOffBits -= 12;

        if ((rd->rmask=GET_4B(buf,0)) == 0)
          return FALSE;

        if ((rd->gmask=GET_4B(buf,4)) == 0)
          return FALSE;

        if ((rd->bmask=GET_4B(buf,8)) == 0)
          return FALSE;
      }
    }

    if ((biClrUsed=rd->ham))
      rd->mode=biDepth=biClrUsed,biClrUsed=1,biClrUsed<<=(biDepth-2);

    if (!biClrUsed && (rd->gray || (rd->gray=rd->mode,rd->gray)))
      biDepth=8,biClrUsed=256;

    if (rd->dplus) {

      if (biDepth <= 8)
        tmp=rd->rmask,rd->rmask=rd->bmask,rd->bmask=tmp;

      if (tmp=0,((val=rd->rmask) & 1) == 0)
        do;while(val>>=1,++tmp,(val & 1) == 0);
      rd->rshift = tmp; rd->rmax = val;

      if (tmp=0,((val=rd->gmask) & 1) == 0)
        do;while(val>>=1,++tmp,(val & 1) == 0);
      rd->gshift = tmp; rd->gmax = val;

      if (tmp=0,((val=rd->bmask) & 1) == 0)
        do;while(val>>=1,++tmp,(val & 1) == 0);
      rd->bshift = tmp; rd->bmax = val;
    }

    val = RECTFMT_RGB;
  }

  bmhd = (struct BitMapHeader *)rd->bmhd;
  bmhd->bmh_Depth = rd->depth = biDepth;
  bmhd->bmh_PageWidth = bmhd->bmh_Width = rd->width;
  bmhd->bmh_PageHeight = bmhd->bmh_Height = rd->height;

  if ((rd->fmt=val) != RECTFMT_RGB) {

    if (biClrUsed > 256)
      return FALSE;

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

    if ((coltab=AllocPooled(rd->pool,val=biClrUsed*cmapentrysize)) == NULL)
      return FALSE;

    if (FFRRead(rd->handle,coltab,val) != val)
      return FALSE;

    bfOffBits -= val;
  }

  if (biClrUsed) {

    SetDTAttrs(rd->obj, NULL, NULL, PDTA_NumColors,(ULONG)biClrUsed, TAG_DONE);
    if (GetDTAttrs(rd->obj, PDTA_ColorRegisters,&rd->u.p[r_col],PDTA_CRegs,&rd->u.p[r_crg], TAG_DONE) != 2)
      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
    */

    coltab += 2; cmap = (UBYTE *)rd->u.p[r_col]; cmapentrysize += 2; cregs = (ULONG *)rd->u.p[r_crg];
    if (c=0,rd->fmt == RECTFMT_RGB) {
      if (rd->ham)
        do {
          tmp = ((c * 255) / (biClrUsed - 1)) * 0x01010101;
          *cmap++ = tmp; *cregs++ = tmp;
          *cmap++ = tmp; *cregs++ = tmp;
          *cmap++ = tmp; *cregs++ = tmp;
        } while (++c < biClrUsed);
      else
        do {
          *cmap++ = c; *cregs++ = c;
          *cmap++ = c; *cregs++ = c; 
          *cmap++ = c; *cregs++ = c;
        } while (c+=0x01010101,--biClrUsed);
      rd->fmt += RECTFMT_LUT8;
    }
    else {
      if (rd->gray)
        do {
           c = *coltab; c *= 11; r = *--coltab; c += 16*r; r = *--coltab; c += 5*r; c >>= 5; c *= 0x01010101;
           *cmap++ = c; *cregs++ = c;
           *cmap++ = c; *cregs++ = c;
           *cmap++ = c; *cregs++ = c;
        } while (coltab+=cmapentrysize,--biClrUsed);
      else
        do {
          *cmap++ = (r = *  coltab); *cregs++ = (r *= 0x01010101);
          *cmap++ = (g = *--coltab); *cregs++ = (g *= 0x01010101);
          *cmap++ = (b = *--coltab); *cregs++ = (b *= 0x01010101);
        } while (coltab+=cmapentrysize,--biClrUsed);
      rd->ham = rd->gray = 0;
    }
  }

  if ((LONG)(val=bfOffBits-14) > 0)
    do
      if (FFRGetC(rd->handle) < 0) break;
    while (--val);

  return ((LONG)val <= 0 ? TRUE : FALSE);
}

/*
** load a picture
*/

STATIC LONG LoadBMP(struct ClassBase *cb,Object *obj)
{ LONG (*decode)(struct ClassBase *,Object *,struct ReadData *);
  struct ReadData *rd;
  LONG error;
  APTR pool;

  if (error=ERROR_NO_FREE_STORE,pool=CreatePool(MEMF_CLEAR,4*1024,4*1024)) {

    if ((rd=AllocPooled(pool,sizeof(*rd)))) {

      rd->pool       = pool;
    //rd->bmhd       = NULL;
    //rd->u.p[r_fhd] = 0;
      rd->u.p[r_src] = DTST_FILE;
    //rd->title      = 0;

      GetDTAttrs(rd->obj=obj,
                 PDTA_BitMapHeader, &rd->bmhd,
                 DTA_Handle,        &rd->u.p[r_fhd],
                 DTA_SourceType,    &rd->u.p[r_src],
                 DTA_Name,          &rd->title,
                 TAG_DONE);

      if (error=0,rd->u.p[r_src] != DTST_RAM) {

        if (error=ERROR_OBJECT_WRONG_TYPE,rd->u.p[r_src] == DTST_FILE && rd->bmhd && rd->u.p[r_fhd]) {

          if ((rd->handle=OpenFFR(cb,pool,FFR_MODE_READ,(BPTR)rd->u.p[r_fhd]))) {

            if (startup(cb,rd,read_bmp_header,pool)) {

              if (decode=decode_picture_v42,!rd->mode)
                decode=decode_picture_v43;
              error = (*decode)(cb,obj,rd);
            }
            (VOID)CloseFFR(rd->handle);
          }
          else
            error = ERROR_NO_FREE_STORE;
        }
      }
    }
    DeletePool(pool);
  }

  if (error) {
    if (error > 0) SetIoErr(error); return FALSE;
  }

  return TRUE;
}

/*
** convert HAM to RGB
*/

STATIC VOID HAMtoRGB(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ ULONG bits,mask,shift,pen;
  UBYTE *cmap,*p,v,r,g,b;

  shift = HD.shift; mask = HD.mask; bits = HD.bits; cmap = HD.cmap;

  r = *cmap++; g = *cmap++; b = *cmap; cmap -= 2;
  do
    if ((v=((pen=*pixelbuf++)>>bits)&3) == 0) {
      p = cmap+2*pen+pen; r = *p++; g = *p++; b = *p;
    }
    else {
      /*pen &= mask;*/ pen <<= shift;
      if (!--v)
        b = pen;
      else if (!--v)
        r = pen;
      else
        g = pen;
    }
  while (*outbuf++=b,*outbuf++=g,*outbuf++=r,--width);
}

/*
** pack 2 bytes into one
*/

STATIC VOID encode_4bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ UBYTE c;

  ++width,width/=2;
  do;while(c=*pixelbuf++<<4,c|=*pixelbuf++,*outbuf++=c,--width);
}

/*
** pack 8 bytes into one
*/

STATIC VOID encode_1bit(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ ULONG mask,c;

  mask = 0x80; c = 0;
  do
    if (c|=*pixelbuf++?mask:0,(mask>>=1) == 0)
      *outbuf++=c,mask=0x80,c=0;
  while (--width);
  if ((mask&0x80) == 0) *outbuf = c;
}

/*
** encode picture (V42 interface)
*/

STATIC LONG encode_picture_v42(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ ULONG *hd,d,padwidth,y;
  UBYTE *buffer;
  FPTR encode;

  if ((hd=AllocPooled(sd->pool,((sizeof(struct hamData)+sd->width+15)&~15)+(padwidth=sd->padwidth)))) {

    *hd++ = (ULONG)sd->cmap;   // cmap
    *hd++ = d = sd->depth - 2; // bits
    *hd++ = (1 << d) - 1;      // mask
    *hd++ = 8 - d;             // shift

    InitRastPort(&sd->u.rp[0]); sd->u.rp[0].BitMap = sd->bm; InitRastPort(&sd->u.rp[1]);

    if ((sd->u.rp[1].BitMap=AllocBitMap(sd->width,1,d+=2,BMF_MINPLANES,sd->bm))) {

      if (encode=HAMtoRGB,!sd->isHAM)
        if (encode=convert_8bit,sd->depth != 8)
          if (encode=encode_4bit,sd->depth != 4)
            encode = encode_1bit;

      buffer = (UBYTE *)hd; y = 0;
      do {
        ReadPixelLine8(&sd->u.rp[0],0,(*sd->lnum)(sd->height,y),sd->width,buffer+padwidth,&sd->u.rp[1]);
        (*encode)(buffer+padwidth,buffer,sd->width);
        if (FFRWrite(sd->handle,buffer,padwidth) != padwidth)
          break;
      } while (++y < sd->height);

      FreeBitMap(sd->u.rp[1].BitMap);

      return (LONG)(y-=sd->height);
    }
  }

  return ERROR_NO_FREE_STORE;
}

/*
** encode picture (V43 interface)
*/

STATIC LONG encode_picture_v43(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ UBYTE *buffer,*lbuf;
  ULONG padwidth,y;
  FPTR encode;

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

    if (lbuf=buffer,encode=convert_24bit,sd->fmt != RECTFMT_RGB)
      if (encode=convert_stub,sd->depth != 8) {
        if (encode=encode_4bit,sd->depth != 4)
          encode = encode_1bit;
        if ((lbuf=AllocPooled(sd->pool,sd->width)) == NULL) return ERROR_NO_FREE_STORE;
      }

    y = 0;
    do {
      DoSuperMethod(PictureClass,obj,
                    PDTM_READPIXELARRAY,lbuf,sd->fmt,0,0,(*sd->lnum)(sd->height,y),sd->width,1);
      (*encode)(lbuf,buffer,sd->width);
      if (FFRWrite(sd->handle,buffer,padwidth) != padwidth)
        break;
    } while (++y < sd->height);

    return (LONG)(y-=sd->height);
  }

  return ERROR_NO_FREE_STORE;
}

/*
** setup and write bmp header
*/

STATIC LONG write_bmp_header(struct ClassBase *cb,LONG *prefs,APTR data)
{ struct SaveData *sd = (struct SaveData *)data;
  ULONG bfSize,bfOffBits,biDepth,height,cnt,nc;
  UBYTE *header,*coltab,*cmap;
  LREG(a6,APTR ptr);
  LONG ret;

  /* check prefs */
  if (ptr=(APTR)bottom_up,height=sd->height,prefs[SD_TOPDOWN])
    height=-height,ptr=(APTR)top_down;
  sd->lnum = (ULONG (*)())ptr;

  /* now write header */
  if (nc=0,biDepth=24,cnt=0,!sd->isHAM && sd->depth != biDepth) {
    if (nc=sd->numcol,sd->isEHB)
      nc += (cnt=nc);
    if (biDepth=1,nc > 2)
      if (biDepth=4,nc > 16)
        biDepth = 8;
  }
  sd->padwidth = (((sd->width * (sd->depth=biDepth)) + 31) & ~31) / 8;

  bfOffBits = 14 + 40 + 4*nc; bfSize = bfOffBits + sd->height * sd->padwidth;

  if ((header=AllocPooled(sd->pool,bfOffBits)) != NULL) {

    PUT_2B(header,  0, 0x4D42);
    PUT_4B(header,  2, bfSize);
  //PUT_4B(header,  6, 0);
    PUT_2B(header, 10, bfOffBits);

    PUT_1B(header, 14, 40);
    PUT_2B(header, 18, sd->width);
    PUT_4B(header, 22, height);
    PUT_1B(header, 26, 1);
    PUT_1B(header, 28, biDepth);

  //PUT_1B(header, 30, BI_RGB);
  //PUT_4B(header, 34, 0);
  //PUT_4B(header, 38, 0);
  //PUT_4B(header, 42, 0);
    if (nc &&  (nc != (1<<biDepth)))
      PUT_2B(header, 46, nc);
  //PUT_4B(header, 50, 0);

    if (nc) {

      coltab = &header[14+40+2]; cmap = sd->cmap; if (cnt) nc = cnt;
      do {
        *coltab = *cmap++; *--coltab = *cmap++; *--coltab = *cmap++;
      } while (coltab+=4+2,--nc);

      if (cmap=sd->cmap,cnt)
        do {
          *coltab = *cmap++/2; *--coltab = *cmap++/2; *--coltab = *cmap++/2;
        } while (coltab+=4+2,--cnt);

      nc = RECTFMT_LUT8;
    }
  //else nc = RECTFMT_RGB;
    sd->fmt = nc;

    if ((ret=FFRWrite(sd->handle,header,bfOffBits)-bfOffBits))
      ret = -1;
  }
  else
    ret = ERROR_NO_FREE_STORE;

  return ret;
}

/*
** save a picture
*/

#define DTSI(o) ((struct DTSpecialInfo *)(((struct Gadget *)o)->SpecialInfo))

STATIC LONG SaveBMP(struct ClassBase *cb,Object *obj,struct dtWrite *dtw)
{ LREG(a6,LONG (*encode)(struct ClassBase *,Object *,struct SaveData *));
  struct BitMapHeader *bmhd;
  ULONG *methods,mode,id;
  struct SaveData *sd;
  LONG error,fh;
  APTR pool;

  if ((fh=dtw->dtw_FileHandle)) {

    ObtainSemaphoreShared(&(DTSI(obj)->si_Lock));

    if (error=ERROR_NO_FREE_STORE,(pool=CreatePool(MEMF_CLEAR,4*1024,4*1024))) {

      if ((sd=AllocPooled(pool,sizeof(*sd)))) {

        sd->pool       = pool;
      //sd->cmap       = NULL;
      //sd->numcol     = 0;
      //sd->bm         = NULL;
        sd->u.p[s_mid] = INVALID_ID;
      //sd->u.p[s_bmh] = NULL;

        SetDTAttrs(obj, NULL, NULL, PDTA_SourceMode,PMODE_V43, TAG_DONE);
        GetDTAttrs(obj,
                   PDTA_BitMapHeader,   &sd->u.p[s_bmh],
                   PDTA_ModeID,         &sd->u.p[s_mid],
                   PDTA_BitMap,         &sd->bm,
                   PDTA_NumColors,      &sd->numcol,
                   PDTA_ColorRegisters, &sd->cmap,
                   TAG_DONE);

        mode = sd->u.p[s_mid]; error = ERROR_OBJECT_WRONG_TYPE; bmhd = (APTR)sd->u.p[s_bmh];

        if (bmhd && mode != INVALID_ID && sd->bm && (bmhd->bmh_Depth > 8 || (sd->numcol && sd->cmap))) {

          sd->isEHB = ((mode & EXTRAHALFBRITE_KEY) != 0); sd->isHAM = ((mode & HAM_KEY) != 0);

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

          if (error=ERROR_NOT_IMPLEMENTED,bmhd->bmh_Compression == cmpNone) {

             if (error=ERROR_NO_FREE_STORE,(sd->handle=OpenFFR(cb,sd->pool,FFR_MODE_WRITE,fh))) {

              if ((error=startup(cb,sd,write_bmp_header,sd->pool)) == 0) {

                if (id=~0,(sd->depth > 8) && (methods=GetDTMethods(obj)))
                  do;while(((id=*methods++) != ~0) && (id-=PDTM_READPIXELARRAY));

                if (encode=encode_picture_v42,!id)
                  encode=encode_picture_v43;
                error = (*encode)(cb,obj,sd);
              }
              if (!CloseFFR(sd->handle)) error = -1;
            }
          }
        }
        pool = sd->pool;
      }
      DeletePool(pool);
    }

    ReleaseSemaphore(&(DTSI(obj)->si_Lock));

    if (error) {
      if (error > 0) SetIoErr(error); return FALSE;
    }
  }

  return TRUE;
}

/*
** class dispatcher
*/

ASM(Object *) Dispatch(REG(a0,struct IClass *cl),REG(a2,Object *obj),REG(a1,Msg msg))
{ struct ClassBase *cb = (struct ClassBase *)cl->cl_UserData;
  struct RastPort *rp;
  Object *o = obj;

  if ((msg->MethodID == DTM_WRITE) && (((struct dtWrite *)msg)->dtw_Mode == DTWM_RAW))
    return (Object *)SaveBMP(cb,obj,(struct dtWrite *)msg);

  if (msg->MethodID != OM_UPDATE || !DoMethod(obj,ICM_CHECKLOOP)) {
    if ((obj=(Object *)DoSuperMethodA(cl,obj,msg))) {
      if (msg->MethodID == OM_NEW) {
        if ((o != (Object *)cl) || !LoadBMP(cb,obj))
          { CoerceMethod(cl,obj,OM_DISPOSE); if (obj) ; obj = NULL; }
      }
      else if (msg->MethodID == OM_UPDATE || msg->MethodID == OM_SET) {
        if ((rp=ObtainGIRPort(((struct opSet *)msg)->ops_GInfo)) != NULL) {
          DoMethod(o,GM_RENDER,((struct opSet *)msg)->ops_GInfo,rp,GREDRAW_UPDATE);
          ReleaseGIRPort(rp); obj = NULL;
        }
      }
    }
  }
  else obj = NULL;

  return obj;
}
