/* VIEW.C - tiny ILBM viewer; (c) 1986 DJH

   	Note that there are TWO ways to call this program; from CLI,
  as in : "View filespec" or from WorkBench. If spawned from WorkBench
  via a tool icon, its ToolTypes array is searched for a "PICTURE=fspec"
  argument detailing the file to display.
*/
   
extern struct WBStartup *WBenchMsg;

#define TITLE "View 1.8 : (c) 1986-87 John Hodgson\n"

#define MAXWIDTH  376 /* max non-HIRES width */
#define MAXHEIGHT 242 /* max non-interlaced height */
#define MAXCOLORS 32  /* max # colors supported */

#define MakeID(a,b,c,d) ((a)<<24L | (b)<<16L | (c)<<8 | (d))

#define ID_FORM MakeID('F','O','R','M')
#define ID_ILBM MakeID('I','L','B','M')
#define ID_BMHD MakeID('B','M','H','D')
#define ID_CAMG MakeID('C','A','M','G')
#define ID_CMAP MakeID('C','M','A','P')
#define ID_BODY MakeID('B','O','D','Y')

#define cmpByteRun1 1

#define ROUNDODDUP(a) (((a)+1)&(~1L))

typedef struct {
  long ckID,ckSize;
} Chunk;

typedef struct {
  short w,h,x,y;
  char  nPlanes,masking,compression,pad1;
  short transparentColor;
  char  xAspect, yAspect;
  short pageWidth,pageHeight;
} BitMapHeader;

#define SafeRead(a,b,c) if (Read(a,b,c)==-1L) { Close(a); return(NULL); }

void *IntuitionBase,*GfxBase,*IconBase;
 
/************************************************************************
*                                                                       *
*    Routine name(s) : ReadILBM()                                       *
*    Author          : D. John Hodgson                                  *
*    Environment     : Aztec "C", default                               *
*                                                                       *
*    ReadILBM attempts to read an IFF file and display it on a freshly  *
*    opened custom screen. Returns a screen pointer if sucessful,       *
*    otherwise NULL.                                                    *
*                                                                       *
*    LIMITATIONS : no masking, CATS/LISTS/PROPS. CAMG chunks supported. *
************************************************************************/

BitMapHeader bmhd;
char *bufstart;
Chunk header;
  
void *ReadILBM(fspec)
char *fspec; /* AmigaDOS filename */
{
  struct NewScreen NewScreen;
  struct Screen *screen;
  struct FileHandle *fp;
  char colormap[MAXCOLORS][3],*sourcebuf;
  short colorcount;
  long id,ViewModes=0;

  if ((fp=Open(fspec,MODE_OLDFILE))==0) return(NULL);

  SafeRead(fp,&header,(long)sizeof(header));
  if (header.ckID!=ID_FORM) { Close(fp); return(NULL); }

  SafeRead(fp,&id,(long)sizeof(id));
  if (id!=ID_ILBM) { Close(fp); return(NULL); }

  for (;;) {
    SafeRead(fp,&header,(long)sizeof(header));

    if (header.ckID==ID_BODY) break;

    switch(header.ckID) {
      case ID_BMHD: SafeRead(fp,&bmhd,(long)sizeof(bmhd));
                    break;

      case ID_CMAP: SafeRead(fp,&colormap[0][0],(long)header.ckSize);
                    colorcount=header.ckSize/3;
                    break;

      case ID_CAMG: SafeRead(fp,&ViewModes,(long)header.ckSize);
                    break;

      default:      Seek(fp,ROUNDODDUP(header.ckSize),OFFSET_CURRENT);
    }
  }

  /* Read planes into RAM for ease if decompression */
     
  sourcebuf=bufstart=AllocMem((long)header.ckSize,MEMF_PUBLIC);
  if (sourcebuf==0L) puts("Error allocating memory!");

  SafeRead(fp,sourcebuf,(long)header.ckSize); Close(fp);

  NewScreen.LeftEdge=0; NewScreen.TopEdge=0;
  NewScreen.Width=bmhd.w; NewScreen.Height=bmhd.h;
  NewScreen.Depth=bmhd.nPlanes;

  /* make some forced assumptions if CAMG chunk unavailable */

  if (!(NewScreen.ViewModes=ViewModes)) {
    if (bmhd.w>MAXWIDTH) NewScreen.ViewModes|=HIRES;
    if (bmhd.h>MAXHEIGHT) NewScreen.ViewModes|=LACE;
  }

  NewScreen.Type=CUSTOMSCREEN;
  NewScreen.Font=0L;
  NewScreen.Gadgets=0L;

  screen=OpenScreen(&NewScreen);

  while (colorcount--)
    SetRGB4(&screen->ViewPort,(long)colorcount,
       colormap[colorcount][0]>>4L,colormap[colorcount][1]>>4L,
          colormap[colorcount][2]>>4L);
  
  return(screen);   
}      

Expand(screen,bmhd,sourcebuf) /* Fast line decompress/deinterleave */
struct Screen *screen;
BitMapHeader *bmhd;
register char *sourcebuf;
{
  
  register char n,*destbuf; /* in order of preferred allocation */
  register short plane,linelen,rowbytes,i;

  linelen=bmhd->w/8;

  for (i=0;i<bmhd->h;i++) /* process n lines/screen */
    for (plane=0;plane<bmhd->nPlanes;plane++) { /* process n planes/line */
      destbuf=(char *)(screen->BitMap.Planes[plane])+linelen*i;

      if (bmhd->compression==cmpByteRun1) { /* compressed screen? */
        rowbytes=linelen;

        while (rowbytes) { /* unpack until 1 scan-line complete */
          n=*sourcebuf++; /* fetch block run marker */

          /* uncompressed block? copy n bytes verbatim */
          if (n>=0) {
            movmem(sourcebuf,destbuf,(unsigned int)++n); rowbytes-=n;
            destbuf+=n; sourcebuf+=n;
          }
          else { /* compressed block? expand n duplicate bytes */
            n=-n+1; rowbytes-=n;
            setmem(destbuf,(unsigned int)n,(unsigned int)*sourcebuf++);
            destbuf+=n;
          }

        } /* finish unpacking line */
      }
      else { /* uncompressed? just copy */
        movmem(sourcebuf,destbuf,(unsigned int)linelen);
        sourcebuf+=linelen; destbuf+=linelen;
      }
    } /* finish interleaved planes, lines */
}

struct Screen *ParseWBArgs() /* analyze WB arguments */
{
  struct DiskObject *diskobj;
  struct Screen *screen;
  char *toolarg;

  /* scrutinize parent icon to fetch ToolTypes */
  if (!(diskobj=GetDiskObject(WBenchMsg->sm_ArgList->wa_Name))) return(0);

  /* scan ToolTypes for a "PICTURE=filespec" argument */

  if (!(toolarg=FindToolType(diskobj->do_ToolTypes,"PICTURE"))) {
    FreeDiskObject(diskobj);
    return(0);
  }

  screen=ReadILBM(toolarg); /* parse ILBM & open screen */

  FreeDiskObject(diskobj);
  return(screen);
}

main(argc,argv)
int argc;
char *argv[];
{
  struct Screen *screen;
  struct Window *window;
  struct NewWindow nw;

  short i;


  IntuitionBase=OpenLibrary("intuition.library",0L);
  GfxBase=OpenLibrary("graphics.library",0L);
  IconBase=OpenLibrary("icon.library",0L);

  if (argc==0) { if (!(screen=ParseWBArgs())) exit(100); } /* son of WB? */
  else {
    Write(Output(),TITLE,(long)sizeof(TITLE)); /* CLI title */
    if (!(screen=ReadILBM(argv[1]))) exit(100);
  }

  /* a note on window selection; the ONLY reason we're opening a window
     AT ALL is to get the LMB event so the user can close the screen.
     Any window that : 1) eats up as little RAM as possible,
                       2) renders absolutely nothing,
                       3) covers the screen title bar
     will do. Note that we can prevent anything from displaying (even
     screen title bars) by leaving DetailPen & BlockPens at zero; rendering
     a background color on an empty screen displays nothing!

     Note that we render DIRECTLY (via Expand()) into the screen's BitMap;
     This saves us from allocating another buffer just so that we can copy
     it into the window itself. Sure this scribbles over any window/screen
     imagery, but we're not displaying any anyway. Nyaah. */

  setmem(&nw,sizeof(nw),0); /* start w/fresh structure */

  nw.IDCMPFlags=MOUSEBUTTONS;
  nw.Flags=SIMPLE_REFRESH|BORDERLESS;
  nw.Screen=screen;
  nw.Type=CUSTOMSCREEN;
  nw.Height=bmhd.h; nw.Width=bmhd.w;

  if (!(window=OpenWindow(&nw))) { CloseScreen(screen); exit(100); }

  Expand(screen,&bmhd,bufstart);
  FreeMem(bufstart,(long)header.ckSize); /* free compressed buffer */

  WaitPort(window->UserPort); /* wait for a LMB */

  CloseWindow(window); /* Intuition will ReplyMsg() for us */
  CloseScreen(screen);

  CloseLibrary(IntuitionBase);
  CloseLibrary(GfxBase);
  CloseLibrary(IconBase);
}
