/*
** our include
*/

#include "classbase.h"

/*
** structure(s)
*/

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

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

struct SaveData {
  ULONG          depth,
                 width,
                 height,
                 padwidth,
                 numcolors;
  UBYTE         *cmap;
  struct BitMap *bm;
  APTR           pool,
                 handle;
  BYTE           isHAM,
                 isEHB,
                 pad[2];
};

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

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

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

#define PUT_2B(array,offset,value)  \
  { UWORD v = value; \
    array[offset] = (UBYTE)((v) & 0xff); \
    array[offset+1] = (UBYTE)((v>>=8) & 0xff); }

#define PUT_4B(array,offset,value)  \
  { ULONG v = value; \
    array[offset] = (UBYTE)((v) & 0xff); \
    array[offset+1] = (UBYTE)((v>>=8) & 0xff); \
    array[offset+2] = (UBYTE)((v>>=8) & 0xff); \
    array[offset+3] = (UBYTE)((v>>=8) & 0xff); }

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

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

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__ */

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

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

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

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

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

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
*/

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; j--) {
      *outbuf++ = ((c & 0x80) ? 1 : 0); c <<= 1;
    }
  if ((i=width%8)) {
    c = *pixelbuf;
    do {
      *outbuf++ = ((c & 0x80) ? 1 : 0); c <<= 1;
    } while (--i);
  }
}

/*
** handle 4 bit compression
*/

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

/*
** handle 8 bit compression
*/

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

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

LONG decode_rle_v42(struct ClassBase *cb,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,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(struct ClassBase *cb,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 = ((((width * depth) + 31) & ~31) / 8);

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

    convert = convert_8bit;
    if (depth != 8) {
      convert = decode_4bit;
      if (depth != 4)
        convert = decode_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(struct ClassBase *cb,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(struct ClassBase *cb,struct BitMapHeader *bmhd,APTR pool,Object *obj,APTR handle,ULONG title)
{ LONG (*decode)(struct ClassBase *,APTR,APTR,ULONG,ULONG,ULONG,UBYTE *,struct RastPort *,struct RastPort *);
  ULONG modeid,width,height,depth;
  struct RastPort trp,rp;
  UBYTE *lbuf;
  LONG ret = ERROR_NO_FREE_STORE;

  width = bmhd->bmh_Width; height = bmhd->bmh_Height; if ((depth=bmhd->bmh_Depth) > 8) depth = 8;

  InitRastPort(&rp);

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

    InitRastPort(&trp);

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

      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)(cb,pool,handle,width,height,depth,lbuf,&rp,&trp)) == 0) {

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

          SetDTAttrs(obj, NULL, NULL,
                     PDTA_BitMap,      rp.BitMap,
                     PDTA_ModeID,      modeid,
                     DTA_NominalHoriz, width,
                     DTA_NominalVert,  height,
                     DTA_ObjName,      title,
                     TAG_DONE);

          rp.BitMap = NULL;
        }
      }

      FreeBitMap(trp.BitMap);
    }

    FreeBitMap(rp.BitMap);
  }

  return ret;
}

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

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

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

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

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

  return ret;
}

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

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

  padwidth = ((((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 = decode_4bit;
        if (depth != 4) {
          convert = decode_1bit;
        }
      }
      rectfmt = RECTFMT_LUT8; if ((lbuf=AllocPooled(pool,width)) == NULL) return ret;
    }

    ret = -1;

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

    ret = 0;
  }

  return ret;
}

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

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

  level = 0; number = 0;

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

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

  return ret;
}

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

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

  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)
    return FALSE;

  if (headerSize == 8) { /* 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)
      return FALSE;
    cmapentrysize = 0;
  }

  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;

    SetDTAttrs(obj, NULL, NULL, PDTA_NumColors,(ULONG)biClrUsed, TAG_DONE);

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

    if ((coltab=AllocPooled(pool,coltabsize=biClrUsed*cmapentrysize)) == 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]; coltab += 2; cregs = (ULONG *)p[1]; cmapentrysize += 2;
    do {
      *cmap++ = (r = *  coltab); *cregs++ = (r *= 0x01010101);
      *cmap++ = (g = *--coltab); *cregs++ = (g *= 0x01010101);
      *cmap++ = (b = *--coltab); *cregs++ = (b *= 0x01010101);
      coltab += cmapentrysize;
    } while (--biClrUsed);
  }

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

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

/*
** load a picture
*/

LONG LoadBMP(struct ClassBase *cb,Object *obj)
{ LONG (*decode)(struct ClassBase *,struct BitMapHeader *,APTR,Object *,APTR,ULONG);
  struct BitMapHeader *bmhd;
  ULONG p[4],*methods,id;
  APTR pool,handle;
  LONG error;
  BPTR fh;

  error = ERROR_NO_FREE_STORE;

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

    error = 0; p[0] = 0; p[1] = 0; p[2] = DTST_FILE; p[3] = 0;

    GetDTAttrs(obj, DTA_Handle,&p[0],PDTA_BitMapHeader,&p[1],DTA_SourceType,&p[2],DTA_Name,&p[3], TAG_DONE);

    if (p[2] != DTST_RAM) {

      error = ERROR_OBJECT_WRONG_TYPE;

      if ((p[2] == DTST_FILE) && (bmhd=(struct BitMapHeader *)p[1]) && (fh=(BPTR)p[0])) {

        if ((handle=OpenFFR(cb,pool,FFR_MODE_READ,fh)) != NULL) {

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

            decode = decode_picture_v42;

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

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

              if (!id) decode = decode_picture_v43;
            }
            error = (*decode)(cb,bmhd,pool,obj,handle,p[3]);
          }
          (VOID)CloseFFR(handle);
        }
        else
          error = ERROR_NO_FREE_STORE;
      }
    }
    DeletePool(pool);
  }

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

  return TRUE;
}

/*
** convert HAM to 24bit
*/

VOID encode_ham(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*sizeof(UBYTE); 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 EHB to 24bit
*/

VOID encode_ehb(UBYTE *pixelbuf,UBYTE *outbuf,ULONG width)
{ UBYTE *cmap,*p,r,g,b;
  ULONG max,pen;

  for(max=HD.max,cmap=HD.cmap; width; width--) {
    if ((pen=*pixelbuf++) < max) {
      p = &cmap[pen*3]; r = *p++; g = *p++; b = *p;
    }
    else {
      p = &cmap[(pen-=max)*3]; r = *p++; r >>= 1; g = *p++; g >>= 1; b = *p; b >>= 1;
    }
    *outbuf++ = b; *outbuf++ = g; *outbuf++ = r;
  }
}

/*
** convert 2 bytes into one
*/

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
*/

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

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

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

LONG encode_picture_v42(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ VOID (*encode)(UBYTE *,UBYTE *,ULONG);
  struct RastPort trp,rp;
  UBYTE *lbuf,*buffer;
  ULONG b,d,padwidth;
  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 ((buffer=AllocPooled(sd->pool,sizeof(struct hamData)+(padwidth=sd->padwidth))) != NULL) {

        { LREG(a0,ULONG *hd) = (ULONG *)buffer;

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

          buffer = (UBYTE *)hd;
        }

        encode = encode_ham;
        if (!sd->isHAM) {
          encode = encode_ehb;
          if (!sd->isEHB) {
            encode = convert_8bit;
            if (sd->depth != 8) {
              encode = encode_4bit;
              if (sd->depth != 4)
                encode = decode_1bit;
            }
          }
        }

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

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

  return ret;
}

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

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

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

    lbuf = buffer; rectfmt = RECTFMT_RGB; encode = convert_24bit;
    if (sd->depth != 24) {
      encode = convert_8bit;
      if (sd->depth != 8) {
        encode = encode_4bit;
        if (sd->depth != 4) {
          encode = encode_1bit;
        }
      }
      rectfmt = RECTFMT_LUT8; if ((lbuf=AllocPooled(sd->pool,sd->width)) == NULL) return ret;
    }

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

    if (sd->height != ret)
      ret = -1;
  }

  return ret;
}

/*
** setup and write a bmp header
*/

LONG write_bmp_header(struct ClassBase *cb,Object *obj,struct SaveData *sd)
{ LONG bfSize,headerSize,biBitCount,ret;
  UBYTE *header,*coltab,*cmap;
  ULONG nc;

  if (!sd->isHAM && !sd->isEHB) {
    nc = sd->numcolors; biBitCount = 1;
    if (sd->depth > biBitCount) {
      biBitCount = 4;
      if (sd->depth > biBitCount) {
        biBitCount = 8;
        if (sd->depth > biBitCount) {
          biBitCount = 24; nc = 0;
        }
      }
    }
  }
  else {
    biBitCount = 24; nc = 0;
  }
  sd->padwidth = (((sd->width * (sd->depth=biBitCount)) + 31) & ~31) / 8;

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

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

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

    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, biBitCount);

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

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

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

  return ret;
}

/*
** save a picture
*/

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

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

  if (dtw->dtw_FileHandle) {

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

    error = ERROR_NO_FREE_STORE;

    if ((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.depth=bmhd->bmh_Depth) > 8 || (sd.bm && sd.numcolors && sd.cmap))) {

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

          sd.width = bmhd->bmh_Width; sd.height = bmhd->bmh_Height; error = ERROR_NOT_IMPLEMENTED;

          if (bmhd->bmh_Compression == cmpNone)

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

              if (!sd.isHAM && !sd.isEHB && (methods=GetDTMethods(obj)) != NULL) {
                do
                  id = *methods++;
                while ((id != ~0) && (id-=PDTM_READPIXELARRAY));
              }
              else
                id = ~0;
              encode = encode_picture_v42; if (!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 (!LoadBMP(cb,obj))
          { CoerceMethod(cl,obj,OM_DISPOSE); 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;
}
