/* CrossFade : A video crossfade simulator (c) 1990 Dallas J. Hodgson

   Usage : CrossFade <ILBM-file1> <ILBM-file2>

   CrossFade takes 2 four-color ILBM pictures and smoothly crossfades between
   them, using nothing more than color register manipulation! The effect
   is similar to that used in pro video equipment, and lends itself to
   other applications such as animation, depth-arranging, etc. This idea
   could be extended to more bitplanes for extra colors or higher #'s of
   simultaneously-crossfadable images.

   HOW IT WORKS:

   CrossFade opens a 4-bitplane screen and loads each 4-color image into 
   two bitplanes apiece. This results in a 16-color image, formed by
   the colors in image 1, (registers 0,1,2,3) image 2 (registers 0,4,8,12)
   and other color registers according to the overlap of bits between
   the two images.

   To blank out either image, we just set its color registers to the
   transparent color - almost. Wherever bits in bitplanes [0,1] (image 1)
   and [2,3] (image 2) overlap, other pencolors are formed from the overlap
   set { 5,6,7,9,10,11,13,14 }. These color registers have to be filled with
   redundant copies of the image colors we wish to display to prevent nasty
   XOR-type video effects.

   To explain this effect in all of its boolean detail would take up to
   much space. Screen-grab a CrossFaded display and manipulate the colors in
   DPaint - you'll see what I mean.

   Click on the window-close gadget in the upper left corner of the
   screen to exit.
*/

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

extern struct WBStartup *WBenchMsg;

#define TITLE "CrossFade 1.0 : (c) 1990 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;
 
/************************************************************************
*                                                                       *
*    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. *
************************************************************************/

void *ReadILBM(fspec,size,bmhd,colormap,NewScreen)
char *fspec;			 /* AmigaDOS filename */
long *size;			 /* size of allocated buffer */
BitMapHeader *bmhd;		 /* header for us to fill */
unsigned char colormap[MAXCOLORS][3];     /* colormap for us to fill */
struct NewScreen *NewScreen;	 /* screen struct for us to fill */
{
  struct Screen *screen;
  struct FileHandle *fp;
  char *sourcebuf;
  short i;
  long id,ViewModes=0;
  char *bufstart;
  Chunk header;

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

  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);
		    for (i=0;i<header.ckSize;i++) colormap[0][i]>>=4;
                    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 of decompression */
     
  sourcebuf=bufstart=AllocMem((long)header.ckSize,MEMF_PUBLIC);
  if (sourcebuf==0L) puts("Error allocating memory!");
  *size=(long)header.ckSize;

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

  setmem(NewScreen,sizeof(*NewScreen),0); /* start fresh */

  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;

  return(bufstart);   
}      

Expand(screen,bmhd,sourcebuf,planeoffset) /* Fast line decompress/deinterleave */
struct Screen *screen;
BitMapHeader *bmhd;
register char *sourcebuf;
short planeoffset;
{
  
  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+planeoffset])+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 */
}
load_colormap(screen,colormap)
struct Screen *screen;
char colormap[MAXCOLORS][3];
{
  short i,colorcnt=1<<screen->BitMap.Depth;

  /* Sure, LoadRGB is faster, but uses UWORDS for its colors! */

  for (i=0;i<colorcnt;i++) {
    SetRGB4(&screen->ViewPort,(long)i,
       (long)colormap[i][0],(long)colormap[i][1],
          (long)colormap[i][2]);
  }
}

create_image1x4_colors(srcmap,dstmap)
char srcmap[MAXCOLORS][3],dstmap[MAXCOLORS][3];
{
  short i,j;

  /* create image 1's color palette. Duplicates image 1 colors
     into the xx11 registers. */

  for (i=0;i<16;i++) 
    for (j=0;j<3;j++)
      dstmap[i][j]=srcmap[i%4][j];
}

create_image2x4_colors(srcmap,dstmap)
char srcmap[MAXCOLORS][3],dstmap[MAXCOLORS][3];
{
  short i,j;

  /* create image 2's color palette */

  for (i=0;i<=4;i++)
    for (j=0;j<3;j++)
      dstmap[i*4][j]=srcmap[i][j];

  /* Duplicate the image2 colors into the 11xx registers */

  for (i=0;i<16;i++) {
    if (i%4) {
       for (j=0;j<3;j++) 
         dstmap[i][j]=srcmap[(i & 0x0c)/4][j];
    }
  }
} 

FadeAtoB(screen,srcmap,dstmap)
struct Screen *screen;
char srcmap[MAXCOLORS][3],dstmap[MAXCOLORS][3];
{
  short i,j,flag,colorcnt=1<<screen->BitMap.Depth;

  do {
    flag=0;

    for (i=0;i<colorcnt;i++) {
      for (j=0;j<3;j++) {
        if (srcmap[i][j]!=dstmap[i][j]) {
          srcmap[i][j]+=SIGN(dstmap[i][j]-srcmap[i][j]);
          flag=1;
        }
      }
    }
    load_colormap(screen,srcmap);
    Delay(5);
  }
  while(flag);
}     

main(argc,argv)
int argc;
char *argv[];
{
  struct Screen *screen=0;
  struct Window *window=0;
  struct NewWindow nw;
  struct NewScreen NewScreen1,NewScreen2;
  BitMapHeader bmhd1,bmhd2;
  char *buf1=0,*buf2=0;
  long size1,size2;
  short i;

  char colormap1[MAXCOLORS][3],colormap2[MAXCOLORS][3],
       colormapA[MAXCOLORS][3],colormapB[MAXCOLORS][3];

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

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

  puts(TITLE);	/* CLI title */

  if (!(buf1=ReadILBM(argv[1],&size1,&bmhd1,colormap1,&NewScreen1))) {
    printf("Error reading IFF file %s.\n",argv[1]);
    goto cleanup;
  }

  if (NewScreen1.Depth!=2) {
    printf("ILBM file %s must be 2 bitplanes. %d found.\n",argv[1],NewScreen1.Depth);
    goto cleanup;
  }
    
  if (!(buf2=ReadILBM(argv[2],&size2,&bmhd2,colormap2,&NewScreen2))) {
    printf("Error reading IFF file %s.\n",argv[2]);
    goto cleanup;
  }

  if (NewScreen2.Depth!=2) {
    printf("ILBM file %s must be 2 bitplanes. %d found.\n",argv[2],NewScreen2.Depth);
    goto cleanup;
  }

  NewScreen1.Depth+=NewScreen2.Depth;

  create_image1x4_colors(colormap1,colormapA);
  create_image2x4_colors(colormap2,colormapB);

  if (!(screen=OpenScreen(&NewScreen1))) {
    puts("Couldn't open screen!");
    goto cleanup;
  }
    
  load_colormap(screen,colormapA);

  nw.IDCMPFlags=CLOSEWINDOW;
  nw.Flags=SIMPLE_REFRESH|BORDERLESS|WINDOWCLOSE;
  nw.Screen=screen;
  nw.Type=CUSTOMSCREEN;
  nw.Height=bmhd1.h; nw.Width=bmhd1.w;

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

  Expand(screen,&bmhd1,buf1,0); /* fill bitplanes 0-1, image 1 */
  Expand(screen,&bmhd2,buf2,2); /* fill bitplanes 1-2, image 2 */

  Delay(100);

  while (!GetMsg(window->UserPort)) { /* until CloseWindow */
    FadeAtoB(screen,colormapA,colormapB);
    create_image1x4_colors(colormap1,colormapB);
    create_image2x4_colors(colormap2,colormapA);
    Delay(20);
    FadeAtoB(screen,colormapA,colormapB);
    create_image1x4_colors(colormap1,colormapA);
    create_image2x4_colors(colormap2,colormapB);
  }

cleanup:
  if (buf1) FreeMem(buf1,size1);   /* free compressed buffer */
  if (buf2) FreeMem(buf2,size2);   /* free compressed buffer */
  if (window) CloseWindow(window); /* Intuition will ReplyMsg() for us */
  if (screen) CloseScreen(screen);

  CloseLibrary(IntuitionBase);
  CloseLibrary(GfxBase);
}
