From bloom-picayune.mit.edu!snorkelwacker.mit.edu!usc!cs.utexas.edu!swrinde!mips!news.cs.indiana.edu!umn.edu!ux.acs.umn.edu!lmaster Thu Apr 30 23:40:51 EDT 1992
Article: 5759 of alt.sources
Path: bloom-picayune.mit.edu!snorkelwacker.mit.edu!usc!cs.utexas.edu!swrinde!mips!news.cs.indiana.edu!umn.edu!ux.acs.umn.edu!lmaster
From: toddl@county.lmt.mn.org (Todd Lehman)
Newsgroups: alt.sources
Subject: VGA 80x60x256 Mode & Fractal Zoom Demo
Keywords: VGA,fractal,zoom
Message-ID: <1992Apr29.034539.13542@ux.acs.umn.edu>
Date: 29 Apr 92 03:45:39 GMT
Sender: toddl@county.lmt.mn.org
Followup-To: comp.os.msdos.programmer
Organization: University of Minnesota
Lines: 803

Archive-name: demo8060
Submitted-by: toddl@county.lmt.mn.org

/*-----------------------------------------------------------------------------

   DEMO8060.C -- A VGA 80x60x256 MODE DEMONSTRATION

      Author:    Todd S. Lehman
                 (toddl@county.lmt.mn.org)

      Compiler:  Microsoft C 6.00+

      Compile:   cl -AS -W3 -Oxz -Gcs demo8060.c
                 (Depending on how your compiler is set up, you may
                 be prompted to link with GRAPHICS.LIB explicitly.)

   This program demonstrates VGA's 80x60x256 graphics mode.  An animated zoom
   into the Mandelbrot Set is created and saved to a file for later redisplay
   at high speed.  A numeric coprocessor is highly recommended when recording
   zoom sequences, but unnecessary for playback.

   Frames are stored in a compressed format where only the differences between
   frames are recorded.  The actual compression is somewhat unsophisticated,
   but then again this is just a demo program.

   To record a zoom sequence, select a favorite spot in the M-Set and note its
   image parameters.  Pass these parameters to this program on the command line
   as follows:

      DEMO8060 filename [xcenter ycenter firstsize lastsize frames maxiter]

   For example:

      DEMO8060  demo8060.zzz -1.7464192648 .0090418669 10 .00000023 600 500

   `filename' specifies a data file and is required.

   `xcenter' and `ycenter' specify the center of the image (i.e. the point of
   zoomage), given as the real and imaginary components, respectively, of the
   central point.

   `firstsize' specifies the width of the initial frame, given as the real
   component of the difference in opposing cornerpoints of the image;
   similarly, `lastsize' specifies the width of the final frame.

   `frames' specifies the number of frames in the animation sequence.

   `maxiter' specifies a maximum number of iterations in calculation of the
   M-Set, actually the maximum-maximum number of iterations -- in any given
   frame, the maximum number of iterations dependent on the zoom level -- it is
   proportional to the inverse of the square root of the frame width.  (Perhaps
   a logarithmic relationship would be work better?)

   `xcenter,' `ycenter,' `startsize,' and `endsize' may be any 64-bit (double-
   precision) values, `frames' should be a reasonable 16-bit integer (not more
   than, say, 10000), and `maxiter' should be a reasonable 32-bit integer (at
   least 500 but not more than, say, 100000, unless the zoom depth is extreme).

   When all six of the latter parameters are omitted, playback mode is
   activated.  For example,

      DEMO8060  demo8060.zzz

   will cause the file demo8060.zzz to be replayed.  (The extension .zzz in the
   example was chosen to remind you that you may like to take a nap while the
   sequence is being recorded -- unless you have a 486/50, 1000 frames can take
   several hours.)

   At any point the Escape key can be pressed to exit the program.

   Enjoy!

   Todd Lehman
   April 1992


   NOTES:

   Since this is just a demo program, error checking is minimal, if not non-
   existent.  If you do something weird, like run out of disk space, well...
   don't do that.  Obviously you won't lose previously existing data, but you
   may lose the current animation sequence.  This program is intended primarily
   as an introduction to the 80x60x256 VGA mode, and only secondarily as usable
   software.  In other words, you may find that it is somewhat user-grouchy.

   When referencing screen and bitmap data, this program uses the (i,j)
   coordinate system, which assumes an origin (0,0) at the upper lefthand
   corner of the array, with the i coordinate being the vertical component and
   the j coordinate being the horizontal component.  In other words, bitmap
   arrays and physical device coordinates in this program are row-major in
   nature.

   This program and its source have been placed in the public domain and were
   posted to comp.os.msdos.programmer, rec.games.programmer, and alt.fractals
   on USENET.  The author would appreciate a copy of any derivative work, but
   this is of course not mandatory.

-----------------------------------------------------------------------------*/

// External definitions.

   #include <stdlib.h>
   #include <stdio.h>
   #include <conio.h>
   #include <fcntl.h>
   #include <sys\types.h>
   #include <sys\stat.h>
   #include <io.h>
   #include <dos.h>
   #include <share.h>
   #include <memory.h>
   #include <string.h>
   #include <math.h>
   #include <limits.h>
   #include <graph.h>


// Basic type definitions.

   typedef  unsigned char  BYTE, PIXEL;
   typedef  unsigned int   BOOL;
   #define  FALSE  0
   #define  TRUE   1


// Function prototypes.

   void     InitGraphics         (void);
   void     ExitGraphics         (void);
   void     InitPalette          (void);
   void     WaitRetrace          (int n);
   void     DisplayFrameDelta    (void);
   void     WriteFrameDelta      (int handle);
   int      ReplayFrames         (char *filename);
   int      MSetFrames           (char *filename, int frames, long maxmaxiter,
                                  double xcenter, double ycenter,
                                  double xsize_first, double xsize_last,
                                  double ysize_first, double ysize_last);
   int      MSetFrame            (double xcenter, double ycenter,
                                  double xsize, double ysize, long maxiter);
   long     MSetPoint            (double x0, double y0, long n);
   PIXEL    MSetColor            (long iter, long maxiter);
   BOOL     EscapeKey            (void);



/*-----------------------------------------------------------------------------

   MAIN CONTROL

   (Please see the description in the program header.)

-----------------------------------------------------------------------------*/

char *usage =
   "\n"
   "VGA 80x60x256 MODE MANDELBROT SET ZOOM DEMO\n"
   "Version 1.00, by Todd S. Lehman, April 1992\n"
   "\n"
   "usage:  %s file [xcenter ycenter firstsize lastsize frames maxiter]\n"
   "\n"
   "Specifiying only a filename activates playback mode. If other parameters\n"
   "are given, record mode is activated.  NUL is a valid filename.\n"
   "\n"
   "Try these parameters:\n"
   "\n"
   "   xcenter       ycenter       firstsize  lastsize      frames  maxiter\n"
   "------------------------------------------------------------------------\n"
   "-1.7464192648  .0090418669        10     .00000023        600     500\n"
   " -.780866923   .1469101255         5     .00005           500    1000\n"
   "  .270238     -.004709            20     .0002            500    1000\n"
   "\n";

int _cdecl main (int argc, char *argv[])
{
   // Record an animation sequence.

      if (argc == 8)
      {
         char    *filename  = argv[1];
         double  xcenter    = atof(argv[2]);
         double  ycenter    = atof(argv[3]);
         double  firstsize  = atof(argv[4]);
         double  lastsize   = atof(argv[5]);
         int     frames     = atoi(argv[6]);
         long    maxmaxiter = atol(argv[7]);

         if (access(filename, 0) == 0)
         {
            printf("File \"%s\" already exists.  "
                   "For safety, you cannot overwrite an existing file.\n"
                   "Please remove the file with DEL or ERASE, or "
                   "specify a different file.\n", filename);
            return(1);
         }
         InitGraphics();
         MSetFrames(filename, frames, maxmaxiter,
                    xcenter, ycenter,
                    firstsize * 1.00, lastsize * 1.00,
                    firstsize * 0.75, lastsize * 0.75);
         ReplayFrames(filename);
         ExitGraphics();
         return(0);
      }


   // Replay an animation sequence.

      else if (argc == 2)
      {
         char *filename = argv[1];
         if (access(filename, 0) != 0)
         {
            printf("You bonehead, file \"%s\" doesn't exist!\n", filename);
            return(1);
         }
         InitGraphics();
         ReplayFrames(filename);
         ExitGraphics();
         return(0);
      }


   // Report program usage.

      else
      {
         char progname[16];
         *strrchr(strlwr(strcpy(progname,strrchr(argv[0],'\\')+1)),'.') = '\0';
         printf(usage, progname);
         return(argc == 1 ?  0 : 1);
      }
}



/*-----------------------------------------------------------------------------

   INITIALIZE 80x60x256 GRAPHICS MODE

   This routine is called once to initialize the 80x60x256 graphics mode.  Once
   initialized, the display screen is accessed simply as a two-dimensional
   array of pixels, one byte per pixel.  To set a pixel at (i,j) to the color
   p, the following is written:

      SCREEN[i][j] = p;

   This is a row-major array, so i is the vertical coordinate, starting at the
   top with zero, and j is the horizontal coordinate, starting at the left with
   zero.  Elements of the array SCREEN are in direct correspondence with the
   [large and contrived] pixels on the physical display surface.  That is to
   say, no graphics library calls are necessary to set and read pixels, nor are
   tweaks to video registers.

   How could this possibly work?  The VGA is first programmed with the settings
   for the standard BIOS Mode 13h (320x200x256).  Display memory in this mode
   is simply a 64K contiguous block beginning at A000:0000.  Next, certain VGA
   registers are tweaked directly to snap it into Mode Z (80x60x256), which is
   simply a derivative of Mode X (320x240x256).

   A basic understanding of Mode X is assumed here*, and will serve as a
   commonground upon which to note the differences between Modes X and Z.
   The differences are tackled on each axis separately:

   I.  To achieve a 60-pixel-high screen, the Cell Height or Maximum Scan Line
       Register of the CRTC group is changed from 2 to 8, causing octuple-
       scanning in place of the normal double-scanning:

       1.  Write 0x09 to port 0x3D4 (CRTC Address Register).
       2.  Write 0x47 to port 0x3D5 (CRTC Data Register:  Cell Height).

   II. To achieve a 80-pixel wide screen, the VGA is programmed in such a way
       that software is tricked into thinking the screen is 80 pixels wide,
       when in fact it is really 320 pixels wide.  Mode X is a planarized mode,
       meaning in this case that pixels are grouped by fours to a common
       address.  To select among four members in a group, the Sequencer's Map
       Mask Register is manipulated for writes and the Graphics Controller's
       Read Map Select Register is manipulated for reads.  If the Map Mask
       Register is set to write simultaneously to all four planes, then an
       entire group of four pixels is assigned a common value when the
       associated memory address is written to.  Dividing 320 pixels by 4
       pixels in a group results in an effective width of 80 pixels.  Obtaining
       correct values with read operations is simply a matter of choosing an
       arbitrary plane, since all four planes contain the same data.

       3.  Write 0x02 to port 0x3C4 (Sequencer Address Register).
       4.  Write 0x0F to port 0x3C5 (Sequencer Data Register:  Map Mask).
       5.  Write 0x04 to port 0x3CE (Graphics Controller Address Register).
       6.  Write 0x00 to port 0x3CF (Graphics Controller Data Reg:  Read Map).

   And that's it!  To get from Mode X to Mode Z, only six registers must be
   written.  Display memory in Mode Z is organized identical to that of Mode X,
   except that three-fourths of it is wasted, as is the case with Mode 13h.  To
   the VGA, this new Mode Z is pretty funky, gobbling up about 19K of precious
   display memory.  However, to the application software, this mode is rather
   elegant -- a 4800-byte contiguous chunk of memory with no strings attached
   -- just write to and read from it as if it were a two-dimensional array.
   (It is actually set up as a one-dimensional array of pointers to scan lines
   in display memory, but for all practical purposes, due to the magic of C
   pointers, it behaves just like any other two-dimensional array).

   If speed is an issue, the entire video memory can be written to and read
   from as a single object using a variety of standard C library calls:

      write(handle, (void *) &SCREEN[0][0], 4800);
      read(handle, (void *) &SCREEN[0][0], 4800);
      memset((void *) &SCREEN[0][0], 0, 4800);
      memcpy((void *) &SCREEN[0][0], (void *) buffer, 4800);
      etc.

   (When using the Small memory model, far pointers to the screen are used.
   That is, (void _far *) is required in calls to write() and read(), and
   _fmemset() and _fmemcpy() should be used in place of memset() and memcpy().


   * For more information on Mode X, the reader is referred to Michael Abrash's
   most excellent column in Dr. Dobb's Journal:  [1] "Mode X: 256-Color VGA
   Magic," July 1991, pp. 133-138, 154-158, [2] "More Undocumented 256-Color
   VGA Magic," August 1991, pp. 165-169, 181-183, and [3] "256-Color VGA
   Animation," September 1991, pp. 127-130, 143-147.

-----------------------------------------------------------------------------*/

#define  DI  60           // Height of display in pixels.
#define  DJ  80           // Width of display in pixels.
PIXEL _far *SCREEN [DI];  // Direct access to display screen as 2D array.

void InitGraphics (void)
{
   // Set up the special screen array for direct access as a two-dimensional
   // array of pixels.  Display memory is 4800 contiguous bytes beginning at
   // A000:0000, but can be accessed by scan line or by individual pixel.

      {
         int i;
         for (i = 0; i < DI; i++)
            SCREEN[i] = ((PIXEL _far *) 0xA0000000) + (DJ * i);
      }


   // Set standard BIOS VGA Mode 13h and wait for completion of current video
   // cycle before directly programming VGA control registers.

      _setvideomode(_MRES256COLOR);
      WaitRetrace(1);


   // Tweak Mode 13h into Mode 80x60x256.  (These hard-coded values are really
   // disgusting and need to be documented better.)

      outp(0x3C4,0x04); outp(0x3C5,0x06);    // Disable C4 (Chain Four)
      outp(0x3C4,0x00); outp(0x3C5,0x01);    // Synchronous reset sequencer
      outp(0x3C2,0xE3);                      // 28 MHz. dot rate, 60 Hz. scan
      outp(0x3C4,0x00); outp(0x3C5, 0x03);   // Restart sequencer
      outp(0x3D4,0x11); outp(0x3D5,0x7F &    // Remove write protect on various
                         inp(0x3D5));        //   CRTC registers
      outp(0x3D4,0x06); outp(0x3D5,0x0D);    // Vertical total
      outp(0x3D4,0x07); outp(0x3D5,0x3E);    // Overflow
      outp(0x3D4,0x09); outp(0x3D5,0x47);    // Cell height
      outp(0x3D4,0x10); outp(0x3D5,0xEA);    // V-sync start
      outp(0x3D4,0x11); outp(0x3D5,0xAC);    // V-sync end & protect cr0-cr7
      outp(0x3D4,0x12); outp(0x3D5,0xDF);    // V-displayed
      outp(0x3D4,0x15); outp(0x3D5,0xE7);    // V-blank start
      outp(0x3D4,0x16); outp(0x3D5,0x06);    // V-blank end
      outp(0x3D4,0x14); outp(0x3D5,0x00);    // Disable CB4 (Count by Four)
      outp(0x3D4,0x17); outp(0x3D5,0xE3);    // Enable W/B (Word/Byte Mode)
      outp(0x3C4,0x02); outp(0x3C5,0x0F);    // Simultaneous 4-plane writes
      outp(0x3CE,0x04); outp(0x3CF,0x00);    // Read plane 0


   // Clear the screen.  This is necessary because the Mode 13h only uses
   // plane 0.  If the screen weren't cleared here, there would be random
   // vertical stripes of color from uncleared planes 1, 2, and 3.

      _fmemset(&SCREEN[0][0], 0, DI * DJ);


   // Wait one-half second for the video signal to stabilize and initialize
   // the color palette.

      WaitRetrace(30);
      InitPalette();
}

void ExitGraphics (void)
{
   _setvideomode(_DEFAULTMODE);
}



/*-----------------------------------------------------------------------------

   INITIALIZE PALETTE

   A simple palette is constucted which covers the basic rainbow colors.  Due
   to the method of frame compression employed, only the first half (128) of
   the palette is actually defined.  The first two entries are black and white,
   the remaining 126 are divided among smooth transitions between adjacent
   pairs of blue, cyan, green, yellow, red, and magenta, i.e. the rainbow.

   Since the DAC registers are not programmed directly here, initialization of
   the palette may not be instant.  Some VGA BIOS's can only redefine one
   palette value per vertical trace cycle, meaning that an entire palette of
   256 values can take over 4 seconds (yuck).  Other VGA's can initialize an
   entire palette in one cycle.

-----------------------------------------------------------------------------*/

void InitPalette (void)
{
   long pal[256];
   int  n, i, k = 0;

   #define  BLACK  ((PIXEL)0)
   #define  WHITE  ((PIXEL)1)

   #define  OFF  (BYTE)0
   #define  ON   (BYTE)63
   #define  INC  (BYTE)((i*ON)/n)
   #define  DEC  (BYTE)(((n-i)*ON)/n)
   #define  RGB(r,g,b)   (((((long)(b) << 8) | (g)) << 8) | (r))
   #define  RANGE(N,R,G,B)  for(n=N,i=0;i<n;i++)  pal[k++]=RGB(R,G,B);

   //       N    Red      Green    Blue
   //
   RANGE(   1,   OFF,     OFF,     OFF   );     //         Black
   RANGE(   1,   ON,      ON,      ON    );     //         White
   RANGE(  24,   OFF,     INC,     ON    );     //     Blue -> Cyan
   RANGE(   8,   OFF,     ON,      DEC   );     //     Cyan -> Green
   RANGE(  10,   INC,     ON,      OFF   );     //    Green -> Yellow
   RANGE(  42,   ON,      DEC,     OFF   );     //   Yellow -> Red
   RANGE(  17,   ON,      OFF,     INC   );     //      Red -> Magenta
   RANGE(  25,   DEC,     OFF,     ON    );     //  Magenta -> Blue

   //_remapallpalette((long _far *) &pal[0]);
   //   ^ for some reason, this didn't work.

   for (i = 0; i < 128; i++)
      _remappalette(i, pal[i]);
}



/*-----------------------------------------------------------------------------

   WAIT FOR COMPLETION OF VIDEO CYCLE

   This routine is provided for synchronization with the CRT video cycle.
   Used properly, this routine can help smooth many video operations.

   At entry, `n' specifies the number of _complete_ display cycles to wait.  If
   zero, action is suspended only until completion of the current video cycle.
   At exit, a new vertical retrace period is just beginning.

-----------------------------------------------------------------------------*/

#define  IS_RETRACE()  (inp(0x3DA) & 8)

void WaitRetrace (int n)
{
   do
   {
      while ( IS_RETRACE());
      while (!IS_RETRACE());
   }
   while (n--);
}



/*-----------------------------------------------------------------------------

   FRAME ROUTINES

   This small set of routines provides a rudimentary means for tracking frame
   deltas (differences) and replaying previously recorded frame sequences by
   reconstructing a series of frames from successive deltas.

   Frame compression is primitive but adequate:  only the deltas are stored
   (rather than the compressed deltas) for speed and for simplicity.  Deltas
   here are simply pixel values and jump values, defined like so:

        0-127:  Pixel value.  Store pixel and increment target address by one.
      128-255:  Jump value.  Increment target address by this value minus 127.

   Frame delta can never be larger than the frame size.  Observed compression
   ratios in practice tests ranged from 10:1 to 2:1.

   (A superior compression technique would surely incorporate some kind of
   Huffman encoding or Lempel-Ziv compression, but at the time of this writing
   the author had not yet studied these compression techniques in depth.)

-----------------------------------------------------------------------------*/

// A frame is two-dimensional array of pixels of height DI and width DJ.  Here
// in addition to the special direct screen frame are three global static
// general-purpose frames.

   typedef  PIXEL  FRAME [DI] [DJ];

   FRAME f0, f1, f2;


// Calculate address of frame, initialize frame, copy frame, and display frame.

   #define ADDR_FRAME(x)   (&((x)[0][0]))
   #define INIT_FRAME(x)   _fmemset(ADDR_FRAME(x), (PIXEL) 0,    sizeof(FRAME))
   #define COPY_FRAME(x,y) _fmemcpy(ADDR_FRAME(x), ADDR_FRAME(y),sizeof(FRAME))
   #define DISP_FRAME(x)   COPY_FRAME(SCREEN, x);


// Display delta between two frames.

   void DisplayFrameDelta (void)
   {
      PIXEL p;
      int i, j;
      for (i = 0; i < DI; i++)
      for (j = 0; j < DJ; j++)
         SCREEN[i][j] = ((p = f1[i][j]) != f0[i][j])?  WHITE : p;
   }


// Write the delta between two frames to an output file.

   void WriteFrameDelta (int handle)
   {
      #define  WRITE_BYTE(b)  { BYTE x = (BYTE)(b); write(handle, &x, 1); }
      #define  WRITE_RUN(r)   { WRITE_BYTE(127+(r)); r = 0; }

      int i, j;
      BYTE run = 0;

      for (i = 0; i < DI; i++)
      for (j = 0; j < DJ; j++)
      {
         PIXEL p = f1[i][j];
         if (p == f0[i][j])
         {
            if (++run == 128)
               WRITE_RUN(run);
         }
         else
         {
            if (run) WRITE_RUN(run);
            WRITE_BYTE((p - f0[i][j]) & 127);
         }
      }
      if (run) WRITE_RUN(run);
   }


// Replay animation sequence from specified input file.

   int ReplayFrames (char *filename)
   {
      int   handle;

      static BYTE buf [4096];    // Input buffer, pointer, and counter.
      BYTE   *bp;
      int    bc = 0;

      static FRAME f;            // Work frame buffer, pointers, and counters.
      PIXEL  *fp, _far *sp;
      int    fc = 0;

      if (-1 == (handle = open(filename, O_BINARY | O_RDONLY, SH_DENYWR)))
         return(FALSE);

      INIT_FRAME(f);
      DISP_FRAME(f);

      while (1)
      {
         // Reinitialize frame pointers when time to begin a new frame.  Also
         // synchronize with vertical retrace and check for the Escape key.

            if (fc <= 0)
            {
               fp = ADDR_FRAME(f);
               sp = ADDR_FRAME(SCREEN);
               fc = DI * DJ;

               WaitRetrace(0);
               if (EscapeKey())
                  break;
            }


         // Read another section of the input file when the buffer is empty.

            if (bc == 0)
            {
               bp = &buf[0];
               if ((bc = read(handle, (void *) buf, sizeof(buf))) <= 0)
                  break;
            }


         // Interpret the next input byte, advancing the asscoiated pointers
         // and decrementing the associated counters.

            {
               PIXEL p = *(bp++);
               if (p < 128)
               {
                  *(sp++) = *(fp++) = (PIXEL)((*fp + p) & 127);
                  fc--;
               }
               else
               {
                  p  -= 127;
                  sp += p; fp += p;
                  fc -= p;
               }
               bc--;
            }
      }

      return(close(handle) != -1);
   }



/*-----------------------------------------------------------------------------

   M-SET FRAME ROUTINES

   In the interest of simplicity, this code takes the brute-force approach for
   each frame.  Speed enhancements can be had by analyzing previous frames and
   deciding which parts are most likely to change.  Large uniformly colored
   areas need not be resampled at each frame, just the borders between color
   regions.  Such enhancements are not addressed in this demo program.

   Actually, while the current brute-force approach may be slow, it _is_ the
   most general approach and is probably the most straightforward.

-----------------------------------------------------------------------------*/

// Calculate, display, and record a sequence.

   int MSetFrames (char *filename, int frames, long maxmaxiter,
                   double xcenter, double ycenter,
                   double xsize_first, double xsize_last,
                   double ysize_first, double ysize_last)
   {
      int handle, frame;
      double xsize, ysize;
      double xratio = pow(xsize_last/xsize_first, 1.0/(double)max(frames-1,1));
      double yratio = pow(ysize_last/ysize_first, 1.0/(double)max(frames-1,1));

      if (-1 == (handle = sopen(filename, O_BINARY | O_WRONLY | O_CREAT,
                                          SH_DENYRW,
                                          S_IREAD | S_IWRITE)))
         return(FALSE);

      INIT_FRAME(f0);
      INIT_FRAME(f1);

      for (frame = 0, xsize = xsize_first, ysize = ysize_first;
           frame < frames;
           frame++, xsize *= xratio, ysize *= yratio)
      {
         double m = 100.0 / sqrt(xsize);
         long maxiter = m <= (double)maxmaxiter ? (long)m : maxmaxiter;

         if (!MSetFrame(xcenter, ycenter, xsize, ysize, maxiter))
            break;

         if (frame > 0)
            DisplayFrameDelta();
         WriteFrameDelta(handle);

         DISP_FRAME(f1);
         COPY_FRAME(f0, f1);
      }

      return(close(handle) != -1);
   }


// Calculate and display a single frame in a sequence.

   double  EPSILON;

   int MSetFrame (double xcenter, double ycenter, double xsize, double ysize,
                  long maxiter)
   {
      int i, j;

      EPSILON = xsize / 1000000.0;

      for (i = 0; i < DI; i++)
      for (j = 0; j < DJ; j++)
      {
         double y = ycenter - (ysize * ((double)i / (double)(DI-1) - 0.5));
         double x = xcenter + (xsize * ((double)j / (double)(DJ-1) - 0.5));

         SCREEN[i][j] = WHITE;
         SCREEN[i][j] = f1[i][j] = MSetColor(MSetPoint(x,y, maxiter), maxiter);

         if (EscapeKey())
            return(FALSE);
      }

      return(TRUE);
   }


// Calculate a single point in the Argand plane.

   long MSetPoint (double x0, double y0, long n)
   {
      double _x, _y, x, y, x2, y2;
      long _i, i;

      static BOOL periodicity = FALSE;


      // Test for membership in main disc.

         if (x0 <= -0.75)
         {
            x = x0 + 1.0;
            if (x*x + y0*y0 <= 0.0625)
               return(n);
         }


      // Test for membership in main cardioid.

         else
         {
            x = x0 - 0.25; y = x*x + y0*y0; x += y + y;
            if (x * x <= y)
               return(n);
         }


      // Handle other cases with periodicity checking, a la FractInt.

         x = x0; y = y0;

         if (periodicity)
         {
            _i = 1; _x = _y = 100.0;
            for (i = 0; (i < n) && ((x2 = x*x) + (y2 = y*y) < 4.0); i++)
            {
               if ((fabs(x-_x) < EPSILON) && (fabs(y-_y) < EPSILON))
                  return(n);
               else if (i == _i)
                  { _x = x; _y = y; _i <<= 1; }

               y *= x; y += y + y0;
               x = x2 - y2 + x0;
            }
         }
         else
         {
            for (i = 0; (i < n) && ((x2 = x*x) + (y2 = y*y) < 4.0); i++)
            {
               y = x * y; y += y + y0;
               x = x2 - y2 + x0;
            }
            periodicity = (i == n);
         }
         return(i);
   }


// Assign color value to iteration result.  The method of color mapping
// employed here is simplistic, so it has been broken out of the main loops
// for easier modification if a better algorithm is needed.

   PIXEL MSetColor (long iter, long maxiter)
   {
      if (iter < maxiter)
         return((PIXEL)(2 + (iter % 126)));
      else
         return(BLACK);
   }



/*-----------------------------------------------------------------------------

   TEST KEYBOARD FOR ESCAPE KEY

   At any point the viewer can exit the program by pressing the Escape key.

-----------------------------------------------------------------------------*/

BOOL EscapeKey (void)
{
   if (kbhit())
   {
      int ch = getch();
      if (ch == 27) return(TRUE);
      else if (ch == 0) getch();
   }
   return(FALSE);
}


