 /*****************************************************************
 *
 * Read an ILBM file and display as a screen/window until closed.
 *   Simulated close gadget in upper left corner of window.
 *   Clicking below title bar area toggles screen bar for dragging.
 *
 *  By Carolyn Scheppner   CBM  03/15/86
 *
 * Based on early ShowILBM.c    11/12/85
 *  By Jerry Morrison, Steve Shaw, and Steve Hayes, Electronic Arts.
 *  This software is in the public domain.
 *
 * >>NOTE<<: This example must be linked with additional IFF rtn files.
 *           See linkage information below.
 *
 * The IFF reader portion is essentially a recursive-descent parser.
 * This program will look into a CAT or LIST to find a FORM ILBM, but it
 * won't look inside another FORM type for a nested FORM ILBM.
 *
 * The display portion is specific to the Commodore Amiga computer.
 *
 * Linkage Information:
 *
 * FROM     LStartup.obj, SeeILBM.o, iffr.o, ilbmr.o, unpacker.o
 * TO       SeeILBM
 * LIBRARY  LC.lib, Amiga.lib
 * 
 **************************************************************************/

#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <graphics/gfxbase.h>
#include <graphics/rastport.h>
#include <graphics/gfx.h>
#include <graphics/view.h>
#include <workbench/startup.h>
#include <intuition/intuition.h>
#include <lattice/stdio.h>

#include "iff/ilbm.h"

/* This example's max number of planes in a bitmap. Could use MaxAmDepth. */
#define EXDepth 5
#define maxColorReg (1<<EXDepth)
#define MIN(a,b) ((a)<(b)?(a):(b))

#define SafeFreeMem(p,q) {if(p)FreeMem(p,q);}
    
/* Define the size of a temp buffer used in unscrambling the ILBM rows.*/
#define bufSz 512

/* general usage pointers */
struct GfxBase       *GfxBase;
struct IntuitionBase *IntuitionBase;
struct IntuiMessage  *message;

/* Globals for displaying an image */
struct Screen   *screen1;
struct Window   *window1;
struct RastPort *rport1;
struct ViewPort *vport1;

struct BitMap   tBitMap;      /* Temp BitMap struct for small pics  */

/* For WorkBench startup */    
extern struct WBStartup *WBenchMsg;
BOOL   fromWB;
struct FileLock *startLock, *newLock;

/* Other globals */
int   i, error;
BYTE  c;
BOOL  TBtoggle, Done;
ULONG class, code, pBytes;

/* Structures for new Screen, new Window */

struct   TextAttr       TextFont = {
   "topaz.font",                    /* Font Name   */
   TOPAZ_EIGHTY,                    /* Font Height */
   FS_NORMAL,                       /* Style       */
   FPF_ROMFONT,                     /* Preferences */
   };

struct   NewScreen      ns = {
   0, 0,                                  /* LeftEdge and TopEdge   */
   0, 0,                                  /* Width and Height       */
   0,                                     /* Depth                  */
   -1, -1,                                /* DetailPen and BlockPen */
   NULL,                                  /* Special display modes  */
   CUSTOMSCREEN,                          /* Screen Type            */
   &TextFont,                             /* Use my font            */
   NULL,                                  /* Title                  */
   NULL,                                  /* No gadgets yet         */
   NULL,                                  /* Ptr to CustomBitmap    */
   };

struct   NewWindow      nw = {
   0, 0,                                  /* LeftEdge and TopEdge */
   0, 0,                                  /* Width and Height */
   -1, -1,                                /* DetailPen and BlockPen */
   MOUSEBUTTONS,                          /* IDCMP Flags */
   ACTIVATE
   |BACKDROP
   |BORDERLESS,                           /* Flags */
   NULL, NULL,                            /* Gadget and Image pointers */
   NULL,                                  /* Title string */
   NULL,                                  /* Put Screen ptr here */
   NULL,                                  /* SuperBitMap pointer */
   0, 0,                                  /* MinWidth and MinHeight */
   0, 0,                                  /* MaxWidth and MaxHeight */
   CUSTOMSCREEN,                          /* Type of window */
   };

USHORT  allBgColor[32];


/* Message strings for IFFP codes. */
char MsgOkay[]        = {"(IFF_OKAY) No FORM ILBM in the file." };
char MsgEndMark[]     = {"(END_MARK) How did you get this message?" };
char MsgDone[]        = {"(IFF_DONE) All done."};
char MsgDos[]         = {"(DOS_ERROR) The DOS returned an error." };
char MsgNot[]         = {"(NOT_IFF) Not an IFF file." };
char MsgNoFile[]      = {"(NO_FILE) No such file found." };
char MsgClientError[] = {"(CLIENT_ERROR) ShowILBM bug or insufficient RAM."};
char MsgForm[]        = {"(BAD_FORM) A malformed FORM ILBM." };
char MsgShort[]       = {"(SHORT_CHUNK) A malformed FORM ILBM." };
char MsgBad[]         = {"(BAD_IFF) A mangled IFF file." };

/* THESE MUST APPEAR IN RIGHT ORDER!! */
char *IFFPMessages[-LAST_ERROR+1] = {
    /*IFF_OKAY*/  MsgOkay,
    /*END_MARK*/  MsgEndMark,
    /*IFF_DONE*/  MsgDone,
    /*DOS_ERROR*/ MsgDos,
    /*NOT_IFF*/   MsgNot,
    /*NO_FILE*/   MsgNoFile,
    /*CLIENT_ERROR*/ MsgClientError,
    /*BAD_FORM*/  MsgForm,
    /*SHORT_CHUNK*/  MsgShort,
    /*BAD_IFF*/   MsgBad
    };

/*------------ ILBM reader -----------------------------------------------*/
/* ILBMFrame is our "client frame" for reading FORMs ILBM in an IFF file.
 * We allocate one of these on the stack for every LIST or FORM encountered
 * in the file and use it to hold BMHD & CMAP properties. We also allocate
 * an initial one for the whole file.
 * We allocate a new GroupContext (and initialize it by OpenRIFF or
 * OpenRGroup) for every group (FORM, CAT, LIST, or PROP) encountered. It's
 * just a context for reading (nested) chunks.
 *
 * If we were to scan the entire example file outlined below:
 *    reading          proc(s)                new               new
 *
 * --whole file--   ReadPicture+ReadIFF   GroupContext        ILBMFrame
 * CAT              ReadICat                GroupContext
 *   LIST           GetLiILBM+ReadIList       GroupContext        ILBMFrame
 *     PROP ILBM    GetPrILBM                   GroupContext
 *       CMAP       GetCMAP
 *       BMHD       GetBMHD
 *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
 *       BODY       GetBODY
 *     FORM ILBM    GetFoILBM                   GroupContext        ILBMFrame
 *       BODY       GetBODY
 *   FORM ILBM      GetFoILBM                 GroupContext        ILBMFrame
 */
typedef struct {
   ClientFrame clientFrame;
   UBYTE foundBMHD;
   UBYTE nColorRegs;
   BitMapHeader bmHdr;
   Color4 colorMap[maxColorReg];
   /* If you want to read any other property chunks, e.g. GRAB or CAMG, add
    * fields to this record to store them. */
   } ILBMFrame;


/* NOTE: For a simple version of this program, set Fancy to 0.
 * That'll compile a program that skips all LISTs and PROPs in the input
 * file. It will look in CATs for FORMs ILBM. That's suitable for most uses.
 *
 * For a fancy version that handles LISTs and PROPs, set Fancy to 1. */

#define Fancy  1

/* Modified by C. Scheppner */
/*  iFrame    made global -  moved from ReadPicture() */
/*  ilbmFrame made global -  moved from GetFoILBM()   */

ILBMFrame   iFrame;       /* top level client frame */
ILBMFrame   ilbmFrame;    /* global bitmap frame    */


/** main() ****************************************************************/
main(argc, argv)
   int argc;
   char **argv;
   {
   LONG            file;
   IFFP            iffp = NO_FILE;
   struct WBArg    *arg;  
   char            *filename;

   fromWB = (argc==0) ? TRUE : FALSE;

   if(argc>1)                 /* Passed filename via command line  */
      {
      filename = argv[1];
      }
   else if ((argc==0)&&(WBenchMsg->sm_NumArgs > 1))
      {                        /* Passed filename via  WorkBench */
      arg = WBenchMsg->sm_ArgList;
      arg++;
      filename = (char *)arg->wa_Name;
      newLock    = (struct FileLock *)arg->wa_Lock;
      startLock  = (struct FileLock *)CurrentDir(newLock);
      }
   else if (argc==1)           /* From CLI but no filename */
      cleanexit("Usage: 'SeeILBM filename'\n");
   else                        /* From WB but no filename */
      cleanexit("\nClick ONCE on SeeILBM\nSHIFT and DoubleClick on Pic\n");



   if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)))
      cleanexit("Can't open graphics library\n");

   if(!(IntuitionBase=
          (struct IntuitionBase *)OpenLibrary("intuition.library",0)))
      cleanexit("Can't open graphics library\n");

   if(file = Open(filename, MODE_OLDFILE))
      {
      printf("\nCLICK PIC TOP LEFT TO END DISPLAY\n");
      printf("CLICK LOWER TO TOGGLE DRAG BAR\n");
      Delay(150);  /* wait about 3 seconds to give person time to read it */

      iffp = ReadPicture(file);
      Close(file);
      if (iffp == IFF_DONE)
         {
         error = DisplayPic(&ilbmFrame);
         if(error)  cleanexit("Can't open screen or window\n");

         TBtoggle   = FALSE;      /* Title bar toggle */
         Done       = FALSE;      /* Close flag       */
         while (!Done)
            {
            if(1<<window1->UserPort->mp_SigBit)   chkmsg();
            }
         }
      else cleanexit(IFFPMessages[-iffp]);
      }
   else cleanexit("Picture file not found.\n");

   cleanup();
   }


chkmsg()
   {
   while(message=(struct IntuiMessage *)GetMsg(window1->UserPort))
      {
      class = message->Class;
      code  = message->Code;
      ReplyMsg(message);
      switch(class)
         {
         case MOUSEBUTTONS:
            if ((code == SELECTDOWN)&&
                  (window1->MouseX < 10)&&(window1->MouseY<10))
               {
               Done = TRUE;
               }
            else if ((code == SELECTDOWN)&&
                       (window1->MouseY>10)&&(TBtoggle==FALSE))
               {
               TBtoggle = TRUE;
               ShowTitle(screen1,TRUE);
               }
            else if ((code == SELECTDOWN)&&
                       (window1->MouseY>10)&&(TBtoggle==TRUE))
               {
               TBtoggle = FALSE;
               ShowTitle(screen1,FALSE);
               }
            break;
         default:
            printf("Unknown IDCMP message\n");
         }
      }
   }


cleanexit(errstr)
   char  *errstr;
   {
   printf("\n %s \n",errstr);
   cleanup();
   if (fromWB)    /* Wait so user can read messages */
      {
      printf("\nPRESS RETURN TO CLOSE THIS WINDOW\n");
      while ((c=getchar()) != '\n');
      }
   exit();
   }

cleanup()
   {
   /* tBitMap planes were deallocated in DisplayPic() */
   if (window1) CloseWindow(window1);
   if (screen1) CloseScreen(screen1);
   if (IntuitionBase) CloseLibrary(IntuitionBase);
   if (GfxBase)       CloseLibrary(GfxBase);
   if (newLock != startLock)  CurrentDir(startLock);
   }


/** getBitMap() *********************************************************
 *
 * Open screen or temp bitmap.
 *   Returns ptr destBitMap  or  0 = error
 *
 *************************************************************************/
struct BitMap *getBitMap(ptilbmFrame)
   ILBMFrame *ptilbmFrame;
   {
   int     i, nPlanes, plsize;
   SHORT  sWidth, sHeight, dWidth, dHeight;
   struct BitMap *destBitMap;

   sWidth  = ptilbmFrame->bmHdr.w;
   sHeight = ptilbmFrame->bmHdr.h;
   dWidth  = ptilbmFrame->bmHdr.pageWidth;
   dHeight = ptilbmFrame->bmHdr.pageHeight;
   nPlanes   = MIN(ptilbmFrame->bmHdr.nPlanes, EXDepth);

   for (i = 0; i < ptilbmFrame->nColorRegs; i++)
      {
      allBgColor[i] = ptilbmFrame->colorMap[0];
      }

   ns.Width  = dWidth;
   ns.Height = dHeight;
   ns.Depth  = nPlanes;

   if (ptilbmFrame->bmHdr.pageWidth <= 320)
      ns.ViewModes = 0;
   else
      ns.ViewModes = HIRES;

   if (ptilbmFrame->bmHdr.pageHeight > 200)
      ns.ViewModes |= LACE;

   if ((screen1 = (struct Screen *)OpenScreen(&ns))==NULL)    return(0);

   vport1 = &screen1->ViewPort;
   LoadRGB4(vport1, &allBgColor[0], ptilbmFrame->nColorRegs);

   nw.Width  = dWidth;
   nw.Height = dHeight;
   nw.Screen = screen1;

   if ((window1 = (struct Window *)OpenWindow(&nw))==NULL)    return(0);

   ShowTitle(screen1, FALSE);

   if ((sWidth == dWidth) && (sHeight == dHeight))
      {
      destBitMap = (struct BitMap *)screen1->RastPort.BitMap;
      }
   else
      {
      InitBitMap( &tBitMap,
                  nPlanes,
                  sWidth,
                  sHeight);

      plsize = RowBytes(ptilbmFrame->bmHdr.w) * ptilbmFrame->bmHdr.h;
      if (tBitMap.Planes[0] =
       (PLANEPTR)AllocMem(nPlanes * plsize, MEMF_CHIP))
         {
         for (i = 1; i < nPlanes; i++)
            tBitMap.Planes[i] = (PLANEPTR)tBitMap.Planes[0] + plsize*i;
         destBitMap = &tBitMap;
         }
      else
         {
         return(0);  /* can't allocate temp BitMap */
         }
      }
   return(destBitMap);          /* destBitMap allocated */
   }


/** DisplayPic() *********************************************************
 *
 * Display loaded bitmap.  If tBitMap, first transfer to screen.
 *
 *************************************************************************/
DisplayPic(ptilbmFrame)
   ILBMFrame *ptilbmFrame;
   {
   int    i, row, byte, nrows, nbytes;
   struct BitMap  *tbp, *sbp; /* temp and screen BitMap ptrs */
   UBYTE  *tpp, *spp;         /* temp and screen plane ptrs  */

   if (tBitMap.Planes[0])     /* transfer from tBitMap if nec. */
      {
      tbp = &tBitMap;
      sbp = screen1->RastPort.BitMap;
      nrows  = MIN(tbp->Rows, sbp->Rows);
      nbytes = MIN(tbp->BytesPerRow, sbp->BytesPerRow);

      for (i = 0; i < sbp->Depth; i++)
         {
         tpp = (UBYTE *)tbp->Planes[i];
         spp = (UBYTE *)sbp->Planes[i];
         for (row = 0; row < nrows; row++)
            {
            tpp = tbp->Planes[i] + (row * tbp->BytesPerRow);
            spp = sbp->Planes[i] + (row * sbp->BytesPerRow);
            for (byte = 0; byte < nbytes; byte++)
               {
               *spp++ = *tpp++;
               }
            }
         }
      /*  Can now deallocate the temp BitMap  */
      FreeMem(tBitMap.Planes[0],
                 tBitMap.BytesPerRow * tBitMap.Rows * tBitMap.Depth);
      }

   vport1 = &screen1->ViewPort;
   LoadRGB4(vport1, ptilbmFrame->colorMap, ptilbmFrame->nColorRegs);

   return(0);
   }


/** GetLiILBM() **********************************************************
 *
 * Called via ReadPicture to handle every LIST encountered in an IFF file.
 *
 *************************************************************************/
#if Fancy
IFFP GetLiILBM(parent)
GroupContext *parent; {
    ILBMFrame newFrame;   /* allocate a new Frame */

    newFrame = *(ILBMFrame *)parent->clientFrame;  /* copy parent frame */

    return( ReadIList(parent, (ClientFrame *)&newFrame) );
    }
#endif


/** GetPrILBM() **********************************************************
 *
 * Called via ReadPicture to handle every PROP encountered in an IFF file.
 * Reads PROPs ILBM and skips all others.
 *
 *************************************************************************/
#if Fancy
IFFP GetPrILBM(parent)
GroupContext *parent; {
   /*compilerBug register*/ IFFP iffp;
   GroupContext propContext;
   ILBMFrame *ilbmFrame = (ILBMFrame *)parent->clientFrame;

   if (parent->subtype != ID_ILBM)
      return(IFF_OKAY);   /* just continue scaning the file */

   iffp = OpenRGroup(parent, &propContext);
   CheckIFFP();

   do switch (iffp = GetPChunkHdr(&propContext)) {
      case ID_BMHD: {
         ilbmFrame->foundBMHD = TRUE;
         iffp = GetBMHD(&propContext, &ilbmFrame->bmHdr);
         break; }
      case ID_CMAP: {
         ilbmFrame->nColorRegs = maxColorReg; /* room for this many */
         iffp = GetCMAP( &propContext, (WORD *)ilbmFrame->colorMap,
                          &ilbmFrame->nColorRegs);
         break; }

      } while (iffp >= IFF_OKAY);/* loop if valid ID of ignored chunk or
                                  * subrtn returned IFF_OKAY (no errors).*/

   CloseRGroup(&propContext);
   return(iffp == END_MARK ? IFF_OKAY : iffp);
   }
#endif


/** GetFoILBM() **********************************************************
 *
 * Called via ReadPicture to handle every FORM encountered in an IFF file.
 * Reads FORMs ILBM and skips all others.
 * Inside a FORM ILBM, it stops once it reads a BODY. It complains if it
 * finds no BODY or if it has no BMHD to decode the BODY.
 *
 * Once we find a BODY chunk, we'll allocate the BitMap and read the image.
 *
 * Modified by C. Scheppner:  ilbmFrame moved above main making it
 *                            global so main can call DisplayPic()
 *
 *************************************************************************/
IFFP GetFoILBM(parent)
   GroupContext *parent;
   {
   IFFP iffp;
   GroupContext formContext;
   BYTE buffer[bufSz];
   struct BitMap *destBitMap;

   if (parent->subtype != ID_ILBM)
      return(IFF_OKAY);   /* just continue scaning the file */

   ilbmFrame = *(ILBMFrame *)parent->clientFrame;
   iffp = OpenRGroup(parent, &formContext);
   CheckIFFP();

   do switch (iffp = GetFChunkHdr(&formContext)) {
      case ID_BMHD: {
         ilbmFrame.foundBMHD = TRUE;
         iffp = GetBMHD(&formContext, &ilbmFrame.bmHdr);
         break; }
      case ID_CMAP: {
         ilbmFrame.nColorRegs = maxColorReg;  /* we have room for this many */
         iffp = GetCMAP(&formContext, (WORD *)ilbmFrame.colorMap,
                           &ilbmFrame.nColorRegs);
         break; }
      case ID_BODY: {
         if (!ilbmFrame.foundBMHD)  return(BAD_FORM);   /* No BMHD chunk! */

         if(destBitMap=(struct BitMap *)getBitMap(&ilbmFrame))
            {
            iffp = GetBODY( &formContext,
                            destBitMap,
                            NULL,
                            &ilbmFrame.bmHdr,
                            buffer,
                            bufSz);
            if (iffp == IFF_OKAY) iffp = IFF_DONE;   /* Eureka */
            }
         else
            iffp = CLIENT_ERROR;   /* not enough RAM for the bitmap */
         break; }

      case END_MARK: {
         iffp = BAD_FORM;
         break; }

   } while (iffp >= IFF_OKAY);  /* loop if valid ID of ignored chunk or a
           * subroutine returned IFF_OKAY (no errors).*/

   if (iffp != IFF_DONE)  return(iffp);

   CloseRGroup(&formContext);
   return(iffp);
   }

/** Notes on extending GetFoILBM *****************************************
 *
 * To read more kinds of chunks, just add clauses to the switch statement.
 * To read more kinds of property chunks (GRAB, CAMG, etc.) add clauses to
 * the switch statement in GetPrILBM, too.
 *
 * To read a FORM type that contains a variable number of data chunks--e.g.
 * a FORM FTXT with any number of CHRS chunks--replace the ID_BODY case with
 * an ID_CHRS case that doesn't set iffp = IFF_DONE, and make the END_MARK
 * case do whatever cleanup you need.
 *
 *************************************************************************/


/** ReadPicture() ********************************************************
 *
 * Read a picture from an IFF file, given a file handle open for reading.
 *
 * Modified by Carolyn Scheppner   CBM   03-86
 *   iFrame made global (above main)
 *   Close(file) moved to main
 *
 *************************************************************************/
IFFP ReadPicture(file)
      LONG file;
   {
   IFFP iffp = IFF_OKAY;

#if Fancy
   iFrame.clientFrame.getList = GetLiILBM;
   iFrame.clientFrame.getProp = GetPrILBM;
#else
   iFrame.clientFrame.getList = SkipGroup;
   iFrame.clientFrame.getProp = SkipGroup;
#endif
   iFrame.clientFrame.getForm = GetFoILBM;
   iFrame.clientFrame.getCat  = ReadICat ;

   /* Initialize the top-level client frame's property settings to the
    * program-wide defaults. This example just records that we haven't read
    * any BMHD property or CMAP color registers yet. For the color map, that
    * means the default is to leave the machine's color registers alone.
    * If you want to read a property like GRAB, init it here to (0, 0). */
   iFrame.foundBMHD  = FALSE;
   iFrame.nColorRegs = 0;

   iffp = ReadIFF(file, (ClientFrame *)&iFrame);
   return(iffp);
   }


