[net.micro.amiga] More Fun with 6 Planes

bobp@amiga.UUCP (Robert S. Pariseau) (11/14/85)

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);
}

-----------------------------That's all for now!