/*
 * Article 582 of net.micro.amiga:
 * ion: version B 2.10.2 9/17/84 chuqui version 1.9 3/12/85; site unisoft.UUCP
 * Posting-Version: version B 2.10.3 4.3bsd-beta 6/6/85; site amiga.amiga.UUCP
 * Path: unisoft!dual!vecpyr!lll-lcc!lll-crg!seismo!hao!hplabs!pesnta!greipa!decwrl!pyramid!amiga!bobp
 * From: bobp@amiga.UUCP (Robert S. Pariseau)
 * Newsgroups: net.micro.amiga
 * Subject: More Fun with 6 Planes (LONG SOURCE)
 * Message-ID: <223@amiga.amiga.UUCP>
 * Date: 11 Nov 85 10:39:27 GMT
 * Date-Received: 14 Nov 85 21:00:59 GMT
 * Reply-To: bobp@snake.UUCP (Robert S. Pariseau)
 * Organization: Commodore-Amiga Inc., 983 University Ave #D, Los Gatos CA 95030
 * Lines: 471
 * 
 * TITLE:  More Fun with 6 Planes (LONG SOURCE)
 * 
 * The source below is a bit of eye-candy that shows off the use of Hold
 * and Modify graphics display mode on the Amiga.  Hold and Modify (or HAM
 * for short) is a compromise between the limited direct color access of
 * an inexpensive color-palette, frame buffer graphics engine like the
 * Amiga, and the high cost of a direct reading frame buffer graphic engine
 * like some of the more expensive CAD systems.  On the Amiga, the bits
 * associated with a given pixel select one of the 32 color registers.  Each
 * color register contains a color value consisting of 4 bits of Red
 * component, 4 bits of Green, and 4 bits of Blue -- yielding 4096 different
 * colors from black to white.  The Amiga hardware has sufficient bandwidth
 * to retrieve 6 bit-planes worth of data for a given pixel (4 in high
 * resolution display).  The combination of these two constraints says that
 * in a static display (no dynamic color changing going on in the 
 * background), you can display up to 32 colors simultaneously in low
 * res and up to 16 colors simultaneously in highs res choosen out of
 * the palette of 4096 possible colors.
 * 
 * [Note: as discussed earlier, you can actually get up to 64 distinct
 * colors in low res using 6 planes and EXTRA_HALFBRITE display mode.
 * Further, note that dynamic color changing goes on all the time in
 * normal Amiga displays -- that's how each of the separately slideable
 * screens gets its own color palette for instance.]
 * 
 * If the Amiga were only somehow capable of pulling in 12 planes of data
 * per pixel, then we could avoid using the color palette altogether.  We
 * could encode the desired pixel color (4 bits each R, G, and B) directly
 * into the frame buffer.  This would allow us to independently choose
 * any of the 4096 colors for each pixel -- with no limit on how many
 * different colors appeared in a given display.
 * 
 * [Note: there's a downside to this as well.  Since the display doesn't
 * feed through a color palette, there's no easy way of swapping or
 * changing colors in your picture -- you have to actually go out and
 * change all those pixels.  With a color palette, you just change the
 * color values in the registers.  This trick is fundamental to the
 * technique of "color cycle animation".]
 * 
 * HAM is a color modulation technique which approaches the flexibility
 * of a direct reading frame buffer.  Using HAM, you can specify any
 * desired color out of the 4096 available within 3 horizontally
 * adjacent pixels.  Here is how it works.
 * 
 * Consider a 4 plane image.  The 4 bits of each pixel select one of the
 * first 16 color registers.  Now consider the extra 2 bits per pixel
 * of a 6 plane HAM image.  Those extra 2 bits are used as control bits,
 * selecting between the following 4 choices:
 * 
 * 0) Use the lower 4 bits as a color register selector, just as normal.
 * 
 * 1) Use the lower 4 bits as the Blue component of the color for this
 *    pixel.  Copy the Red and Green components of the color for this
 *    pixel from the color of the Playfield pixel immediately to the
 *    left of this one.
 * 
 * 2) Use the lower 4 bits as the Red component of the color for this
 *    pixel.  Copy the Blue and Green components of the color for this
 *    pixel from the color of the Playfield pixel immediately to the
 *    left of this one.
 * 
 * 3) Use the lower 4 bits as the Green component of the color for this
 *    pixel.  Copy the Red and Blue components of the color for this
 *    pixel from the color of the Playfield pixel immediately to the
 *    left of this one.
 * 
 * Note that only the color produced for the pixel is varied.  The contents
 * of the color registers themselves are NOT changed by HAM.  Also note
 * that the effects are cumulative.  Thus, in 3 adjacent pixels, you can
 * Modify-Red, Modify-Green, and Modify-Blue, thereby producing any color
 * you want.
 * 
 * Finally, note that a Modify-x pixel takes its "Hold" colors from the
 * color that is OR WOULD HAVE BEEN produced for the PLAYFIELD (i.e.,
 * frame buffer) pixel immediately to its left.  This means that sprite
 * images moving over the display do NOT mess up your carefully choosen
 * HAM colors.
 * 
 * There is a hardware mode bit that controls HAM.  A HAM display requires
 * 5 or 6 bit planes.  If you provide a 5 plane image, the hardware assumes
 * a 6th bit value of 0 for each pixel -- this means you can only choose
 * normal and Modify-Blue out of the choices above.
 * 
 * The graphics kernel software automatically supports HAM as a ViewPort
 * attribute.  That means that each of your separately slideable ViewPorts
 * (Screens in Intuition lingo) can have, or not have, HAM turned on without
 * affecting the others.
 * 
 * BEWARE!  Some of the older developer machines and store demo machines
 * do not produce reliable HAM images.  ALL of the consumer machines do
 * HAM just fine.
 */

/*
 * ----------------------------Program Notes:
 * 
 * The Colorful program will compile and link cleanly using the native
 * Amiga Lattice C tools on the standard V1.0 C disk.  The Make script
 * in the examples directory will do all the work for you.  As usual,
 * I recommend that you copy the source into ram: disk and then direct
 * Make at it by typing:
 * 
 *   1> execute Make ram:Colorful
 * 
 * The program displays 256 boxes each having a different color out of
 * the possible 4096.  Colorful listens for left mouse button events
 * and switches between two display modes.  The first is an ordered
 * display with labelled color components.  The second is a random
 * display where boxes randomly selected have their color randomly
 * changed.  Note that at no time do any 2 boxes share the same color.
 * 
 * If you are sharp-eyed, you will notice a narrow stripe of red and a
 * narrow stripe of green at the leading (leftmost) edge of each of the
 * boxes.  These are the color modifiers in action.  Each box consists
 * of a vertical line of Modify-Red, followed by a vertical line of
 * Modify-Green, followed by a rectangle of Modify-Blue as follows:
 * 
 * 	 RGBBBBBBBBB
 * 	 RGBBBBBBBBB
 * 	 RGBBBBBBBBB
 * 	 RGBBBBBBBBB
 * 	 RGBBBBBBBBB
 * 	 RGBBBBBBBBB
 * 
 * The routine hamBox() produces such a box.
 * 
 * For further information, see the Amiga Hardware Manual, the Amiga
 * ROM Kernel Manual, and the manual Intuition:  The Amiga User Interface.
 * 
 * ----------------------------Program Source Follows:
 * 
 */

/***********************************************************************
 *  Colorful -- A demo of the Amiga's Hold and Modify mode, showing, at
 *              all times,  a different subset of 256 of the 4096
 *              colors available on the Amiga.  At any moment, no two
 *              squares have the same exact color in them.
 *
 *  Rob Peck     -- November  5, 1985
 *  Bob Pariseau -- November 10, 1985   (Rework for tutorial)
 *
 **********************************************************************/

#include <exec/types.h>
#include <exec/tasks.h>
#include <exec/libraries.h>
#include <exec/devices.h>
#include <devices/keymap.h>
#include <graphics/copper.h>
#include <graphics/display.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <graphics/view.h>
#include <graphics/gels.h>
#include <graphics/regions.h>
#include <hardware/blit.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>

#define  XSIZE 11                      /* Color box sizes             */
#define  YSIZE 6

struct   GfxBase       *GfxBase;       /* Export the library pointers */
struct   IntuitionBase *IntuitionBase;


struct   RastPort      *rp;            /* Graphics structures           */
struct   ViewPort      *vp;

struct TextAttr TestFont =
    {
       "topaz.font", /* Standard system font */
       8,    0,    0
    };

struct   Window        *w;             /* Intuition structures        */ 
struct   Screen        *screen;
struct   IntuiMessage  *message;


struct NewScreen ns = {
   0, 0,                               /* start position                */
   320, 200, 6,                        /* width, height, depth          */
   0, 1,                               /* detail pen, block pen         */
   HAM,                                /* Hold and Modify ViewMode      */
   CUSTOMSCREEN,                       /* screen type                   */
   &TestFont,                          /* font to use                   */
   " 256 different out of 4096",       /* default title for screen      */
   NULL                                /* pointer to additional gadgets */
   };


struct NewWindow nw = {
        0, 11,                         /* start position                */
        320, 186,                      /* width, height                 */
        -1, -1,                        /* detail pen, block pen         */
        MOUSEBUTTONS|CLOSEWINDOW,      /* IDCMP flags                   */
        ACTIVATE|WINDOWCLOSE,          /* window flags                  */
        NULL,                          /* pointer to first user gadget  */
        NULL,                          /* pointer to user checkmark     */
        "colors at any given moment",  /*  window title                 */
        NULL,                          /* pointer to screen (set below) */
        NULL,                          /* pointer to superbitmap        */
        0, 0, 320, 186,                /* ignored since not sizeable    */
        CUSTOMSCREEN                   /* type of screen desired        */
        };
 


LONG  squarecolor[16 * 16], freecolors[4096-(16*16)];
SHORT squares[16 * 16];
SHORT xpos[16], ypos[16];

char  *number[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                    "A", "B", "C", "D", "E", "F" };

SHORT sStop, cStop, sequence;
BOOL  textneeded;



main()
{
   ULONG  class;
   USHORT code, i;
   BOOL   wheelmode;

   for(i=0; i<16; i++)               /* Establish color square positions */
      {
      xpos[i] = (XSIZE + 4) * i + 20;
      ypos[i] = (YSIZE + 3) * i + 21;
      }

   GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);
   if (GfxBase == NULL) exit();

   IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
   if (IntuitionBase == NULL)
   {
      CloseLibrary(GfxBase);
      exit();
   }

   screen = (struct Screen *)OpenScreen(&ns); 
   if (screen == NULL)
   {
      CloseLibrary(IntuitionBase);
      CloseLibrary(GfxBase);
      exit();
   }

   nw.Screen = screen;                /* Open window in our new screen */
   w = (struct Window *)OpenWindow(&nw);
   if (w == NULL)
   {
      CloseScreen(screen);
      CloseLibrary(IntuitionBase);
      CloseLibrary(GfxBase);
      exit();
   }

   vp = &screen->ViewPort;             /* Set colors in screen's VP    */
   rp = w->RPort;                      /* Render into the window's RP  */

   /*  Set the color registers:  Black, Red, Green, Blue, White */

   SetRGB4(vp, 0, 00, 00, 00);   
   SetRGB4(vp, 1, 15, 00, 00);    
   SetRGB4(vp, 2, 00, 15, 00);   
   SetRGB4(vp, 3, 00, 00, 15);
   SetRGB4(vp, 4, 15, 15, 15);

   SetBPen(rp, 0);                    /* Insure clean text              */
   textneeded = TRUE;
   wheelmode  = TRUE;                 /* Start with Color Wheel display */

   FOREVER
   {  /* Process any and all messages in the queue, then update the     */
      /* display colors once, then come back here to look at the queue  */
      /* again.  If you see a left-mouse-button-down event, then switch */
      /* display modes.  If you see a Close-Window-gadget event, then   */
      /* clean up and exit the program.  NOTE:  This is a BUSY LOOP so  */
      /* that the colors will cycle as quickly as possible.             */

      while((message = (struct IntuiMessage *)GetMsg(w->UserPort)) != NULL)
      {
         class = message->Class;
         code  = message->Code;
         ReplyMsg(message);       /* Can't reply until done using it!   */

         if(class == CLOSEWINDOW)                   /* Exit the program */
         {
            CloseWindow(w);
            CloseScreen(screen);
            CloseLibrary(IntuitionBase);
            CloseLibrary(GfxBase);
            exit();
         };

         if(class == MOUSEBUTTONS && code == SELECTDOWN)  /* swap modes */
         {
            wheelmode = NOT wheelmode;

            SetAPen(rp, 0);                   /* Clear the drawing area */
            SetDrMd(rp, JAM1);
            RectFill(rp, 3, 12, 318, 183);
            textneeded = TRUE; 
         }
      }
      if(wheelmode) colorWheel();  else colorFull();
   }
}


colorFull()              /* Display a ramdomized set of colors          */
{
   SHORT sChoice, cChoice, usesquare;
   LONG  usecolor;

   if(textneeded)        /* First call since mode change?               */
   {
       prompt();
       sStop = 255;        /* Top of list of squares yet to change      */
       cStop = 4095 - 256; /* Top of list of colors still needing use   */

       for(usecolor=0; usecolor<256; usecolor++)   /* Initialize colors */
       {
          usesquare = usecolor;
          squares[usesquare] = usesquare;
          squarecolor[usesquare] = usecolor;
          hamBox(usecolor, xpos[usesquare % 16], ypos[usesquare / 16]);
       }

       for(usecolor=256; usecolor<4095; usecolor++) /* Ones not yet used */
          freecolors[usecolor - 256] = usecolor;
   }

/*************************************************************************
 * Randomly choose next square to change such that all squares change
 * color at least once before any square changes twice.  squares[0]
 * through squares[sStop] are the square numbers that have not yet
 * changed in this pass.  RangeRand(r) is an integer function provided
 * in "amiga.lib" which produces a random result in the range 0 to
 * (r-1) given an integer r in the range 1 to 65535.
 ************************************************************************/

   sChoice = RangeRand(sStop + 1);        /* Pick a remaining square    */

   usesquare = squares[sChoice];          /* Extract square number      */
   squares[sChoice] = squares[sStop];     /* Swap it with sStop slot    */
   squares[sStop] = usesquare;

   if(NOT sStop--) sStop = 255;           /* Only one change per pass   */

/************************************************************************
 * Randomly choose new color for selected square such that all colors
 * are used once before any color is used again, and such that no two
 * squares simultaneously have the same color.  freecolors[0] through
 * freecolors[cStop] are the colors that have not yet been choosen in
 * this pass.  Note that the 256 colors in use at the end of the
 * previous pass are not available for choice in this pass.
 ***********************************************************************/

   cChoice = RangeRand(cStop + 1);

   usecolor = freecolors[cChoice];
   freecolors[cChoice] = freecolors[cStop];
   freecolors[cStop] = squarecolor[usesquare];
   squarecolor[usesquare] = usecolor;

   if(NOT cStop--) cStop = 4095 - 256;
      

   hamBox(usecolor, xpos[usesquare % 16], ypos[usesquare / 16]);
}


colorWheel()                  /* Display an ordered set of colors */
{
   SHORT  i, j;

   if(textneeded)
   {
      prompt();

      SetAPen(rp, 2);              /* Green pen for green color numbers */
      Move(rp, 260, ypos[15]+17);
      Text(rp, "Green", 5);
      for(i=0; i<16; i++)
      {
         Move(rp, xpos[i]+3, ypos[15]+17);
         Text(rp, number[i], 1);
      }

      SetAPen(rp, 3);              /* Blue pen for blue color numbers   */
      Move(rp, 4, 18);
      Text(rp, "Blue", 4);
      for(i=0; i<16; i++)
      {
         Move(rp, 7, ypos[i]+6);
         Text(rp, number[i], 1);
      }

      SetAPen(rp, 1);              /* Red pen for red color numbers     */
      Move(rp, 271, 100);
      Text(rp, "Red", 3);

      sequence = 0;
   }

   SetAPen(rp, 1);                 /* Identify the red color in use     */
   SetDrMd(rp, JAM2);
   Move(rp, 280, 115);
   Text(rp, number[sequence], 1);

   for(j=0; j<16; j++)             /* Update all of the squares         */
      for(i=0; i<16; i++)
         hamBox((sequence<<8 | i<<4 | j), xpos[i], ypos[j]);

   if(++sequence == 16) sequence=0;
}


prompt()                           /* Display mode changing prompt */
{
   SetDrMd(rp, JAM2);
   SetAPen(rp, 4);
   Move(rp, 23, 183);
   Text(rp, "[left mouse button = new mode]", 30);
   textneeded = FALSE;
}


/**********************************************************************
 *  hamBox() -- routine to draw a colored box in Hold and Modify mode.
 *              Draws a box of size XSIZE by YSIZE with an upper left
 *              corner at (x,y).  The desired color is achieved in 3
 *              steps on each horizontal line of the box.  First we
 *              set the red component, then the green, then the blue.
 *              We achieve this by drawing a vertical line of Modify-Red,
 *              followed by a vertical line of Modify-Green, followed by
 *              a rectangle of Modify-Blue.  Note that the resulting
 *              color for the first two vertical lines depends upon the
 *              color(s) of the pixels immediately to the left of that
 *              line.  By the time we reach the rectangle we are assured
 *              of getting (and maintaining) the desired color because
 *              we have set all 3 components (R, G, and B) straight from
 *              the bit map.
 ***********************************************************************/
hamBox(color, x, y)
LONG   color, x, y;
{
   SHORT  c;

   SetDrMd(rp, JAM1);      /* Establish Drawing Mode in RastPort       */

   c=((color & 0xf00)>>8); /* Extract desired Red color component.     */
   SetAPen(rp, c + 0x20);  /* Hold G, B from previous pixel.  Set R=n. */
   Move(rp, x, y);
   Draw(rp, x, y+YSIZE);

   x++;
   c=((color & 0xf0)>>4);  /* Extract desired Green color component.   */
   SetAPen(rp, c + 0x30);  /* Hold R, B from previous pixel.  Set G=n. */
   Move(rp, x, y);
   Draw(rp, x, y+YSIZE);

   x++;
   c=(color & 0xf);        /* Extract desired Blue color component.    */
   SetAPen(rp, c + 0x10);  /* Hold R, G from previous pixel.  Set B=n. */
   RectFill(rp, x, y, x+XSIZE-2, y+YSIZE);
}

