/**************************************************************************
*                                                                         *
*   iffpack                                                               *
*                                                                         *
*   Routines for loading and saving pictures with windows                 *
*   using the old formats and the new compression format                  *
*                                                                         *
**************************************************************************/
#include <intuition/intuition.h>
#include <functions.h>
#include <stdio.h>

#define NOERRORTEXT
#include "iffpack.h"

#define BADFLAGS (SPRITES|VP_HIDE|GENLOCK_AUDIO|GENLOCK_VIDEO)
#define FLAGMASK (~BADFLAGS)
#define CAMGMASK (FLAGMASK & 0xffffL)

struct BMHD {
   ULONG  Size;
   USHORT Width,Height;
   SHORT  LeftEdge,TopEdge;
   UBYTE  Depth;
   UBYTE  Mask;
   UBYTE  Compression;
   UBYTE  Pad;
   USHORT Transparent;
   UBYTE  XAspect,YAspect;
   SHORT  ScrWidth,ScrHeight;
};

static USHORT buf[256];
static short count,runlen,ptr;

static short rows,bytes,depth;
static cmode;

static short TempY=0,TempX=0;
static struct BitMap MyBm;
static struct RastPort MyRast;
static short anzcolors;
static USHORT colors[32];

/**************************************************************************
*                                                                         *
*  SetColors(vp):                                                         *
*                                                                         *
*  Set the colors, that were previously loaded with ReadPicSize           *
*                                                                         *
*  vp: Viewport of the Screen                                             *
*                                                                         *
**************************************************************************/
SetColors(vp)
struct ViewPort *vp;
{
   LoadRGB4(vp,colors,(long)anzcolors);
}

/**************************************************************************
*                                                                         *
*  ReadPicSize(fp,winx,winy,srcx,srcy,depth,vmode                         *
*                                                                         *
*  Read the size and viewmode of the picture and load the colors to an    *
*  internal buffer                                                        *
*                                                                         *
*  fp:        FILE-Pointer of stdio                                       *
*  winx,winy: references to the size of the window                        *
*  srcx,srcy: references to the size of the screen                        *
*  depth:     reference to the depth of the screen                        *
*  vmode:     reference to Viewmode                                       *
*                                                                         *
*  ReadPicSize sets the values of the referenced parameters               *
*                                                                         *
*  the result of ReadPicSize is 0 if no error occured or an error-code    *
*  defined in iffpack.h                                                   *
*                                                                         *
**************************************************************************/

ReadPicSize(fp,winx,winy,scrx,scry,depth,vmode)
FILE *fp;
SHORT *winx,*winy;
SHORT *scrx,*scry;
SHORT *depth;
USHORT *vmode;
{
   struct BMHD bmhd;
   int err;
   if(err=ReadHeader(fp,&bmhd,vmode)) return(err);
   *winx=bmhd.Width;
   *winy=bmhd.Height;
   *scrx=bmhd.ScrWidth;
   *scry=bmhd.ScrHeight;
   *depth=bmhd.Depth;
   return(0);
}

/**************************************************************************
*                                                                         *
*  ReadBody: read the bitmap data to a rastport                           *
*                                                                         *
*  rp: pointer to rastport                                                *
*                                                                         *
*  fp: FILE-Pointer of stdio                                              *
*                                                                         *
**************************************************************************/

ReadBody(rp,fp)
struct RastPort *rp;
FILE *fp;
{
   long size;
   short x,y,p;
   int c;

   if(!fread(&size,4,1,fp)) return(BAD_IFF);

   if(cmode==0) {  /* Compression mode 0: no compression */
      for(y=0;y<rows;y++) {
         for(p=0;p<depth;p++) {
            for(x=0;x<bytes;x++) {
               if((c=getc(fp))==EOF) return(BAD_IFF);
               MyBm.Planes[p][x]=c;
            }
         }
         ClipBlit(&MyRast,0L,0L,rp,0L,(long)y,(long)TempX,1L,192L);
         WaitBlit();
      }
   } else if(cmode==1) {  /* Compression mode 1: normal compression */
      for(y=0;y<rows;y++) {
         for(p=0;p<depth;p++) {
            for(x=0;x<bytes;x++) {
               if((c=compget(fp))==EOF) return(BAD_IFF);
               if(c<256) {
                  MyBm.Planes[p][x]=c;
               }
            }
         }
         ClipBlit(&MyRast,0L,0L,rp,0L,(long)y,(long)TempX,1L,192L);
         WaitBlit();
      }
   } else if(cmode==2) { /* Compression mode 2: new compression */
      for(x=0;x<bytes;x++) {
         for(p=0;p<depth;p++) {
            for(y=0;y<rows;y++) {
               if((c=compget(fp))==EOF) return(BAD_IFF);
               if(c<256) {
                  MyBm.Planes[p][y<<1]=c;
               }
            }
         }
         ClipBlit(&MyRast,0L,0L,rp,(long)(x<<3),0L,8L,(long)TempY,192L);
         WaitBlit();
      }
   }
   IFFCleanup();
   return(0);
}

/**************************************************************************
*                                                                         *
*  WriteWindow(fp,w,mode):                                                *
*                                                                         *
*  Write the window win to the file with FILE-Pointer fp using the        *
*  compression method mode                                                *
*                                                                         *
**************************************************************************/

WriteWindow(fp,w,mode)
FILE *fp;
struct Window *w;
short mode;
{
   if(mode<0 || mode>2) return(UNKNOWN_COMPRESSION);
   if(WriteBMHD(w,fp,mode)) return(WRITE_ERROR);
   if(WriteCMAP(w->WScreen,fp)) return(WRITE_ERROR);
   if(WriteCAMG(w->WScreen,fp)) return(WRITE_ERROR);
   if(initcomp((short)w->Width,(short)w->Height,(short)w->WScreen->BitMap.Depth,
          mode)) return(NO_MEMORY);
   if(WriteBODY(w,fp,mode)) return(WRITE_ERROR);
   IFFCleanup();
   return(0);
}

/**************************************************************************
*                                                                         *
*  IFF-Cleanup: frees all allocated memory                                *
*                                                                         *
**************************************************************************/

IFFCleanup()
{
   short a;
   if(TempX) {
      for(a=0;a<8;a++) {
         if(MyBm.Planes[a])
                 FreeRaster(MyBm.Planes[a],(long)TempX,(long)TempY);
      }
   }
   TempX=TempY=0;
}


/***********************************
*                                  *
*  internal functions:             *
*                                  *
***********************************/

/*  ReadHeader: read size-information, viewmode and colors  */

static ReadHeader(fp,bmhd,viewmode)
FILE *fp;
struct BMHD *bmhd;
USHORT *viewmode;
{
   short status=0;
   long id[3],sid,size;
   if(!fread(id,12,1,fp)) return(BAD_IFF);
   if(id[0]!='FORM') return(BAD_IFF);
   if(id[2]!='ILBM') return(BAD_IFF);
   *viewmode=0;
   do {
      if(!fread(&sid,4,1,fp)) return(BAD_IFF);
      if(sid=='CMAP') {
         if(status&1) return(BAD_IFF); /* doppelt */
         if(ReadCMAP(fp)) return(BAD_IFF);
         status|=1;
      } else if(sid=='CAMG') {
         if(status&2) return(BAD_IFF); /* doppelt */
         if(ReadCAMG(fp,viewmode)) return(BAD_IFF);
         status|=2;
      } else if(sid=='BMHD') {
         if(status&4) return(BAD_IFF); /* doppelt */
         if(ReadBMHD(fp,bmhd,viewmode)) return(BAD_IFF);
         status|=4;
      } else if(sid!='BODY') {
         if(!fread(&size,4,1,fp)) return(BAD_IFF);
         size+=size&1;
         if(fseek(fp,size,1)) return(BAD_IFF);
      }
   } while(sid!='BODY');
   if(!(status&1) || !(status&4)) return(BAD_IFF); /* Daten fehlen */
   if(initcomp((short)bmhd->Width,(short)bmhd->Height,(short)bmhd->Depth,
            (short)bmhd->Compression)) return(NO_MEMORY);
   rows=bmhd->Height;
   bytes=((bmhd->Width+15)>>3)&0xfffe;
   depth=bmhd->Depth;
   cmode=bmhd->Compression;
   if(cmode<0 || cmode>2) return(UNKNOWN_COMPRESSION);
   return(0);
}

/* Read chunk BMHD */

static ReadBMHD(fp,bmhd,vmode)
FILE *fp;
struct BMHD *bmhd;
USHORT *vmode;
{
   if(!fread(bmhd,sizeof(struct BMHD),1,fp)) return(BAD_IFF);
   if(bmhd->Size!=20) return(BAD_IFF);
   *vmode=0;
   if(bmhd->Width>400 && bmhd->Depth<=4) *vmode|=HIRES;
   if(bmhd->Height>300) *vmode|=LACE;
   if(bmhd->Depth>6) *vmode|=HAM;
   return(0);
}

/* read colormap */

static ReadCMAP(fp)
FILE *fp;
{
   long size;
   short a;
   int r,g,b;
   if(!fread(&size,4,1,fp)) return(1);
   if(size % 3) return(1);
   size/=3;
   anzcolors=size;
   for(a=0;a<size;a++) {
      if((r=getc(fp))==EOF) return(1);
      if((g=getc(fp))==EOF) return(1);
      if((b=getc(fp))==EOF) return(1);
      r&=0xf0;
      g&=0xf0;
      b&=0xf0;
      colors[a]=(r<<4)|g|(b>>4);
   }
   return(0);
}

/* read viewmode */

static ReadCAMG(fp,vmode)
FILE *fp;
USHORT *vmode;
{
   long a;
   long size;
   if(!fread(&size,4,1,fp)) return(1);
   if(size!=4) return(1);
   if(!fread(&a,4,1,fp)) return(1);
   *vmode=a&CAMGMASK;
   return(0);
}

/*  write BMHD */

static WriteBMHD(w,fp,mode)
struct Window *w;
FILE *fp;
short mode;
{
   struct BMHD bm;
   if(!fwrite("FORM    ILBMBMHD",16,1,fp)) return(1);
   bm.Size=20;
   bm.Width=w->Width;
   bm.Height=w->Height;
   bm.ScrWidth=w->WScreen->Width;
   bm.ScrHeight=w->WScreen->Height;
   bm.LeftEdge=bm.TopEdge=0;
   bm.Depth=w->WScreen->BitMap.Depth;
   bm.Mask=bm.Pad=0;
   bm.Compression=mode;
   bm.Transparent=0;
   bm.YAspect=11;
   bm.XAspect=10;
   if((w->WScreen->ViewPort.Modes&LACE) && !(w->WScreen->ViewPort.Modes&HIRES)) bm.XAspect=20;
   if(!(w->WScreen->ViewPort.Modes&LACE) && (w->WScreen->ViewPort.Modes&HIRES)) bm.XAspect=5;
   if(!fwrite(&bm,sizeof(struct BMHD),1,fp)) return(1);
   return(0);
}

/* write colormap */

static WriteCMAP(s,fp)
struct Screen *s;
FILE *fp;
{
   long size;
   short anz,col;
   short i;
   int c;

   if(!fwrite("CMAP",4,1,fp)) return(1);
   anz=s->BitMap.Depth;
   anz=1<<anz;
   if(anz>32) anz=32;
   size=3*anz;
   if(!fwrite(&size,4,1,fp)) return(1);

   for(i=0;i<anz;i++) {
      col=GetRGB4(s->ViewPort.ColorMap,(long)i);
      c=((col>>8)&0xf)<<4;
      if(putc(c,fp)==EOF) return(1);
      c=((col>>4)&0xf)<<4;
      if(putc(c,fp)==EOF) return(1);
      c=(col&0xf)<<4;
      if(putc(c,fp)==EOF) return(1);
   }
   return(0);
}

/* write viewmode */

static WriteCAMG(s,fp)
struct Screen *s;
FILE *fp;
{
   long a;
   if(!fwrite("CAMG",4,1,fp)) return(1);
   a=4;
   if(!fwrite(&a,4,1,fp)) return(1);
   a=s->ViewPort.Modes;
   a&=CAMGMASK;
   if(!fwrite(&a,4,1,fp)) return(1);
   return(0);
}

/* write bitmaps */

static WriteBODY(w,fp,mode)
struct Window *w;
FILE *fp;
short mode;
{
   struct RastPort *rp;
   struct BitMap *bm;
   short bytes,rows;
   long pos;
   long size,bodysize,depth;
   short x,y,p;

   UBYTE *plane;

   rp=w->RPort;

   if(!fwrite("BODY    ",8,1,fp)) return(1);
   pos=ftell(fp);

   bm=MyRast.BitMap;

   bytes=((w->Width+15)>>3) & 0xfffe;
   rows=w->Height;
   depth=bm->Depth;

   if(mode==2) {
      for(x=0;x<bytes;x++) {
         if(x) {
            ClipBlit(rp,(x-1L)<<3L,0L,&MyRast,0L,0L,16L,(long)TempY,192L);
            WaitBlit();
         } else {
            ClipBlit(rp,0L,0L,&MyRast,8L,0L,8L,(long)TempY,192L);
            WaitBlit();
         }
         for(p=0;p<depth;p++) {
            if(comprow(fp,rows,x,p)) return(1);
         }
      }
   } else if(mode==1) {
      for(y=0;y<rows;y++) {
         if(y) {
            ClipBlit(rp,0L,(long)y-1L,&MyRast,0L,0L,(long)TempX,2L,192L);
            WaitBlit();
         } else {
            ClipBlit(rp,0L,0L,&MyRast,0L,1L,(long)TempX,1L,192L);
            WaitBlit();
         }
         for(p=0;p<depth;p++) {
            if(compline(fp,bytes,y,p)) return(1);
         }
      }
   } else {
      for(y=0;y<rows;y++) {
         ClipBlit(rp,0L,(long)y,&MyRast,0L,0L,(long)TempX,1L,192L);
         WaitBlit();
         for(p=0;p<depth;p++) {
            plane=MyBm.Planes[p];
            for(x=0;x<bytes;x++) {
               if(putc((int)(plane[x]),fp)==EOF) return(1);
            }
         }
      }
   }

   size=ftell(fp);
   bodysize=size-pos;
   if(fseek(fp,pos-4L,0)) return(1);
   if(!fwrite(&bodysize,4,1,fp)) return(1);
   if(fseek(fp,4L,0)) return(1);
   size-=8;
   if(!fwrite(&size,4,1,fp)) return(1);
   return(0);
}

/* compress one line */

static compline(fp,bytes,y,p)
FILE *fp;
short bytes;
short y,p;
{
   short x,x2,x3,lx;
   short found;
   short a;
   UBYTE * ptr;
   UBYTE *lptr;

   ptr=&MyBm.Planes[p][bytes];
   lptr=MyBm.Planes[p];
   lx=0;
   for(x=0;x<bytes;x++) {
      x2=x;
      found=0;  /* count bytes with equal value */
      while(ptr[x2]==ptr[x2+1] && x2<bytes-1 && x2-x<127) x2++;
      x2++;
      if((x-lx)==0 && x2-x>=2 || x2-x>=3) {
         if(x-lx) {
            /* save uncompressed bytes */
            if(putc((int)(x-lx-1),fp)==EOF) return(1);
            for(a=lx;a<x;a++) if(putc((int)ptr[a],fp)==EOF) return(1);
         }
         /* save compressed bytes */
         if(putc(x-x2+1,fp)==EOF) return(1);
         if(putc((int)ptr[x],fp)==EOF) return(1);
         x=x2-1;
         found=1;
      }
      if(found) {
         lx=x+1;
      } else {
         if(x-lx==127) {
            /* save uncompressed bytes */
            if(fputc(127,fp)==EOF) return(1);
            for(a=lx;a<=x;a++) if(putc(ptr[a],fp)==EOF) return(1);
            lx=x+1;
         }
      }
   }
   if(lx<bytes) {
      if(putc(bytes-lx-1,fp)==EOF) return(1);
      for(a=lx;a<bytes;a++) if(putc((int)ptr[a],fp)==EOF) return(1);
   }
   return(0);
}

/* compress one column (width: 8 bit) */

static comprow(fp,rows,x,p)
FILE *fp;
short rows;
short x,p;
{
   short y,y2,y3,ly;
   short found;
   short a;
   UBYTE * ptr;

   ptr=MyBm.Planes[p];
   ly=0;
   for(y=0;y<rows;y++) {
      y3=y2=y;
      found=0;   /* count bytes with equal value */
      while(ptr[(y2<<1)+1]==ptr[(y2<<1)+3] && y2<rows-1 &&
                                                    y2-y<127) y2++;
      y2++;
      if(x) {
         /* compare with previous column */
         while(ptr[y3<<1]==ptr[(y3<<1)+1] && y3<rows &&
                                                    y3-y<256) y3++;
      }
      if(y2>=y3) {
         if(y-ly==0 && y2-y>=2 || y2-y>=3) {
            if(y-ly) {
               /* write uncompressed data */
               if(putc(y-ly-1,fp)==EOF) return(1);
               for(a=ly;a<y;a++)
                  if(putc((int)ptr[(a<<1)+1],fp)==EOF) return(1);
            }
            /* write compressed data: Byte-Run */
            if(putc(y-y2+1,fp)==EOF) return(1);
            if(putc((int)ptr[(y<<1)+1],fp)==EOF) return(1);
            y=y2-1;
            found=1;
         }
      } else {
         if(!y-ly && y3-y>=2 || y3-y>=3) {
            if(y-ly) {
               /* write uncompressed data */
               if(putc(y-ly-1,fp)==EOF) return(1);
               for(a=ly;a<y;a++)
                  if(putc((int)ptr[(a<<1)+1],fp)==EOF) return(1);
            }
            /* write compressed data: copy previous column */
            if(putc(128,fp)==EOF) return(1);
            if(putc(y3-y-1,fp)==EOF) return(1);
            y=y3-1;
            found=1;
         }
      }
      if(found) {
         ly=y+1;
      } else {
         if(y-ly==127) {
            /* write uncompressed data */
            if(fputc(127,fp)==EOF) return(1);
            for(a=ly;a<=y;a++)
                  if(putc(ptr[(a<<1)+1],fp)==EOF) return(1);
            ly=y+1;
         }
      }
   }
   if(ly<rows) {
      if(putc(rows-ly-1,fp)==EOF) return(1);
      for(a=ly;a<rows;a++)
         if(putc((int)ptr[(a<<1)+1],fp)==EOF) return(1);
   }
   return(0);
}

/* init compression */

static initcomp(width,height,depth,mode)
short width,height,depth,mode;
{
   short a;
   if(TempX) IFFCleanup();
   if(mode!=2) {
      TempX=width;
      TempY=2;
      InitRastPort(&MyRast);
      InitBitMap(&MyBm,(long)depth,(long)width,2L);
      for(a=0;a<8;a++) MyBm.Planes[a]=0;
      for(a=0;a<depth;a++) {
         if(!(MyBm.Planes[a]=AllocRaster((long)width,2L))) return(NO_MEMORY);
      }
   } else {
      TempX=16;
      TempY=height;
      InitRastPort(&MyRast);
      InitBitMap(&MyBm,(long)depth,16L,(long)height);
      for(a=0;a<8;a++) MyBm.Planes[a]=0;
      for(a=0;a<depth;a++) {
         if(!(MyBm.Planes[a]=AllocRaster(16L,(long)height))) return(NO_MEMORY);
      }
   }
   MyRast.BitMap=&MyBm;
   count=ptr=0;
   runlen=1;
   return(0);
}

/* uncompress bitplanes */

static compget(fp)
FILE *fp;
{
   int c;
   char cc;
   UBYTE val;
   if(ptr>=count) {
      if((c=getc(fp))==EOF) return(EOF);
      cc=c;
      ptr=0;
      if(c==128) {   /* code for copying columns */
         if((c=getc(fp))==EOF) return(EOF);
         count=c+1;  /* count shorts of 256 (value indicating copy mode) */
         for(c=0;c<count;c++) buf[c]=256;
      } else if(cc>=0) {  /* read uncompressed data */
         count=cc+1;
         for(c=0;c<count;c++) if((buf[c]=getc(fp))==EOF) return(EOF);
      } else {
         count=-cc+1;   /* read compressed data and uncompress it */
         if((c=getc(fp))==EOF) return(EOF);
         val=c;
         for(c=0;c<count;c++) buf[c]=val;
      }
   }
   if(ptr>=count) return(EOF);
   return(buf[ptr++]);
}


