/**************************************************************************/
/* iff.c                                                       26.01.88   */
/* Version 3.0                                                            */
/*                                                                        */
/*                                                                        */
/* Lade- und Speicherroutine fuer Bilder im IFF-Format                    */
/*                                                                        */
/* fuer Markt+Technik geschrieben von Heiko Knappe                        */
/*                                                                        */
/*                                                                        */
/* Die Routinen sind so geschrieben, dass sie ohne Schwierigkeiten in ein */
/* groesseres Programm eingebunden  werden koennen.  Auch bei der Auswer- */
/* tung  anderer  IFF-Formate  sind sie   sicher hilfreich.  Geladen  und */
/* gespeichert  werden  koennen  sowohl 'uncompressed' (z.B.Graphicraft), */
/* als auch 'compressed' (z.B. Deluxe-Paint) Formate.  Die  verschiedenen */
/* Aufloesungen und Display-Modes werden beruecksichtigt! Auch HAM-Bilder */
/* koennen geladen und gespeichert werden werden!                         */
/* Der neu geoeffnete Screen bleibt beim Laden dunkel (bei HAM natuerlich */
/* nicht).  Die Farben werden erst nach Enden des Diskzugriffs aufgeblen- */
/* det.                                                                   */
/**************************************************************************/


#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>


#define BMHDChunk 1
#define CMAPChunk 2
#define BODYChunk 3
#define CAMGChunk 4
#define Unknown   5

#define MEM_SIZE 20000 /* Buffer fuer Speichern                   */

struct CMAPC          /* Definition des CMAP-Chunks               */
{
 int anz;             /* Anzahl der Farben                        */
 struct ColorRegister /* Definition einer Farbe                   */
 {
  UBYTE red;
  UBYTE green;
  UBYTE blue;
 } Color[64];         /* lieber zuviel als zuwenig                */
};

struct FORMC          /* Definition des Anfang-Chunks             */
{
 char  FORMName[4];   /* handelt es sich um ein IFF-Format?       */
 long  len;           /* Gesamte Laenge des Files                 */
 char  ILBM[4];       /* handelt es sich um ILBM-Format?          */
};

struct BMHDC          /* Definition des BMHD                      */
{
 char  BMHDName[4];
 long  lenBMHD;       /* Laenge des BMHD-Chunks                   */
 short Width;         /* Breite des Bildes                        */
 short Height;        /* Hoehe des Bildes                         */
 short xpos;          /* x-Position des Bildes                    */
 short ypos;          /* y-Position des Bildes                    */
 char  Depth;         /* Anzahl der BitPlanes                     */
 char  mask;          /* Mask-Bit                                 */
 char  compression;   /* Bild 'compressed'?                       */
 char  null;
 short color;
 char  xaspect;
 char  yaspect;
 short PWidth;
 short PHeight;
};

struct CHUNKID        /* hier werden die zu analysierenden Bytes  */
{                     /* abgelegt                                 */
 char  Name[4];       /* Name des Chunks                          */
 int   len;           /* Laenge des Chunks                        */
};

struct HEADER         /* diese Struktur enthaelt die Zeichenfolge */
{                     /* um den Chunk zu erkennen.                */
 char bmhd[4];
 char cmap[4];
 char body[4];
 char camg[4];
};

struct CAMGC          /* Definition des CAMG                      */
{
 char CAMGName[4];
 long lenCAMG;
 long ViewModes;
};

struct CMAPC CMAPl;
struct FORMC FORMl;
struct BMHDC BMHDl;
struct CHUNKID CHUNKid;
struct FORMC FORMs = {'F','O','R','M',0,'I','L','B','M'};
struct BMHDC BMHDs = {'B','M','H','D',20,0,0,0,0,0,2,0,0,0,10,11,0,0};
struct CAMGC CAMGs = {'C','A','M','G',4,0};
struct HEADER Header=
{
 'B','M','H','D',    /* BMHD-Chunk                                */
 'C','M','A','P',    /* CMAP-Chunk                                */
 'B','O','D','Y',    /* BODY-Chunk                                */
 'C','A','M','G',    /* CAMG-Chunk                                */
};

char cmap[]="CMAP";
char body[]="BODY";
char form[]="FORM";
char ilbm[]="ILBM";

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct ViewPort *MyViewPort;
struct RastPort *MyRastPort;
struct BitMap *MyBitMap;
struct Screen *myscr;
struct NewScreen mynewscr=
 {0,0,320,200,2,0,1,0,CUSTOMSCREEN,0,0,0,0};

long file,end,*waste,laenge;
BYTE *plane[6],*speicher,*zeiger,*zeiger0;



/*********************/
/* Laderoutinen      */
/*********************/

/* Aufblenden der Farben */
blendup()
{
 /* Bild geladen -> Farben setzen */
 static float r[64],g[64],b[64];
 static int i,j;

 for(i=0;i<64;++i)
 {
    r[i]=(CMAPl.Color[i].red)/16;    /* Aufblenden in 16 Schritten */
    g[i]=(CMAPl.Color[i].green)/16;
    b[i]=(CMAPl.Color[i].blue)/16;
 }    
 for(j=1;j<=16;++j)
    for(i=0;i<CMAPl.anz;++i)
       SetRGB4(MyViewPort,i,(int)(r[i]*j/16),
                (int)(g[i]*j/16),(int)(b[i]*j/16));
}


/* Analyse des Headers */
/* fname: zeigt auf den Namen des Bild-Files                         */
/* scr: zeigt auf den Screen in den das Bild geladen werden soll.    */
/*      Steht hier eine Null, so wird ein neuer Screen geoeffnet.    */
/*      Dieser bleibt dann solange offen, bis dieser herunterge-     */
/*      zogen wird. Das Programm kehrt erst dann zurueck. Tritt      */
/*      beim Laden kein Fehler auf so wird eine Null uebergeben.     */
/*      folgende Fehler sind moeglich:                               */
/*        Fehler 1: File kann nicht geoeffnet werden.                */
/*        Fehler 2: kein IFF-File.                                   */
/*        Fehler 3: Screen bzw. Libs koennen nicht geoeffnet werden. */
load(fname,scr)
char *fname;
struct Screen *scr;
{
 static short id;
 static BYTE wait=1;
 static int i;

 if(scr!=0)
 {
    myscr=scr;
    wait=0;
 }
 if(init_load_file(fname)==0)
    return(1);
 end=read_file(&FORMl,sizeof(FORMl));
 if((strncmp(&FORMl.FORMName,&form,4)!=0)||(strncmp(&FORMl.ILBM,&ilbm,4)!=0))
 {
   close_load_file();
   return(2);
 }
 while(end!=0)
 {
    end=read_file(&CHUNKid,sizeof(CHUNKid));
    if(end==8) id=test();
    else {end=0; break;};
    switch(id)        /* Welcher Chunk liegt vor? */
    {
    case BMHDChunk:
            BMHDl.lenBMHD=CHUNKid.len;
            end=read_file(&BMHDl.Width,CHUNKid.len);
            if((end<CHUNKid.len)) break;
            if(CHUNKid.len<(sizeof(BMHDl)-12))
            {
               close_load_file();
               return(2);
            }
            mynewscr.Width    =BMHDl.Width;
            mynewscr.Depth    =BMHDl.Depth;
            mynewscr.Height   =BMHDl.Height;
            mynewscr.LeftEdge =BMHDl.xpos;
            mynewscr.TopEdge  =BMHDl.ypos;
            if(mynewscr.ViewModes==0) /* ViewMode schon gesetzt? */
            {
               if(mynewscr.Width>320) mynewscr.ViewModes+=HIRES;
               if(mynewscr.Height>250) mynewscr.ViewModes+=LACE;
               if(mynewscr.Depth==6) mynewscr.ViewModes+=HAM;
            }
            break;
    case CMAPChunk:
            CMAPl.anz=CHUNKid.len/3;
            for(i=0;i<CMAPl.anz;++i)
               end=read_file(&(CMAPl.Color[i].red),3);
            break;
    case BODYChunk:
            open_();
            get_pic();
            break;
    case CAMGChunk:
            end=read_file(&mynewscr.ViewModes,2);
            end=read_file(&mynewscr.ViewModes,2); /* erst hier richtig */
            break;
    case Unknown:
            if((waste=AllocMem(CHUNKid.len,MEMF_CLEAR))==0)
              {end=0; break;}
            end=read_file(waste,CHUNKid.len);
            FreeMem(waste,CHUNKid.len);
            break;
    }
 }  
 if(wait==1)
 {
    blendup();
    while(myscr->TopEdge<1);    /* Und, wie ist das Bild? */
    close_();
 }
 close_load_file();
 return(0);
}


/* Um welchen Chunk handelt es sich? */
test()
{
 static char *p;
 static short i;

 for(i=0,p=&Header;
     (i<sizeof(Header)/4)&&(strncmp(p,&CHUNKid.Name,4)!=0);++i,p+=4);
 return(i+1);
}


/* Laden der BitPlanes */
get_pic()
{
 static int i,j;
 static short count;
 static char *next,*next0,*n;

 if(BMHDl.compression!=0)         /* File compressed */
 {
    for(i=0;(i<BMHDl.Height)&&(end!=0);++i)
    {
       for(j=0;j<BMHDl.Depth;++j)
       {
          next0=next=plane[j]+BMHDl.Width/8*i;
          while((next-next0)<BMHDl.Width/8)
          {
             end=read_file(next,1);
             if(*next==-128) continue;  /* noop */
             if(*next>=0)
             {  
                count=1+(*next);
                end=read_file(next,count);
                next+=count;
                continue;
             }   
             if((*next<0)&&(*next!=-128))
             {
                count=1-(*next);
                end=read_file(next,1);
                for(n=next;count>0;--count,*next=*n,++next);
                continue;
             }
          }

       }
    }
 }
 else
 {
    for(i=0;i<BMHDl.Height;++i)
       for(j=0;j<BMHDl.Depth;++j)
          end=read_file(plane[j]+BMHDl.Width/8*i,BMHDl.Width/8);
 }
}


/* Oeffnen des Screens bzw. der Libs */
open_()
{
 static int i;

 if(IntuitionBase==0)
    if((IntuitionBase=OpenLibrary("intuition.library",0))==0)
    {
       close_();
       return(3);
    }
 if(GfxBase==0)
    if((GfxBase=OpenLibrary("graphics.library",0))==0)
    {
       close_();
       return(3);
    }
 if(myscr==0)
    if((myscr=OpenScreen(&mynewscr))==0)
    {
       close_();
       return(3);
    }
 MyViewPort=&(myscr->ViewPort);
 MyRastPort=&(myscr->RastPort);
 for(i=0;i<CMAPl.anz;++i)    /* Screen zuerst dunkel */
    SetRGB4(MyViewPort,i,0,0,0);
 MyBitMap=MyRastPort->BitMap;
 for(i=0;i<BMHDl.Depth;++i)
    plane[i]=MyBitMap->Planes[i];
}


/* Schliessen des Screens bzw. der Libs */
close_()
{
 if(IntuitionBase!=0) CloseLibrary(IntuitionBase);
 if(GfxBase!=0)       CloseLibrary(GfxBase);
 if(myscr!=0)      CloseScreen(myscr);
 IntuitionBase=GfxBase=myscr=0;
}

init_load_file(fname)
char *fname;
{
 if((laenge=filelaenge_(fname))==0)
    return(0);
 if((file=Open(fname,MODE_OLDFILE))==0)
    return(0);
 if((speicher=AllocMem(laenge,MEMF_CLEAR))==0)
    return(0);
 Read(file,speicher,laenge);
 zeiger=zeiger0=speicher;
 return(1);
}

filelaenge_(fname)
char *fname;
{
 static struct FileInfoBlock *MyInfoBlock;
 static long *MyLock,help,l;

 if((MyLock=Lock(fname,ACCESS_READ))==0)
    return(0);
 if((help=AllocMem(1000,MEMF_CLEAR))==0)
    return(0);
 MyInfoBlock=help+help%4;
 if((Examine(MyLock,MyInfoBlock))==0)
 {
    UnLock(MyLock);
    FreeMem(help,1000);
    return(0);
 }
 UnLock(MyLock);
 l=MyInfoBlock->fib_Size;
 FreeMem(help,1000);
 return(l);
}

close_load_file()
{
 if(speicher!=0) FreeMem(speicher,laenge);
 if(file!=0) Close(file);
}

read_file(p,anz)
BYTE *p;
int anz;
{
 while((zeiger<=speicher+laenge)&&(zeiger-zeiger0<anz))
 {
    *p=*zeiger;
    ++zeiger,++p;
 }
 zeiger0=zeiger;
}



/*********************/
/* Speicherroutine   */
/*********************/

/* Speichern von IFF-Bildern */
/* fname: zeigt auf den Namen des Bild-files                             */
/* modeh: gibt an, ob das Bild kompremiert abgespeichert werden soll.    */
/*        es bedeutet:      0 -> 'uncompressed'                          */
/*                          1 -> 'compressed'                            */
/* MyScreenh: zeigt auf den Screen des Bildes                            */
/* Ist das Speichern erfolgreich, so kehrt die Routine mit TRUE zurueck. */
/* Andernfalls wird ein FALSE uebergeben.                                */
save(fname,modeh,MyScreenh)
char *fname;
BYTE modeh;
struct Screen *MyScreenh;
{
 static long i,j,k,anzcol,lenheader,lenfile,lencmap,lenbody;
 static WORD color;
 static BYTE ccount,*b1,*b2,*help;
 static BYTE r,g,b,*plane[6],null=0;
 static struct RastPort *MyRastPorth;
 static struct BitMap *MyBitMaph;
 static struct ViewPort *MyViewPorth;

 if(MyScreenh==0) return(FALSE);
 file=Open(fname,MODE_NEWFILE);
 if(file==0)
    return(FALSE);
 if((speicher=AllocMem(MEM_SIZE,MEMF_CLEAR))==0)
 {
    Close(file);
    return(FALSE);
 }
 zeiger0=zeiger=speicher;
 MyRastPorth=&(MyScreenh->RastPort);
 MyBitMaph=MyRastPorth->BitMap;
 MyViewPorth=&(MyScreenh->ViewPort);
 /* Initialisierung des BMHD-Chunks */
 BMHDs.Height=MyBitMaph->Rows;
 BMHDs.Width=(MyBitMaph->BytesPerRow)*8;
 BMHDs.Depth=MyBitMaph->Depth;
 BMHDs.compression=modeh;
 BMHDs.PWidth=BMHDs.Width;
 BMHDs.PHeight=BMHDs.Height;
 if(BMHDs.Height>=400) BMHDs.yaspect*=2;
 if(BMHDs.Width>=640) BMHDs.xaspect*=2;
 CAMGs.ViewModes=MyViewPorth->Modes;
 for(i=0;i<BMHDs.Depth;++i)
    plane[i]=MyBitMaph->Planes[i];
 /* Anzahl der Farben? */
 j=BMHDs.Depth;
 if(j>5) j=5;  /* maximal 32 Farbregister abspeichern! */
 for(i=0,anzcol=1;i<j;anzcol*=2,++i);
 lencmap=anzcol*3;
 lenbody=BMHDs.Width/8*BMHDs.Height*BMHDs.Depth;
 lenheader=sizeof(FORMs)-8+sizeof(BMHDs)+sizeof(CAMGs)+lencmap+8+8;
 lenfile=lenheader+lenbody;
 FORMs.len=lenfile;
 /* Speichern des FORM-Chunks */
 write_file(&FORMs,sizeof(FORMs));
 /* Speichern des BMHD-Chunks */
 write_file(&BMHDs,sizeof(BMHDs));
 /* Speichern des CAMG-Chunks */
 write_file(&CAMGs,sizeof(CAMGs));
 /* Speichern des CMAP-Chunks */
 write_file(&cmap,4);
 write_file(&lencmap,4);
 lencmap+=8;
 for(i=0;i<anzcol;++i)
 {
    color=GetRGB4(MyScreenh->ViewPort.ColorMap,i);
    r=color/256; r*=16; color-=r*16;
    g=color/16; g*=16; color-=g;
    b=color*16;
    write_file(&r,1);
    write_file(&g,1);
    write_file(&b,1);
 }                     
 /* Speichern des BODY-Chunks */
 write_file(&body,4);
 write_file(&lenbody,4);
 if(modeh==0)           /* nicht kompremiert! */
 {
    for(i=0;i<BMHDs.Height;++i)
       for(j=0;j<BMHDs.Depth;++j)
          write_file(plane[j]+BMHDs.Width/8*i,BMHDs.Width/8);
 }
 else  /* kompremiert speichern! */
 {
    lenbody=0;
    for(i=0;i<BMHDs.Height;++i)
    {
       for(j=0;j<BMHDs.Depth;++j)
       {
          for(ccount=0,k=0;k<(BMHDs.Width/8-1);++k)
          {
             b1=plane[j]+BMHDs.Width/8*i+k;
             b2=b1+1;
             if(*b1==*b2)
             {
                if(ccount>0)
                {
                   --ccount;
                   write_file(&ccount,1);
                   write_file(b1-ccount-1,ccount+1);
                   lenbody+=2+ccount;
                   ccount=0;
                }
                --ccount;
             }
             else
             {
                if(ccount<0)
                {
                   write_file(&ccount,1);
                   write_file(b1,1);
                   ccount=-1;
                   lenbody+=2;
                }
                ++ccount;
             }
          }
          if(ccount>=0)
          {
             write_file(&ccount,1);
             write_file(b2-ccount,ccount+1);
             lenbody+=2+ccount;
          }
          if(ccount<0)
          {
             write_file(&ccount,1);
             write_file(b2,1);
             lenbody+=2;
          }
       }
    }
    if((lenbody%2)!=0)    /* ungerade FileLaenge */
    {
       ++lenbody;
       write_file(&null,1);
    }
    if(zeiger!=speicher)
    {
       Write(file,speicher,zeiger-speicher);
       zeiger=speicher;
    }    
    lenfile=lenheader+lenbody;
    Seek(file,4,OFFSET_BEGINNING);
    Write(file,&lenfile,4);
    Seek(file,lenheader+4,OFFSET_BEGINNING);
    Write(file,&lenbody,4);
 }
 if(zeiger!=speicher) Write(file,speicher,zeiger-speicher);
 Close(file);
 if(speicher!=0) FreeMem(speicher,MEM_SIZE);
 return(TRUE);
}


write_file(p,anz)
BYTE *p;
int anz;
{
 if(MEM_SIZE-(zeiger0-speicher)-anz<0)
 {
    Write(file,speicher,zeiger-speicher);
    zeiger=zeiger0=speicher; /* Ruecksetzen */
 }
 while(zeiger-zeiger0<anz)
 {
    *zeiger=*p;
    ++zeiger,++p;
 }
 zeiger0=zeiger;
}
