From: k044477@hobbes.kzoo.edu (Jamie R. McCarthy)
Subject: ColorTables:  misc. tidbits and code
Organization: Kalamazoo College
Date: Thu, 3 Sep 1992 16:32:09 GMT

This article starts off with some AppleLink mail from Forrest Tanaka,
telling you more than you ever wanted to know about color tables, and
ends with a fair amount of ANSI C source code for manipulating them.
This is, in short, a hodgepodge of answers and solutions.  If you're
a little confused about color graphics on the Mac (who isn't?!),
well, who knows--this may clear up something for you.
 
Please note that the code is copyrighted and may not be used directly
in a for-profit program.  That's 'cause my company paid me to write
it and it's not fair for you to steal it.  Sorry.  If you want to use
it, fix a bug for me.  (There must be at least one.)  Otherwise,
you're free to use it directly in private or free programs, and to
write your own code based on mine.
 
Comments, especially on the source code, are heartily welcomed.

 Jamie McCarthy      Internet: k044477@kzoo.edu      AppleLink: j.mccarthy





Item    5744291                        10-July-92        16:02PDT
From:   DEVSUPPORTJ                    Developer Support Center
To:     J.MCCARTHYJ                    MCE-Lawrence Prod, J McCarthy
Sub:    RE-ColorTable code-level Q's
 
July 10, 1992
 
Hi Jamie
 
I received your questions about color tables, and since there are
several questions, IUll put the text of your question in a narrow
paragraph and respond to each one in the normal paragraphs
afterwards.  
 
        When drawing offscreen, every CQD routine will handle both
        indexed and sequential tables properly;  in addition,
        CopyBits will handle a "paletted" table for its source
        PixMap.  Right?
 
Yes.  If the source PixMap to CopyBits is paletted and the destination
is a window with a palette, then CopyBits uses the value field of
each ColorSpec record as a palette index for each pixel, which is of
course the whole idea behind paletted color tables.  If the
destination is anything else, then bit 14 of ctFlags is ignored and
the color table is treated as either indexed or sequential depending
on the value of bit 15.  Bit 14 is also ignored when its color
tableUs PixMap is used as the destination to CopyBits.  
 
        IM V-136, contradictorially, says "The ctFlags field is
        significant for gDevices only," and "Color tables that are
        part of [non-GDevice] pixMaps have this bit clear."  What
        this means is, "Clear your own ctFlags, use indexed tables,
        and don't mess with a GDevice's ctFlags."  Right?
 
Inside Macintosh is simply wrong on this point, though it was the
popular belief for quite a while.  The truth is that QuickDraw reads
ctFlags regardless of whether the color tableUs PixMap belongs to a
GDevice, a GWorld, or is one that you created.  If you create your
own PixMap and want to use a sequential color table, set this bit. 
If you want an indexed color table, clear this bit. QuickDraw will
read this bit and do the right thing.  I personally always make
indexed color tables because I happen to be more comfortable with
them, but you can make a sequential one if you want, and QuickDraw
will have no problems with it.
 
ItUs true that all screen GDevices have color tables which are
sequential, but that doesnUt mean that all GDevices must have
sequential color tables.  If you make a GDevice yourself, say for
off-screen drawing, then you can have either a sequential or indexed
color table.  QuickDraw will read the most significant bit of ctFlags
and act accordingly.
 
 
        I understand that video drivers expect sequential tables.
        But, is there anything at all wrong with mixing and
        matching sequential and indexed tables when they don't
        belong to GDevices?
 
No, no problem at all.  In fact, as I mentioned, you can mix and match
even if a color table belongs to a GDevice.  The only thing you canUt
do is change the color table of a screenUs GDevice directly.
 
 
        Must an indexed table be sorted?
 
No.  You can arrange an indexed color table in random order if you
want to, as long as you set the value field of each the color
specification records to the pixel value that you want for each
color.  For example, these two four-entry color tables are identical
assuming ctFlags is zero:
 
Entry  value rgb          Entry  value rgb
0      0     white        0      2     mauve
1      1     green        1      1     green
2      2     mauve        2      3     black
3      3     black        3      0     white
 
 
        IM V-138 says "...when the Color2Index routine is called,
        it can find the best match to the full 48-bit resolution
        available in a colorSpec."  Really?  Even if the inverse
        table's only 3 bits and my color table consists of (for
        example) a 256-shade gradation from black to white?
        Doesn't that require appending a whole lot of information
        after the inverse table?
 
Inside Macintosh is right, and so are you.  If you have two colors
that differ only in the least significant bit of each of their
components, Color2Index will be able to tell the difference.  The
inverse-table look-up is just the first step; itUs a coarse
estimation of what the final pixel value is. At the end of the
inverse table is a linked list of colors, called the hidden-color
list.  After the inverse-table look-up is complete, this coarse
estimate is fine-tuned using the hidden-color list so that
Color2Index can tell the difference between two colors down to the
last bit.  Color QuickDraw doesnUt always use the hidden-color list
though.  If you use the arithmetic modes with CopyBits, only the
coarse, inverse-table look-up is done.  If you use dithering with
CopyBits, the hidden-color list isnUt used either.
 
 
        What will happen if you ask CQD to manipulate a table whose
        top two ctFlags bits are 11?  (Just out of curiosity.)
 
IUve done this, and itUs fine.  If the PixMap that owns this color
table is used as a source to CopyBits and if the destination is a
window with a palette, this PixMap is treated as a paletted color
table.  If the destination is a window without a palette or a PixMap
thatUs not associated with a window at all, the color table is
treated as a sequential color table.  If the PixMap is used as the
destination to CopyBits, then itUs just treated as a sequential color
table.  Think of it this way: bit 14 has an effect if the color table
belongs to the source PixMap to CopyBits and a window with a palette
is the destination.  In any other case, bit 14 is ignored and bit 15
tells QuickDraw what to do.
 
 
Well, I hope that covers everything.  If you need any clarifications
or any more help, just let me know!
 
-- Forrest
 
 
 
 
 
/*
 * ColorTableUtils.h
 *
 * Copyright ) 1992 by Lawrence Productions, Inc.  All Rights Reserved.
 *
 * This code may not be used in any for-profit program without the
 * written permission of Lawrence Productions, Inc.
 *
 */
 
 
 
/********************************/
 
#pragma once
 
/********************************/
 
   /*
    * Note that the values of these three constants happen to equal the
    * top two bits of the ctFlags field.  The macro for getColorTableMode()
    * is the only thing that depends on this fact.
    */
   
enum {
   kIndexedColorTable = 0x00,
   kPalettedColorTable = 0x01,
   kSequentialColorTable = 0x02
} ;
 
/********************************/
 
   /*
    * Determine the mode of a color table.
    */
   
short getColorTableMode(CTabPtr theTablePtr);
#define getColorTableMode(theTablePtr) ( \
   ((unsigned short)(theTablePtr)->ctFlags) >> 14 )
#define colorTableIsIndexed(theTablePtr) \
   (getColorTableMode(theTablePtr) == kIndexedColorTable)
#define colorTableIsPaletted(theTablePtr) \
   (getColorTableMode(theTablePtr) == kPalettedColorTable)
#define colorTableIsSequential(theTablePtr) \
   (getColorTableMode(theTablePtr) == kSequentialColorTable)
 
 
   /*
    * Determine whether a color table is sorted.  Naturally, it's designed
    * for indexed tables, but works (trivially) with sequential and
    * paletted tables too.
    */
   
Boolean colorTableIsSorted(CTabPtr theTable);
#define assertColorTableIsSorted(theTable) ASSERT(colorTableIsSorted(theTable));
 
 
   /*
    * Find an entry's offset in a color table.  Naturally, it's designed
    * for indexed tables, but works (trivially) on sequential tables too.
    * Paletted tables may have none or several entries that point to a
    * particular palette entry, and this routine will not attempt to
    * search them, returning kNoEntryFound instead.
    * 
    * For all tables, the output is the index into the ctTable field.
    */
   
#define kNoEntryFound (-1)
short findEntryInSortedTable(CTabPtr theTable, short wEntry);
 
 
   /*
    * Add an entry to a color table.  Returns TRUE if the entry was
    * actually added (i.e. if the table's size went up), and FALSE if
    * the entry wasn't added.  The entry number is of course in the
    * value field.
    *
    * For this one, the table really _does_ have to be indexed.
    */
   
Boolean addEntryToSortedIndexedTable(CTabHandle theIndexedTable, ColorSpec *theColorSpec);
 
 
   /*
    * Convert color storage modes back 'n' forth.  These allocate memory
    * and don't alter the original table.
    *
    * You can't switch to or from a paletted color table without knowing
    * what the palette is, so no routines are provided for that.
    */
   
CTabHandle makeIndexedFromSequential(CTabHandle theSequentialTable);
CTabHandle makeSequentialFromSortedIndexed(CTabHandle theIndexedTable, RGBColor *fillColor);
 
 
   /*
    * Sort a table's entries.  Does nothing if the table is sequential
    * or paletted.
    */
   
void sortIndexedTable(CTabHandle theTable);
 
 
   /*
    * Compare two tables, even if they use different modes.  Paletted
    * tables can't be compared to anything, and so always return FALSE.
    * If both tables are indexed, they must both be sorted.
    */
   
Boolean colorTablesAreEquivalent(CTabHandle table1, CTabHandle table2, RGBColor *fillColor);
 
 
   /*
    * Functions to manipulate the number of entries of a color table.
    * The "get" function is simple, so it comes with a macro.  The
    * others alter the ctSize field and resize the handle;  "set"
    * sets the number of entries directly, while "change" adds or
    * subtracts from the current number of entries.  If the table's
    * size changes, it (and memory) may be moved, even if it's
    * locked.  If the table is lengthened, the new entries contain
    * garbage.
    */
   
short getNCTEntries(CTabPtr theTable);
#define getNCTEntries(theTable) ((theTable)->ctSize+1)
void setNCTEntries(CTabHandle theTable, short nEntries);
void changeNCTEntries(CTabHandle theTable, short nEntries);
 
 
   /*
    * Quickie macros to convert a table's depth to the maximum number of
    * entries, and to convert a number of entries to a minimum depth.
    *
    * The function prototypes are only there to do a little parameter
    * type-checking;  since the macros do their conversion in place,
    * the functions wouldn't actually do anything if you were to call
    * them.  (And if they took pointers instead of doing the work in
    * place, you couldn't use register variables, ick.)  If the functions
    * ever actually _do_ get called, they pop into the debugger with
    * an appropriate message.
    */
 
void convertDepthToMaxNEntries(short d, short n);  
#define convertDepthToMaxNEntries(d,n)                \
   {  short ___depth=(d);                             \
      for ((n)=1; ___depth>=1; --___depth)            \
         (n) <<= 1;                    }
 
void convertNEntriesToMinDepth(short n, short d);
#define convertNEntriesToMinDepth(n,d)                \
   {  short ___nEntries=(n);                          \
      for ((d)=0; ___nEntries>1; ___nEntries>>=1)     \
         ++(d);                        }
 
 
 
 
 
/*
 * ColorTableUtils.c
 *
 * Copyright ) 1992 by Lawrence Productions, Inc.  All Rights Reserved.
 *
 * This code may not be used in any for-profit program without the
 * written permission of Lawrence Productions, Inc.
 *
 */
 
 
 
/********************************/
 
#include "ColorTableUtils.h"
 
/********************************/
 
   /*
    * You'll need to put qsort.c in your project.
    */
#include <stdlib.h>
 
/********************************/
 
   /*
    * The only thing used from JamieUtilities.c is "jmemcmp()," which is
    * just a clone of memcmp().  (I save 100 bytes of object code by
    * taking it out of mem.c, because memcmp() is the only function in
    * there that I would ever use.  I guess I'm a fanatic.)  Anyway, if
    * you don't have JamieUtilities, just #include <string.h>, drop mem.c
    * in your project, and change "jmemcmp" to "memcmp".
    */
#include <JamieUtilities.h>
 
/********************************/
 
 
 
short (getColorTableMode)(CTabPtr theTable)
{
   ASSERT(theTable != NULL);
   
   switch ( ((unsigned short) (theTable)->ctFlags) >> 14 ) {
      
      case 0x00:     return kIndexedColorTable;       break;
      case 0x01:     return kPalettedColorTable;      break;
      case 0x02:     return kSequentialColorTable;    break;
      default:       ASSERT(0);     return -1;        break;
      
   }
}
 
 
 
Boolean colorTableIsSorted(CTabPtr theTable)
{
   register ColorSpec *theCTTable;
   register short cPosition, nEntriesMinusOne, lastEntry;
   
   ASSERT(theTable != NULL);
   
   if (!colorTableIsIndexed(theTable)) {
      return TRUE;
   }
   
   theCTTable = &theTable->ctTable[0];
   nEntriesMinusOne = theTable->ctSize;
   cPosition = 0;
   lastEntry = -1;
   for (cPosition = 0; cPosition <= nEntriesMinusOne; ++cPosition) {
      
      register short thisEntry;
      
      if ( (thisEntry=theCTTable[cPosition].value) < lastEntry) {
         return FALSE;
      } else {
         lastEntry = thisEntry;
      }
      
   }
   
   return TRUE;
}
 
 
 
      /*
       * Returns TRUE iff the entry was found.  If the entry was not found,
       * it returns the position where it should be inserted.
       */
      
   Boolean engineFindEntryInSortedTable(CTabPtr tablePtr, register short wEntry, short *thePosition);
   Boolean engineFindEntryInSortedTable(CTabPtr tablePtr, register short wEntry, short *thePosition)
   {
         /*
          * I'm 99.9% sure this code will find an existing entry, but only
          * 80% sure that it'll return the proper position for a nonexisting
          * one, the proper position being the entry to the right.  I suppose
          * I should test it rigorously one of these days...
          */
         
      register unsigned short cPosition;
      register unsigned short leftBounds, rightBounds;
      register ColorSpec *theCSpecArray;
      
      if (!colorTableIsIndexed(tablePtr)) {
         if (wEntry <= tablePtr->ctSize) {
            *thePosition = wEntry;
            return TRUE;
         } else {
            *thePosition = tablePtr->ctSize;
            return FALSE;
         }
      }
      
      assertColorTableIsSorted(tablePtr);
      
      theCSpecArray = tablePtr->ctTable;
      leftBounds = 0;  rightBounds = tablePtr->ctSize + 1 - 1;
      cPosition = rightBounds / 2;
      
      while (leftBounds < rightBounds) {
         
         if (theCSpecArray[cPosition].value == wEntry) {
            *thePosition = cPosition;
            return TRUE;
         } else {
            if (theCSpecArray[cPosition].value < wEntry) {
               leftBounds = cPosition;
               cPosition += rightBounds + 1;
               cPosition >>= 1;
            } else {
               rightBounds = cPosition;
               cPosition += leftBounds;
               cPosition >>= 1;
            }
         }
         
      }
      
         /*
          * If the requested entry is not there, we want the entry that's
          * right _after_ where it would go.
          */
         
      if (theCSpecArray[cPosition].value < wEntry) {
         ++cPosition;
      }
      
      ASSERT(cPosition == tablePtr->ctSize+1 || theCSpecArray[cPosition].value > wEntry);
      ASSERT(cPosition == 0 || theCSpecArray[cPosition-1].value < wEntry);
      
      *thePosition = cPosition;
      return FALSE;
   }
   
   
short findEntryInSortedTable(CTabPtr theTable, short wEntry)
{
   short thePosition;
   
   if (engineFindEntryInSortedTable(theTable, wEntry, &thePosition)) {
      return thePosition;
   } else {
      return kNoEntryFound;
   }
}
 
 
 
Boolean addEntryToSortedIndexedTable(CTabHandle theIndexedTable, ColorSpec *theColorSpec)
{
   register CTabPtr theIndexedTabPtr;
   short wEntry;
   short thePosition;
   
   ASSERT( colorTableIsIndexed(*theIndexedTable) );
   
   wEntry = theColorSpec->value;
   
   theIndexedTabPtr = *theIndexedTable;
   if (engineFindEntryInSortedTable(theIndexedTabPtr, wEntry, &thePosition)) {
      
         /* No need to add the entry, it's already there. */
      
      theIndexedTabPtr->ctTable[thePosition] = *theColorSpec;
      
      return FALSE;
      
   } else {
      
      long newSize;
      
      newSize = sizeof(ColorTable)
         + sizeof(ColorSpec) * ( theIndexedTabPtr->ctSize + 1 );
      SetHandleSize( (Handle) theIndexedTable, newSize );
      theIndexedTabPtr = *theIndexedTable;
      
      BlockMove( &theIndexedTabPtr->ctTable[thePosition],
         &theIndexedTabPtr->ctTable[thePosition]+1,
         (theIndexedTabPtr->ctSize+1 - thePosition) * sizeof(ColorSpec) );
      
      theIndexedTabPtr->ctTable[thePosition] = *theColorSpec;
      
      return TRUE;
      
   }
}
 
 
 
CTabHandle makeIndexedFromSequential(CTabHandle theSequentialTable)
{
   CTabHandle theIndexedTable;
   register ColorSpec *theSequentialColorSpecPtr;
   register ColorSpec *theIndexedColorSpecPtr;
   register CTabPtr theSequentialTablePtr;
   register CTabPtr theIndexedTablePtr;
   register short cIndexedEntry;
   register short nSequentialEntriesMinusOne;
   
   ASSERT(theSequentialTable != NULL);
   ASSERT(colorTableIsSequential(*theSequentialTable));
   
   nSequentialEntriesMinusOne = (**theSequentialTable).ctSize;
   
   theIndexedTable = (CTabHandle) NewHandle( sizeof(ColorTable)
      + sizeof(ColorSpec) * nSequentialEntriesMinusOne );
   theIndexedTablePtr = *theIndexedTable;
   theSequentialTablePtr = *theSequentialTable;
   
   theIndexedTablePtr->ctSeed = theSequentialTablePtr->ctSeed;
   theIndexedTablePtr->ctFlags = theSequentialTablePtr->ctFlags
      & 0x3FFF
      | 0x0000;
   theIndexedTablePtr->ctSize = nSequentialEntriesMinusOne;
   theIndexedColorSpecPtr = &theIndexedTablePtr->ctTable[0];
   theSequentialColorSpecPtr = &theSequentialTablePtr->ctTable[0];
   
   for (cIndexedEntry = 0;
      cIndexedEntry <= nSequentialEntriesMinusOne;
      ++cIndexedEntry) {
      
      theIndexedColorSpecPtr->value = cIndexedEntry;
      (theIndexedColorSpecPtr++)->rgb = (theSequentialColorSpecPtr++)->rgb;
      
   }
   
   return theIndexedTable;
}
 
 
 
CTabHandle makeSequentialFromSortedIndexed(CTabHandle theIndexedTable, RGBColor *fillColor)
{
   CTabHandle theSequentialTable;
   register ColorSpec *theIndexedColorSpecPtr;
   register ColorSpec *theSequentialColorSpecPtr;
   register CTabPtr theIndexedTablePtr;
   register CTabPtr theSequentialTablePtr;
   register short cSequentialEntry, nextIndexedEntry, nextIndexedPosition;
   register short nIndexedEntriesMinusOne, nSequentialEntriesMinusOne;
   
   ASSERT(theIndexedTable != NULL);
   ASSERT(colorTableIsIndexed(*theIndexedTable));
   assertColorTableIsSorted(*theIndexedTable);
   
   nIndexedEntriesMinusOne = (**theIndexedTable).ctSize;
   nSequentialEntriesMinusOne =
      (**theIndexedTable).ctTable[nIndexedEntriesMinusOne].value;
   
   theSequentialTable = (CTabHandle) NewHandle( sizeof(ColorTable)
      + sizeof(ColorSpec) * nSequentialEntriesMinusOne );
   theSequentialTablePtr = *theSequentialTable;
   theIndexedTablePtr = *theIndexedTable;
   
   theSequentialTablePtr->ctSeed = theIndexedTablePtr->ctSeed;
   theSequentialTablePtr->ctFlags = theIndexedTablePtr->ctFlags
      & 0x3FFF
      | 0x8000;
   theSequentialTablePtr->ctSize = nSequentialEntriesMinusOne;
   theSequentialColorSpecPtr = &theSequentialTablePtr->ctTable[0];
   theIndexedColorSpecPtr = &theIndexedTablePtr->ctTable[0];
   
   nextIndexedPosition = 0;
   nextIndexedEntry = theIndexedColorSpecPtr[0].value;
   for (cSequentialEntry = 0;
      cSequentialEntry <= nSequentialEntriesMinusOne;
      ++cSequentialEntry) {
      
      ASSERT(cSequentialEntry <= nextIndexedEntry);
      
      if (cSequentialEntry == nextIndexedEntry) {
         theSequentialColorSpecPtr->value = cSequentialEntry;
         (theSequentialColorSpecPtr++)->rgb = (theIndexedColorSpecPtr++)->rgb;
         if (++nextIndexedPosition > nIndexedEntriesMinusOne) {
            nextIndexedEntry = MAXINT;
         } else {
            nextIndexedEntry = theIndexedColorSpecPtr->value;
         }
      } else {
         theSequentialColorSpecPtr->value = 0;
         (theSequentialColorSpecPtr++)->rgb = *fillColor;
      }
      
   }
   
   return theSequentialTable;
}
 
 
 
   static int compareColorSpecValues(const ColorSpec *color1, const ColorSpec *color2);
   static int compareColorSpecValues(register const ColorSpec *color1,
      register const ColorSpec *color2)
   {
      if (color1->value != color2->value) {
         if (color1->value < color2->value) {
            return -1; 
         } else {
            return 1;
         }
      }
      return 0;
   }
   
void sortIndexedTable(CTabHandle theTable)
{
   register CTabPtr theTablePtr;
   short oldState;
   
   ASSERT(theTable != NULL);
   
   theTablePtr = *theTable;
   if (!colorTableIsIndexed(theTablePtr)) {
      return ;
   }
   
   oldState = HGetState( (Handle) theTable );
   HLock( (Handle) theTable);
   qsort( &theTablePtr->ctTable[0],
      theTablePtr->ctSize+1,
      sizeof(ColorSpec),
      (__cmp_func) compareColorSpecValues);
   HSetState( (Handle) theTable, oldState );
   
   assertColorTableIsSorted(theTablePtr);
}
 
 
 
Boolean colorTablesAreEquivalent(CTabHandle table1, CTabHandle table2, RGBColor *fillColor)
{
   Boolean areEquivalent;
   
   ASSERT(table1 != NULL);
   ASSERT(table2 != NULL);
   
   areEquivalent = TRUE;
   
   if (colorTableIsPaletted(*table1) || colorTableIsPaletted(*table2)) {
         /* Paletted tables can't be compared to anything. */
      return FALSE;
   }
   
   if (colorTableIsSequential(*table1) || colorTableIsSequential(*table2)) {
      
         /*
          * Convert one of the tables to sequential, if need be, and compare.
          */
      
      register CTabPtr t1, t2;
      register short nEntriesMinusOne;
      CTabHandle convertedTable;
      
      convertedTable = NULL;
      if (colorTableIsIndexed(*table1)) {
         assertColorTableIsSorted(*table1);
         convertedTable = makeSequentialFromSortedIndexed(table1, fillColor);
         t1 = *convertedTable;
         t2 = *table2;
      } else if (colorTableIsIndexed(*table2)) {
         assertColorTableIsSorted(*table2);
         convertedTable = makeSequentialFromSortedIndexed(table2, fillColor);
         t1 = *table1;
         t2 = *convertedTable;
      }
      
      if ( (nEntriesMinusOne=t1->ctSize) != t2->ctSize) {
         
         areEquivalent = FALSE;
         
      } else {
         
         register short cEntry;
         
         for (cEntry = 0; cEntry <= nEntriesMinusOne; ++cEntry) {
            
               /*
                * When comparing sequential tables, ignore the value field.
                */
               
            if ( t1->ctTable[cEntry].rgb.red != t2->ctTable[cEntry].rgb.red
               || t1->ctTable[cEntry].rgb.green != t2->ctTable[cEntry].rgb.green
               || t1->ctTable[cEntry].rgb.blue != t2->ctTable[cEntry].rgb.blue ) {
               
               areEquivalent = FALSE;
               break;
               
            }
            
         }
         
      }
      
      DisposHandle( (Handle) convertedTable );
      
   } else {
      
         /*
          * Leave both tables indexed, and compare.  Assume they're sorted.
          */
      
      register CTabPtr t1, t2;
      register short nEntriesMinusOne;
      
      t1 = *table1;
      t2 = *table2;
      
      assertColorTableIsSorted(t1);
      assertColorTableIsSorted(t2);
      
      if ( (nEntriesMinusOne=t1->ctSize) != t2->ctSize) {
         
         areEquivalent = FALSE;
         
      } else {
         
         register short cEntry;
         
         areEquivalent = ( jmemcmp(       // jmemcmp() is just a memcmp() clone
            &t1->ctTable[0],
            &t2->ctTable[0],
            sizeof(ColorSpec)*(nEntriesMinusOne+1)
            ) == 0);
         
#if you_want_it_spelled_out_for_you
         for (cEntry = 0; cEntry <= nEntriesMinusOne; ++cEntry) {
            if ( t1->ctTable[cEntry].value != t2->ctTable[cEntry].value
               || t1->ctTable[cEntry].rgb.red != t2->ctTable[cEntry].rgb.red
               || t1->ctTable[cEntry].rgb.green != t2->ctTable[cEntry].rgb.green
               || t1->ctTable[cEntry].rgb.blue != t2->ctTable[cEntry].rgb.blue ) {
               areEquivalent = FALSE;
               break;
            }
         }
#endif
         
      }
      
   }
   
   return areEquivalent;
}
 
 
 
short (getNCTEntries)(CTabPtr theTable)
{
   return theTable->ctSize + 1;
}
 
 
 
void setNCTEntries(CTabHandle theTable, short nEntries)
{
   short oldState;
   
   ASSERT(nEntries >= 0);
   ASSERT(nEntries <= 256); // arbitrary
   
   if (getNCTEntries(*theTable) == nEntries) return;
   
   (**theTable).ctSize = nEntries-1;
   
   oldState = HGetState( (Handle) theTable );
   HUnlock( (Handle) theTable );
   SetHandleSize( (Handle) theTable,
      sizeof(ColorTable) + (nEntries-1)*sizeof(ColorSpec));
   HSetState( (Handle) theTable, oldState);
}
 
 
 
void changeNCTEntries(CTabHandle theTable, short nEntries)
{
   if (nEntries == 0) return;
   
   setNCTEntries(theTable, getNCTEntries(*theTable) + nEntries);
}
 
 
 
void (convertDepthToMaxNEntries)(short d, short n)
{
   DebugStr("\pconvertDepthToMaxNEntries was somehow called!");
}
 
 
 
void (convertNEntriesToMinDepth)(short n, short d)
{
   DebugStr("\pconvertNEntriesToMinDepth was somehow called!");
}