/*
** our include
*/

#include "classbase.h"

/*
** structure(s)
*/

struct ReadData {
  ULONG width,
        height,
        depth,
        padwidth,
        mode,
        gray,
        comp,
        fmt,
        ham;
  APTR  obj,
        pool,
        bmhd,
        handle;
  ULONG title;
  union {
    struct RastPort rp[2];
    UBYTE  buf[64];
    ULONG  p[2];
  } u;
};

#define r_fh  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,
        numcolors,
        fmt;
  APTR  bm,
        pool,
        handle,
        cmap;
  BYTE  isHAM,
        isEHB,
        pad[2];
};

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" (IJG sources 5.0b)
*/

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

/*
** compression types for BMPs
*/

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

/*
** 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,ARG_LAST
};

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

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

/*
** do nothing
*/

STATIC VOID convert_stub(VOID)
{
  /* no work */
}

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

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

/*
** 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)
{ 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;
  }
}

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

STATIC VOID decode_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; c<<=1,j--)
      *outbuf++ = ((c & 0x80) ? 1 : 0);
  if ((i=width&7))
    for(c=*pixelbuf; i!=0; c<<=1,i--)
      *outbuf++ = ((c & 0x80) ? 1 : 0);
}

/*
** handle 8 bit compression
*/

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

  end = buffer + width * height; xpos = 0; get = 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)(),xpos,a,b,c,i;
  UBYTE *end;

  end = buffer + width * height; xpos = 0; get = 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)
*/

STATIC 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 or 8 bit)
*/

STATIC LONG decode_normal_v42(struct ClassBase *cb,struct ReadData *rd,APTR lbuf)
{ VOID (*convert)(UBYTE *,UBYTE *,ULONG);
  ULONG padwidth,height;
  UBYTE *buffer;
  LONG ret;

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

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

  if ((ret=height)) ret = -1; return ret;
}

/*
** obtain best modeID
*/

INLINE ULONG GetBestModeID(struct ClassBase *cb,struct ReadData *rd,ULONG w,ULONG h)
{ UWORD 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)
{ LONG (*decode)(struct ClassBase *,struct ReadData *,APTR);
  ULONG height,size;
  UBYTE *lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

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

  if ((rd->u.rp[0].BitMap=AllocBitMap(rd->width,height=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) < rd->padwidth)
        size = rd->padwidth;

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

        if (decode=decode_normal_v42,rd->comp)
          decode = decode_rle_v42;

        if ((ret=(*decode)(cb,rd,lbuf)) == 0) {

          SetDTAttrs(rd->obj, NULL, NULL,
                     PDTA_ModeID,      GetBestModeID(cb,rd,rd->width,height),
                     PDTA_BitMap,      rd->u.rp[0].BitMap,
                     DTA_NominalHoriz, rd->width,
                     DTA_NominalVert,  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)
*/

STATIC 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 or 24 bit)
*/

STATIC LONG decode_normal_v43(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ VOID (*convert)(UBYTE *,UBYTE *,ULONG);
  ULONG padwidth,height,size;
  UBYTE *buffer,*lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

  if ((size=rd->width) < (padwidth=rd->padwidth))
    size = padwidth;

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

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

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

    ret = 0;
  }

  return ret;
}

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

STATIC LONG decode_picture_v43(struct ClassBase *cb,Object *obj,struct ReadData *rd)
{ LONG (*decode)(struct ClassBase *,Object *,struct ReadData *);
  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 (decode=decode_normal_v43,rd->comp)
      decode = decode_rle_v43;
    ret = (*decode)(cb,obj,rd);
  }

  return ret;
}

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

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;
  LONG bfOffBits;
  LONG biSize;
  LONG biDepth;
  LONG biPlanes;
  LONG biCompression;
  LONG biClrUsed;

  /* 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,rd->u.buf,18) != 18)
    return FALSE;

  if (((UWORD)GET_2B(rd->u.buf,0)) != 0x4D42)
    return FALSE;

  bfOffBits = GET_4B(rd->u.buf,10) - ((biSize=GET_4B(rd->u.buf,14)) + 14);

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

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

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

  if (biPlanes != 1)
    return FALSE;

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

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

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

    if (biDepth != 24)
      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;

    val = RECTFMT_RGB;
  }

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

  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 ((val=bfOffBits) > 0)
    do
      if (FFRGetC(rd->handle) < 0) break;
    while (--val);

  return (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_fh] = 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_fh],
                 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_fh]) {

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

            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,v;
  UBYTE *cmap,*p,r,g,b;

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

  for(r=*cmap++,g=*cmap++,b=*cmap,cmap-=2; width; width--) {
    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;
    }
    *outbuf++ = b; *outbuf++ = g; *outbuf++ = r;
  }
}

/*
** convert 2 bytes into one
*/

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

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

/*
** convert 8 bytes into one
*/

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

  for(i=width/8; i!=0; *outbuf++=c,i--)
    for(c=0,j=0x80; j!=0; j>>=1)
      if (*pixelbuf++) c |= j;
  if ((i=width&7)) {
    for(c=0,j=0x80; i!=0; j>>=1,i--)
      if (*pixelbuf++) c |= j;
    *outbuf = c;
  }
}

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

STATIC LONG encode_picture_v42(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ VOID (*encode)(UBYTE *,UBYTE *,ULONG);
  ULONG *hd,b,d,height,padwidth;
  struct RastPort trp,rp;
  UBYTE *buffer,*lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

  InitRastPort(&rp); rp.BitMap = sd->bm; InitRastPort(&trp); d = GetBitMapAttr(sd->bm,BMA_DEPTH);

  if ((trp.BitMap=AllocBitMap(sd->width,1,d,BMF_MINPLANES,sd->bm)) != NULL) {

    if ((lbuf=AllocPooled(sd->pool,((sd->width+15)&~15))) != NULL) {

      if ((hd=AllocPooled(sd->pool,sizeof(struct hamData)+(padwidth=sd->padwidth))) != NULL) {

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

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

        buffer = (UBYTE *)hd; height = sd->height;
        do {
          ReadPixelLine8(&rp,0,--height,sd->width,lbuf,&trp);
          (*encode)(lbuf,buffer,sd->width);
          if (FFRWrite(sd->handle,buffer,padwidth) != padwidth)
            break;
        } while (height);

        if ((ret=height))
          ret = -1;
      }
    }
    FreeBitMap(trp.BitMap);
  }

  return ret;
}

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

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

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

    if (lbuf=buffer,encode=convert_24bit,sd->fmt != RECTFMT_RGB)
      if (encode=(VOID (*)())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;
      }

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

    if ((ret=height)) ret = -1; return ret;
  }

  return ERROR_NO_FREE_STORE;
}

/*
** setup and write bmp header
*/

STATIC LONG write_bmp_header(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ ULONG bfSize,bfOffBits,biDepth,cnt,nc;
  UBYTE *header,*coltab,*cmap;
  LONG ret;

  if (nc=0,biDepth=24,cnt=0,!sd->isHAM && sd->depth != biDepth) {
    if (nc=sd->numcolors,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_2B(header, 22, sd->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)
{ LONG (*encode)(struct ClassBase *,Object *,struct SaveData *);
  ULONG *methods,depth,mode,id;
  struct BitMapHeader *bmhd;
  struct SaveData sd;
  LONG error;

  if (dtw->dtw_FileHandle) {

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

    if (error=ERROR_NO_FREE_STORE,(sd.pool=CreatePool(MEMF_CLEAR,4*1024,4*1024)) != NULL) {

      if ((sd.handle=OpenFFR(cb,sd.pool,FFR_MODE_WRITE,dtw->dtw_FileHandle)) != NULL) {

        SetDTAttrs(obj, NULL, NULL, PDTA_SourceMode,PMODE_V43, TAG_DONE);

        sd.cmap = NULL; sd.numcolors = 0; sd.bm = NULL; mode = INVALID_ID; bmhd = NULL;

        GetDTAttrs(obj,
                   PDTA_BitMapHeader,   &bmhd,
                   PDTA_ModeID,         &mode,
                   PDTA_BitMap,         &sd.bm,
                   PDTA_NumColors,      &sd.numcolors,
                   PDTA_ColorRegisters, &sd.cmap,
                   TAG_DONE);

        error = ERROR_OBJECT_WRONG_TYPE;

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

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

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

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

            if ((error=write_bmp_header(cb,obj,&sd)) == 0) {

              if (id=~0,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;
      }
      DeletePool(sd.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;
}
