/* Next available MSG number is  66 */

/*    

   COLEXT.C

   Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994 by Autodesk, Inc.
 
   Permission to use, copy, modify, and distribute this software in 
   object code form for any purpose and without fee is hereby granted, 
   provided that the above copyright notice appears in all copies and 
   that both that copyright notice and the limited warranty and 
   restricted rights notice below appear in all supporting 
   documentation.
 
   AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.  
   AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 
   MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
   DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 
   UNINTERRUPTED OR ERROR FREE.
 
   Use, duplication, or disclosure by the U.S. Government is subject to 
   restrictions set forth in FAR 52.227-19 (Commercial Computer 
   Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) 
   (Rights in Technical Data and Computer Software), as applicable.
    
   .

      DESCRIPTION:

        AutoCAD colour manipulation functions

        Designed and implemented in October of 1989 by John Walker

        This ADS application permits AutoCAD colours to  be  specified
        in  any  of  a  number  of  commonly-used colour systems.  The
        application provides AutoLisp functions for each colour system
        which  accept  the  parameters  which  define a colour in that
        system and return the AutoCAD colour number from the  standard
        palette  for  256  colour  devices which best approximates the
        requested colour.

        In  addition, functions are provided to convert either AutoCAD
        colour indices  or  Red-Green-Blue  colour  triples  to  their
        representation in each of the colour systems.

        Finally, functions which convert  colours  in  non-RGB  colour
        systems to RGB triples are provided.


        COLOUR SYSTEMS
        ==============

        The colour systems implemented are:

        RGB      Colours  are  specified  as the  intensities  of  the
                 three  additive  primary colours Red, Green, and Blue
                 which generate the colour.  The intensities are given
                 in the range from 0 to 1, with 0 indicating no  light
                 of  that colour and 1 representing maximum intensity.

        CMY      Colours  are  specified by  the  intensities  of  the
                 subtractive   primary   colours  Cyan,  Magenta,  and
                 Yellow.  The CMY system is used in composing ink  and
                 toner  mixtures  for  subtractive printing processes.
                 Intensities are specified in the range from 0  to  1,
                 with  0  indicating  none  of  the  specified pigment
                 present and 1 indicating the maximum amount.

        YIQ      The  YIQ  system  is  used   to  encode  colours  for
                 television broadcasting.  The YIQ system  remaps  the
                 RGB  space such that Y represents a primary scaled to
                 the human luminosity  response  curve.   Two  colours
                 with the same Y values will be indistinguishable when
                 viewed on a monochrome monitor; to guarantee  colours
                 are  distinct  when viewed in monochrome, they should
                 be converted to YIQ and  checked  for  a  substantial
                 difference  in  Y.   Y  ranges from 0 to 1, with zero
                 indicating black and 1 maximum intensity.  Since  the
                 YIQ system is a remapping of the RGB colour cube by a
                 nontrivial affine transformation, the I and Q  values
                 in  the  YIQ  system do not "come out even"; I ranges
                 from -0.6 to 0.6, and Q ranges from  -0.52  to  0.52.

        HSV      The HSV system approximates the intuitive concepts of
                 hue  (tint),  saturation  (shade), and value (tone or
                 brightness), by mapping colours into a hexcone.   Hue
                 is  specified  by  a number from 0 to 1, representing
                 the  angle  around  the hue circle in fraction of the
                 circumference of the hue wheel with red at 0,  yellow
                 at  1/6,  and  so  on.   Saturation is expressed as a
                 number from 0 to  1,  with  0  indicating  a  totally
                 desaturated  shade  (grey  scale), and 1 a completely
                 saturated  shade  (no  admixture  of  white).   Value
                 expresses intensity from 0 to 1, with zero indicating
                 black and 1 maximum intensity.

        HLS      The  HLS system encodes the intuitive concepts of hue
                 (tint),   lightness   (intensity),   and   saturation
                 (shade), by mapping colours into  a  double  hexcone.
                 The  HLS system is closely related to the HSV system,
                 and can be thought of as the result of stretching the
                 flat   end   of   the  HSV  hexcone  upward  until  a
                 symmetrical   double   hexcone  is  formed.   Hue  is
                 specified by a number from 0 to 1,  representing  the
                 angle  around  the  hue  circle  in  fraction  of the
                 circumference of the hue wheel with red at 0,  yellow
                 at  1/6, and so on.  Lightness expresses intensity as
                 a number from 0 to 1, with zero indicating black  and
                 1  maximum  intensity.   Saturation is expressed as a
                 number from 0 to  1,  with  0  indicating  a  totally
                 desaturated  shade  (grey  scale), and 1 a completely
                 saturated shade (no admixture of white).

        CTEMP    The  CTEMP  system  specifies  colours  emitted  by a
                 Planckian  (black  body)  radiator   with   a   given
                 temperature   in   degrees  Kelvin.   Typical  colour
                 temperatures are:

                    The star Spica                  28000 K
                    The star Sirius                 10700 K
                    North sky light                  7500 K
                    Average daylight                 6500 K
                    Xenon lamp                       6000 K
                    Typical sunlight + skylight      5500 K
                    The star Betelgeuse              3400 K
                    Tungsten/halogen lamps           3300 K
                    Incandescent bulbs (100-200 W)   2900 K
                    Sunlight at sunset               2000 K
                    Candle flame                     1900 K

        CNS      The CNS system expresses colours as English language
                 names.

                 A colour is specified in the CNS system by a sequence
                 of  English  words.   CNS  colours  may be achromatic
                 (grey scale values) or chromatic.  Chromatic  colours
                 consist  of  a  hue  (dominant  spectral  component),
                 saturation (the extent of dilution with  white),  and
                 lightness  (intensity).   A chromatic hue is composed
                 by naming and mixing the following primary hues:

                    Red, Orange/Brown, Yellow, Green, Blue, Purple

                 and secondary hues:

                    Reddish, Orangish/Brownish, Yellowish,
                    Greenish, Bluish, Purplish

                 To  obtain  one of the primary hues, just specify its
                 name,  e.g.   "Yellow".   To  obtain  a  hue  halfway
                 between  two  primary  hues, compose the two bounding
                 hues: for example "Yellow-green" (or  "Green-yellow";
                 it  doesn't  matter  which  is  specified first).  To
                 obtain a hue one quarter the distance from one colour
                 to  an adjacent colour, compose the "ish" form of the
                 adjacent  colour  with  the  primary   colour.    For
                 example, the hues between Yellow and Green are named:

                    Yellow
                    Greenish yellow
                    Yellow-green (or Green-yellow)
                    Yellowish green
                    Green

                 Brown  is  a  somewhat  confusing  special case.  The
                 colour we perceive as  brown  has  the  same  hue  as
                 orange  but  when  seen  with  reduced saturation and
                 intensity it appears as brown, a colour distinct from
                 orange  (that's  why there's no brown in the rainbow,
                 in case you've ever lost sleep pondering that  fact).
                 To  compensate for this perceptual quirk, "brown" and
                 "brownish" may be used as synonyms for  "orange"  and
                 "orangish"  when  specifying  hues.   If  "brown"  or
                 "brownish"  are  used,  the  default  saturation  and
                 lightness  (see  below)  are  set  so  the orange hue
                 appears brown; if explicit saturation  and  lightness
                 are given orange and brown are synonymous.

                 Lightness  (brightness or intensity) may be specified
                 by adding one of  the  following  adjectives  to  the
                 colour name:

                    very dark
                    dark
                    medium
                    light
                    very light

                 If  no  lightness  adjective  appears,  a  default is
                 assumed (unless a brown hue was named, in which  case
                 the default will be "light").  This diverges from the
                 CNS specification in which omitted lightness defaults
                 to medium intensity.

                 Saturation (the degree of admixture of  white  light)
                 is specified by the following adjectives:

                    grayish (or greyish)
                    moderate
                    strong
                    vivid

                 "Vivid" denotes a fully saturated colour, and is  the
                 default  (unless  a brown is specified, in which case
                 the default saturation is "strong").

                 Examples of chromatic colour specifications are:

                    red
                    blue-green
                    purplish red
                    very dark green
                    strong yellow-green
                    very light grayish greenish yellow

                 the last  describing  the  colour  of  snow  I  don't
                 recommend you eat.

                 Achromatic  specifications describe 7 shades of grey,
                 to wit:

                    black
                    very dark gray
                    dark gray
                    gray (or medium gray)
                    light gray
                    very light gray
                    white

                 in all cases "grey" may be used instead of "gray".

                 Although the word order used herein is as  prescribed
                 by the formal specification of CNS, my implementation
                 is  totally  insensitive  to  word  order.   You  can
                 specify  "yellow  greyish light greenish very" if you
                 like, silly seems how notwithstanding it.


        AUTOLISP-CALLABLE FUNCTIONS
        ===========================

        Three groups of AutoLisp-callable functions  are  implemented.
        The first convert specifications in external colour systems to
        AutoCAD's internal colour indices.  These  functions  map  the
        specifications  into either AutoCAD's standard 8 or 256 colour
        palette.  The palette is selected with the (COLSET) function:

           (COLSET <gamut>)

        where  <gamut>  is  either  8  or 256 to choose the colour set
        desired.  (COLSET) returns the current colour gamut; if called
        with  no  arguments, (COLSET) returns the current colour gamut
        without changing it.  The following functions  return  AutoCAD
        colour indices between 1 and <gamut> - 1.

           (CMY   <cyan> <magenta> <yellow>)
           (CMY '(<cyan> <magenta> <yellow>))

           (CNS "CNS colour name description")

           (CTEMP <temperature>)

           (HLS   <hue> <lightness> <saturation>)
           (HLS '(<hue> <lightness> <saturation>))

           (HSV   <hue> <saturation> <value>)
           (HSV '(<hue> <saturation> <value>))

           (RGB   <red> <green> <blue>)
           (RGB '(<<red> <green> <blue>))

           (YIQ   <Y-value> <I-value> <Q-value>)
           (YIQ '(<Y-value> <I-value> <Q-value>))

        Except for the (CNS) function, which takes a string  argument,
        and  (CTEMP)  which  takes  a  single numeric temperature, all
        these  conversion  functions  accept  either  three  numerical
        arguments  (either  integer  or  real),  or  a  list  of three
        numbers.  Representing colour triples as lists,  in  the  same
        manner as three-dimensional point co-ordinates, allows them to
        be manipulated  as  units  and  operated  upon  with  AutoLisp
        functions.  For example the (distance) function can be used to
        determine distance in colour space  as  well  as  in  physical
        space.  If invalid arguments are passed to these functions, an
        error message is displayed and nil  is  returned.   The  (CNS)
        function,  which can generate a wide variety of error messages
        resulting from syntax errors  in  the  string  passed  to  it,
        indicates  an error by returning nil.  A string describing the
        most recent error  detected  by  the  (CNS)  function  can  be
        obtained  by  calling (CNSERR).  If no error has been detected
        by (CNS), (CNSERR) returns nil.

        When passed valid arguments, all  of  these  functions  return
        AutoCAD  colour  numbers  ranging  from  1  to  255.  They may
        therefore be specified at any AutoCAD prompt which requests  a
        colour number.

        A   second   group   of  functions  converts  external  colour
        specifications to lists of RGB  intensities.   Each  of  these
        functions  takes  the  same  arguments  as the functions which
        return AutoCAD colour indices.

           (CMY-RGB   <cyan> <magenta> <yellow>)
           (CMY-RGB '(<cyan> <magenta> <yellow>))

           (CNS-RGB "CNS colour name description")

           (CTEMP-RGB <temperature>)

           (HLS-RGB   <hue> <lightness> <saturation>)
           (HLS-RGB '(<hue> <lightness> <saturation>))

           (HSV-RGB   <hue> <saturation> <value>)
           (HSV-RGB '(<hue> <saturation> <value>))

           (YIQ-RGB   <Y-value> <I-value> <Q-value>)
           (YIQ-RGB '(<Y-value> <I-value> <Q-value>))

        There  is  no RGB-RGB function; it would be simply an identity
        function.

        The  third family of functions converts AutoCAD colour indices
        from 0 to 255 or RGB triples to their representation  in  each
        of  the  external  colour  systems.   AutoCAD  colour  index 0
        (black), which cannot be specified as  an  entity  colour,  is
        nonetheless a valid argument to these functions.

           (TO-CMY <colour>)
           (TO-CNS <colour>)
           (TO-HLS <colour>)
           (TO-HSV <colour>)
           (TO-RGB <colour>)
           (TO-YIQ <colour>)

        With  the  exception  of  (TO-CNS), which returns a CNS colour
        specification string, all of these functions return a list  of
        three  real numbers specifying the values in its colour system
        corresponding to the AutoCAD colour index or RGB  triple.   If
        an  RGB  triple  is  specified for <colour> it may be given as
        three arguments or as a list of three numbers.


        INTERNAL FUNCTIONS
        ==================

        Internal conversion functions implemented in this  module  are
        as  described  below.  Definitions are given as prototypes for
        readability;  for  compatibility  with  older  compilers,  the
        actual  code  is  not  prototyped.  All conversions are to and
        from RGB--to get between two non-RGB systems, you must convert
        through RGB.

                                CMY

        void rgb_cmy(ads_real r, ads_real g, ads_real b,
                     ads_real *c, ads_real *m, ads_real *y)
           Converts r, g, b (0 to 1) to c, m, y (0 to 1).

        void cmy_rgb(ads_real c, ads_real m, ads_real y,
                     ads_real *r, ads_real *g, ads_real *b)
           Converts c, m, y (0 to 1) to r, g, b (0 to 1).

                               CTEMP

        void ctemp_rgb(ads_real temperature,
                       ads_real *r, ads_real *g, ads_real *b)
           Converts a colour temperature specified in degrees Kelvin,
           to r, g, b (0 to 1).

                                YIQ

        void rgb_yiq(ads_real r, ads_real g, ads_real b,
                     ads_real *y, ads_real *i, ads_real *q)
           Converts r, g, b (0 to 1) to y (0 to 1), i (-0.6 to 0.6),
           and q (-0.52 to 0.52).

        void yiq_rgb(ads_real y, ads_real i, ads_real q,
                     ads_real *r, ads_real *g, ads_real *b)
           Converts y (0 to 1), i (-0.6 to 0.6), and q (-0.52 to 0.52)
           to r, g, b (0 to 1).

                                HSV

        void rgb_hsv(ads_real r, ads_real g, ads_real b,
                     ads_real *h, ads_real *s, ads_real *v)
           Converts r, g, b (0 to 1) to h (0 to 360), s (0 to 1),  and
           v  (0  to  1).  Note that rgb_hsv() returns hue in terms of
           degrees, not as a  fraction  of  circumference  as  do  the
           AutoLisp-callable functions.

        void hsv_rgb(ads_real h, ads_real s, ads_real v,
                     ads_real *r, ads_real *g, ads_real *b)
           Converts  h (0 to 360), s (0 to 1), and v (0 to 1) to r, g,
           b (0 to 1).  Note that rgb_hsv() expects hue  in  terms  of
           degrees,  not  as  a  fraction  of  circumference as do the
           AutoLisp-callable functions.

                                HLS

        void rgb_hls(ads_real r, ads_real g, ads_real b,
                     ads_real *h, ads_real *l, ads_real *s)
           Converts r, g, b (0 to 1) to h (0 to 360), l (0 to 1),  and
           s  (0  to  1).  Note that rgb_hls() returns hue in terms of
           degrees, not as a  fraction  of  circumference  as  do  the
           AutoLisp-callable functions.

        void hls_rgb(ads_real h, ads_real l, ads_real s,
                     ads_real *r, ads_real *g, ads_real *b)
           Converts  h (0 to 360), l (0 to 1), and s (0 to 1) to r, g,
           b (0 to 1).  Note that rgb_hls() expects hue  in  terms  of
           degrees,  not  as  a  fraction  of  circumference as do the
           AutoLisp-callable functions.

                                CNS

        void rgb_cns(ads_real r, ads_real g, ads_real b, char *cnstr)
           Edits  a  zero-terminated  CNS  description  of  the colour
           represented by r, g, and b (0 to 1), into the string cnstr.
           The  maximum  length of the edited string is 36 characters,
           so a buffer of at least 37 characters  should  be  supplied
           for  cnstr.   If  the lightness of the colour is closest to
           the CNS nomenclature for the default  lightness  stored  in
           the  scaled  integer  variable  defcnslit (initially 10000,
           representing 1),  no  intensity  is  edited.   The  default
           saturation of "vivid" is not edited.

        Boolean cns_rgb(char *cns, ads_real *r, ads_real *g, ads_real *b)
           Scans  a CNS specification in the string cns and stores RGB
           intensities in r, g, and b which range from 0 to 1.  If  no
           lightness  is  specified, the lightness (as defined for the
           HSV routines) in the  scaled  integer  defcnslit  is  used.
           This  value  is  initially  set  to  10000  for  a  default
           intensity of very light (maximum).  The function returns  1
           if  the  specification  is  valid and 0 if an incorrect CNS
           specification is supplied,  in  which  case  the  character
           pointer  cnserr  will  point to an error message describing
           the problem.

        BIBLIOGRAPHY
        ============

           Fundamentals of Interactive Computer Graphics
              by J. D. Foley and A. van Dam, Reading Massachusetts:
              Addison-Wesley, 1984.

           Measuring Colour
              by R. W. G. Hunt, West Sussex England: Ellis Horwood
              Ltd., 1987.  (Distributed by John Wiley & Sons).

           A New Color-Naming System for Graphics Languages
              by Toby Berk, Lee Brownston, and Arie  Kaufman,  Florida
              International  University,  IEEE  Computer  Graphics and
              Applications, May 1982, Page 37.

*/

#include   <stdio.h>
#include   <string.h>
#include   <ctype.h>
#include   <math.h>

#include   "adslib.h"

#ifdef lint
extern char *sprintf();               /* Adjust for Sun's strange stdio.h */
#endif

/* Special assertion handler for ADS applications. */

#ifndef NDEBUG
#define assert(ex) {if (!(ex)){ads_printf( \
                    /*MSG1*/"COLEXT: Assertion (%s) failed: file \"%s\", \
                    line %d\n", /*MSG0*/" "#ex" ",__FILE__,__LINE__); \
                    ads_abort(/*MSG2*/"Assertion failed.");}}
#else
#define assert(ex)
#endif

/*  Data types  */

typedef enum {False = 0, True = 1} Boolean;
#define V        (void)

/* Set point variable from three co-ordinates */

#define Spoint(pt, x, y, z)  pt[X] = (x);  pt[Y] = (y);  pt[Z] = (z)

/* Copy point  */

#define Cpoint(d, s)   d[X] = s[X];  d[Y] = s[Y];  d[Z] = s[Z]

/* Utility definition to get an  array's  element  count  (at  compile
   time).   For  example:  

       int  arr[] = {1,2,3,4,5};
       ... 
       printf("%d", ELEMENTS(arr));

   would print a five.  ELEMENTS("abc") can also be used to  tell  how
   many  bytes are in a string constant INCLUDING THE TRAILING NULL. */

#define ELEMENTS(array) (sizeof(array)/sizeof((array)[0]))

/* Utility definitions */

#ifdef abs
#undef abs
#endif
#define abs(x)      ((x)<0 ? -(x) : (x))
#ifdef min
#undef  min
#endif
#define min(a,b)    ((a)<(b) ? (a) : (b))
#ifdef max
#undef  max
#endif
#define max(a,b)    ((a)>(b) ? (a) : (b))

#ifndef M_E
#define M_E     2.7182818284590452354
#endif

#define Tbit(x)  (tok & (1L << ((int) (x))))  /* Test token bit set */
#define Tb(x)    (1L << ((int) (x)))  /* Obtain bit to test */

/* AutoCAD standard color palette */

#define         BLACK           0
#define         RED             1
#define         YELLOW          2
#define         GREEN           3
#define         CYAN            4
#define         BLUE            5
#define         MAGENTA         6
#define         WHITE           7

#define         SAT             1.0

struct r_g_b {                        /* RGB colour description */
        ads_real red, green, blue;
};

/*  Colour naming system vocabulary definition.  The vocabulary
    is defined in this somewhat unusal fashion to facilitate
    translation to languages other than English. */

typedef enum {
        /* Chromatic colours */
        Red, Orange, Brown, Yellow, Green, Blue, Purple,

        /* "ish" forms of chromatic colours */
        Reddish, Orangish, Brownish, Yellowish, Greenish, Bluish, Purplish,

        /* Achromatic names */
        Gray, Black, White,

        /* Lightness specifications */
        Very, Dark, Medium, Light,

        /* Saturation specifications */
        Grayish, Moderate, Strong, Vivid,

        /* Punctuation */
        Hyphen, Period, Huh
       } colourvocab;

static struct {
        char *cname;
        colourvocab ccode;
} cvocab[] = {
        {/*MSG3*/"red", Red},
        {/*MSG4*/"orange", Orange},
        {/*MSG5*/"brown", Brown},
        {/*MSG6*/"yellow", Yellow},
        {/*MSG7*/"green", Green},
        {/*MSG8*/"blue", Blue},
        {/*MSG9*/"purple", Purple},

        {/*MSG10*/"reddish", Reddish},
        {/*MSG11*/"orangish", Orangish},
        {/*MSG12*/"brownish", Brownish},
        {/*MSG13*/"yellowish", Yellowish},
        {/*MSG14*/"greenish", Greenish},
        {/*MSG15*/"bluish", Bluish},
        {/*MSG16*/"purplish", Purplish},

        {/*MSG17*/"gray", Gray},
        {/*MSG18*/"grey", Gray},
        {/*MSG19*/"black", Black},
        {/*MSG20*/"white", White},

        {/*MSG21*/"very", Very},
        {/*MSG22*/"dark", Dark},
        {/*MSG23*/"medium", Medium},
        {/*MSG24*/"light", Light},

        {/*MSG25*/"grayish", Grayish},
        {/*MSG26*/"greyish", Grayish},
        {/*MSG27*/"moderate", Moderate},
        {/*MSG28*/"strong", Strong},
        {/*MSG29*/"vivid", Vivid}
       };

/* Table mapping generic hues to HSV hue indices. */

static struct {
        long cbit;
        int chue;
} colhue[] = {
        {Tb(Red),      0},            /* red */
        {Tb(Orange),  30},            /* orange */
        {Tb(Brown),  -30},            /* brown */
        {Tb(Yellow),  60},            /* yellow */
        {Tb(Green),  120},            /* green */
        {Tb(Blue),   240},            /* blue */
        {Tb(Purple), 300},            /* purple */
        {0L,         360}             /* red (other incarnation) */
       };

/* Table mapping secondary hues to HSV hue indices. */

static struct {
        long cbit;
        int chue;
} ishhue[] = {
        {Tb(Reddish),      0},        /* reddish */
        {Tb(Orangish),    30},        /* orangish */
        {Tb(Brownish),   -30},        /* brownish */
        {Tb(Yellowish),   60},        /* yellowish */
        {Tb(Greenish),   120},        /* greenish */
        {Tb(Bluish),     240},        /* bluish */
        {Tb(Purplish),   300},        /* purplish */
        {0L,             360}         /* reddish (other incarnation) */
       };

#define MAXTK    10                   /* Maximum tokens in specification */
#define MAXTKS   20                   /* Longest token in characters */

#define BROWNLIGHT  3                 /* Brown lightness:  Medium */
#define BROWNSAT    3                 /* Brown saturation: Strong */

/* Modal variables  */

static int defcnslit = 10000;         /* Default lightness if none specified */
static int gamut = 256;               /* Colour gamut available */

/*  Local variables  */

static char *cnserr = NULL;           /* Error message string */
static char cnserb[80];               /* Error message edit buffer */
static char tokenb[MAXTKS];           /* Token buffer */

/*  Forward functions  */

void   main _((int, char **));
void   funcload _((void));
void   hsv_rgb _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   rgb_hsv _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   rgb_hls _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
ads_real hlsval _((ads_real, ads_real, ads_real));
void   hls_rgb _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   rgb_yiq _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   yiq_rgb _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   rgb_cmy _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
void   ctemp_rgb _((ads_real, ads_real *,ads_real *,ads_real *));
#ifdef NEEDED
void   cmy_rgb _((ads_real,ads_real,ads_real,ads_real *,ads_real *,ads_real *));
#endif
colourvocab token _((char **));
Boolean cns_rgb _((char *, ads_real *, ads_real *, ads_real *));
char    *cixname _((colourvocab));
void    rgb_cns _((ads_real, ads_real, ads_real, char *));
void    acadrgb _((int, struct r_g_b *));
int     rgbacad _((ads_real, ads_real, ads_real));
void    retrgb _((Boolean, ads_real, ads_real, ads_real));
Boolean triple _((ads_real *, Boolean));
void    cmy _((Boolean));
void    cns _((Boolean));
void    cnser _((void));
void    ctemp _((Boolean));
void    hls _((Boolean));
void    hsv _((Boolean));
void    rgb _((Boolean));
void    yiq _((Boolean));
void    cmyac _((void));
void    ctempac _((void));
void    yiqac _((void));
void    hsvac _((void));
void    rgbac _((void));
void    hlsac _((void));
void    cnsac _((void));
void    cmyrgb _((void));
void    ctemprgb _((void));
void    yiqrgb _((void));
void    hsvrgb _((void));
void    hlsrgb _((void));
void    cnsrgb _((void));
Boolean acadcol _((struct r_g_b *));
void    torgb _((void));
void    tocmy _((void));
void    toyiq _((void));
void    tohsv _((void));
void    tohls _((void));
void    tocns _((void));
void    colset _((void));


/*  Command definition and dispatch table.  */

struct {
        char *cmdname;
        void (*cmdfunc)();
} cmdtab[] = {
/*        Name         Function  */

/* External colour system to AutoCAD colour functions */

{/*MSG0*/"CMY",       cmyac},
{/*MSG0*/"CNS",       cnsac},
{/*MSG0*/"CTEMP",     ctempac},
{/*MSG0*/"HLS",       hlsac},
{/*MSG0*/"HSV",       hsvac},
{/*MSG0*/"RGB",       rgbac},
{/*MSG0*/"YIQ",       yiqac},

/* External colour system to RGB functions */

{/*MSG0*/"CMY-RGB",   cmyrgb},
{/*MSG0*/"CNS-RGB",   cnsrgb},
{/*MSG0*/"CTEMP-RGB", ctemprgb},
{/*MSG0*/"HLS-RGB",   hlsrgb},
{/*MSG0*/"HSV-RGB",   hsvrgb},
{/*MSG0*/"YIQ-RGB",   yiqrgb},

/* AutoCAD colour index to external colour system functions */

{/*MSG0*/"TO-RGB",    torgb},
{/*MSG0*/"TO-CMY",    tocmy},
{/*MSG0*/"TO-YIQ",    toyiq},
{/*MSG0*/"TO-HSV",    tohsv},
{/*MSG0*/"TO-HLS",    tohls},
{/*MSG0*/"TO-CNS",    tocns},

/* Control and utility functions */

{/*MSG0*/"CNSERR",    cnser},
{/*MSG0*/"COLSET",    colset}
};

/*  MAIN  --  Main ADS dispatch loop.  */

void main(argc, argv)
  int argc;
  char *argv[];
{
    int stat, cindex, scode = RSRSLT;
    char errmsg[80];

    ads_init(argc, argv);             /* Initialise the application */

    /* Main dispatch loop. */

    while (True) {

        if ((stat = ads_link(scode)) < 0) {
            V sprintf(errmsg,
                      /*MSG30*/"COLEXT: bad status from ads_link() = %d\n",
                      stat);
#ifdef Macintosh
            macalert(errmsg);
#else
            puts(errmsg);
            fflush(stdout);
#endif /* Macintosh */
            exit(1);
        }

        scode = RSRSLT;               /* Default return code */

        switch (stat) {

        case RQXLOAD:                 /* Load functions.  Called at the start
                                                of the drawing editor.  */
            funcload();
            break;

        case RQSUBR:                  /* Evaluate external lisp function */
            cindex = ads_getfuncode();

            /* Execute the command from the command table with
               the index associated with this function. */

            if (cindex > 0) {
                cindex--;
                assert(cindex < ELEMENTS(cmdtab));
                (*cmdtab[cindex].cmdfunc)();
            }
            break;

        default:
            break;
        }
    }
}

/* FUNCLOAD  --  Load external functions into AutoLisp.  */

static void funcload()
{
    int i;

    for (i = 0; i < ELEMENTS(cmdtab); i++) {
        ads_defun(cmdtab[i].cmdname, i + 1);
    }
}

/*      ***************************************************
        **                                               **
        **       Colour Interconversion Functions        **
        **                                               **
        ***************************************************
*/

/*  HSV_RGB  --  Convert HSV colour specification to RGB  intensities.
                 Hue is specified as a  real  value  from  0  to  360,
                 Saturation  and  Intensity as reals from 0 to 1.  The
                 RGB components are returned as reals from 0 to 1.      */

static void hsv_rgb(h, s, v, r, g, b)
  ads_real h, s, v;
  ads_real *r, *g, *b;
{
    int i;
    ads_real f, p, q, t;

    if (s == 0) {
        *r = *g = *b = v;
    } else {
        if (h == 360.0)
            h = 0;
        h /= 60.0;

        i = h;
        f = h - i;
        p = v * (1.0 - s);
        q = v * (1.0 - (s * f));
        t = v * (1.0 - (s * (1.0 - f)));
        assert(i >= 0 && i <= 5);
        switch (i) {

        case 0:
            *r = v;
            *g = t;
            *b = p;
            break;

        case 1:
            *r = q;
            *g = v;
            *b = p;
            break;

        case 2:
            *r = p;
            *g = v;
            *b = t;
            break;

        case 3:
            *r = p;
            *g = q;
            *b = v;
            break;

        case 4:
            *r = t;
            *g = p;
            *b = v;
            break;

        case 5:
            *r = v;
            *g = p;
            *b = q;
            break;
        }
    }
}

/*  RGB_HSV  --  Map R, G, B intensities in the range from 0 to 1 into
                 Hue, Saturation,  and  Value:  Hue  from  0  to  360,
                 Saturation  from  0  to  1,  and  Value  from 0 to 1.
                 Special case: if Saturation is 0 (it's a  grey  scale
                 tone), Hue is undefined and is returned as -1.

                 This follows Foley & van Dam, section 17.4.4.  */

static void rgb_hsv(r, g, b, h, s, v)
  ads_real r, g, b;
  ads_real *h, *s, *v;
{
    ads_real imax = max(r, max(g, b)),
             imin = min(r, min(g, b)),
             rc, gc, bc;

    *v = imax;
    if (imax != 0)
        *s = (imax - imin) / imax;
    else
        *s = 0;

    if (*s == 0) {
        *h = -1;
    } else {
        rc = (imax - r) / (imax - imin);
        gc = (imax - g) / (imax - imin);
        bc = (imax - b) / (imax - imin);
        if (r == imax)
            *h = bc - gc;
        else if (g == imax)
            *h = 2.0 + rc - bc;
        else
            *h = 4.0 + gc - rc;
        *h *= 60.0;
        if (*h < 0.0)
            *h += 360.0;
    }
}

/*  RGB_HLS  --  Map R, G, B intensities in the range from 0 to 1 into
                 Hue, Lightness, and Saturation: Hue from  0  to  360,
                 Lightness  from  0  to 1, and Saturation from 0 to 1.
                 Special case: if Saturation is 0 (it's a  grey  scale
                 tone), Hue is undefined and is returned as -1.

                 This follows Foley & van Dam, section 17.4.5.  */

static void rgb_hls(r, g, b, h, l, s)
  ads_real r, g, b;
  ads_real *h, *l, *s;
{
    ads_real imax = max(r, max(g, b)),
             imin = min(r, min(g, b)),
             rc, gc, bc;

    *l = (imax + imin) / 2;

    if (imax == imin) {
        *s = 0;
        *h = -1;
    } else {
        if (*l <= 0.5)
            *s = (imax - imin) / (imax + imin);
        else
            *s = (imax - imin) /
                 (2.0 - imax - imin);

        rc = (imax - r) / (imax - imin);
        gc = (imax - g) / (imax - imin);
        bc = (imax - b) / (imax - imin);
        if (r == imax)
            *h = bc - gc;
        else if (g == imax)
            *h = 2.0 + rc - bc;
        else
            *h = 4.0 + gc - rc;
        *h *= 60.0;
        if (*h < 0)
            *h += 360.0;
    }
}

/*  HLS_RGB  --  Convert HLS colour specification to RGB  intensities.
                 Hue  is  specified  as  a  real  value from 0 to 360;
                 Lightness and Saturation as reals from 0 to  1.   The
                 RGB components are returned as reals from 0 to 1.      */

static ads_real hlsval(n1, n2, hue)
  ads_real n1, n2, hue;
{
    if (hue > 360.0)
        hue -= 360.0;
    else if (hue < 0.0)
        hue += 360.0;
    if (hue < 60.0) {
        return n1 + ((n2 - n1) * hue) / 60.0;
    } else if (hue < 180.0) {
        return n2;
    } else if (hue < 240.0) {
        return n1 + ((n2 - n1) * (240.0 - hue)) / 60.0;
    } else {
        return n1;
    }
}

static void hls_rgb(h, l, s, r, g, b)
  ads_real h, l, s;
  ads_real *r, *g, *b;
{
    ads_real m1, m2;

    if (l <= 0.5)
        m2 = l * (1.0 + s);
    else
        m2 = l + s - (l * s);
    m1 = 2 * l - m2;

    if (s == 0) {
        *r = *g = *b = l;
    } else {
        *r = hlsval(m1, m2, h + 120.0);
        *g = hlsval(m1, m2, h);
        *b = hlsval(m1, m2, h - 120.0);
    }
}

/*  RGB_YIQ  --  Convert RGB colour specification, R, G, B ranging
                 from 0 to 1, to Y, I, Q colour specification.

                 |Y|   |0.30  0.59  0.11|   |R|
                 |I| = |0.60 -0.28 -0.32| . |G|
                 |Q|   |0.21 -0.52  0.31|   |B|
*/

static void rgb_yiq(r, g, b, y, i, q)
  ads_real r, g, b;
  ads_real *y, *i, *q;
{
    ads_real ay = (r * 0.30 + g *  0.59 + b *  0.11),
             ai = (r * 0.60 + g * -0.28 + b * -0.32),
             aq = (r * 0.21 + g * -0.52 + b *  0.31);

    *y = ay;
    if (ay == 1.0) {                  /* Prevent round-off on grey scale */
        ai = aq = 0.0;
    }
    *i = ai;
    *q = aq;
}

/*  YIQ_RGB  --  Convert YIQ colour specification, Y, I,  Q  given  as
                 reals,  Y  from  0  to  1, I from -0.6 to 0.6, Q from
                 -0.52 to 0.52, to R, G, B intensities  in  the  range
                 from  0 to 1.  The matrix below is the inverse of the
                 RGB_YIQ matrix above.

                 |R|   |1.00  0.948  0.624|   |Y|
                 |G| = |1.00 -0.276 -0.640| . |I|
                 |B|   |1.00 -1.105  1.730|   |Q|
*/

static void yiq_rgb(y, i, q, r, g, b)
  ads_real y, i, q;
  ads_real *r, *g, *b;
{
    ads_real ar = (y + i *   0.948 + q *  0.624),
             ag = (y + i *  -0.276 + q * -0.640),
             ab = (y + i *  -1.105 + q *  1.730);

    *r = max(0, min(1.0, ar));
    *g = max(0, min(1.0, ag));
    *b = max(0, min(1.0, ab));
}

/*  RGB_CMY  --  Convert RGB colour specification,  R,  G,  B  ranging
                 from  0  to  1, to C, M, Y colour specification, also
                 ranging from 0 to 1.

                 |C|   |1|   |R|
                 |M| = |1| - |G|
                 |Y|   |1|   |B|
*/

static void rgb_cmy(r, g, b, c, m, y)
  ads_real r, g, b;
  ads_real *c, *m, *y;
{
    *c = 1.0 - r;
    *m = 1.0 - g;
    *y = 1.0 - b;
}

#ifdef NEEDED

/*  CMY_RGB  --  Convert CMY colour specification,  C,  M,  Y  ranging
                 from  0  to  1, to R, G, B colour specification, also
                 ranging from 0 to 1.

                 |R|   |1|   |C|
                 |G| = |1| - |M|
                 |B|   |1|   |Y|
*/

static void cmy_rgb(c, m, y, r, g, b)
  ads_real c, m, y;
  ads_real *r, *g, *b;
{
    *r = 1.0 - c;
    *g = 1.0 - m;
    *b = 1.0 - y;
}
#endif

/*  CTEMP_RGB  --  Calculate the relative R, G, and B components for a
                   black body emitting light at a  given  temperature.
                   The  Planck  radiation  equation is solved directly
                   for the R, G, and B wavelengths defined for the CIE
                   1931  Standard  Colorimetric  Observer.  The colour
                   temperature is specified in degrees Kelvin. */

static void ctemp_rgb(temp, r, g, b)
  double temp;
  double *r, *g, *b;
{
    double c1 = 3.74183e10,
           c2 = 14388.0,
           er, eg, eb, es;

/* Lambda is the wavelength in microns: 5500 angstroms is 0.55 microns. */

#define Planck(lambda)  ((c1 * pow((double) lambda, -5.0)) /  \
                         (pow(M_E, c2 / (lambda * temp)) - 1))

    er = Planck(0.7000);
    eg = Planck(0.5461);
    eb = Planck(0.4358);
#undef Planck

    es = 1.0 / max(er, max(eg, eb));

    *r = er * es;
    *g = eg * es;
    *b = eb * es;
}

/*  TOKEN  --  Scan next token from the CNS string and update the
               scan pointer.  */

static colourvocab token(icp)
  char **icp;
{
    char ch;
    char *cp = *icp, *tch;
    int i, t = 0;

    /* Ignore leading space */

    while (True) {
        ch = *cp++;
        if (!isspace(ch))
            break;
    }

    if (ch == EOS)
        return Period;

    if (ch == '-') {
        *icp = cp;
        return Hyphen;
    }

    tch = cp - 1;                     /* Start of token pointer */
    if (!isalpha(ch)) {
        *cp = EOS;
        *icp = tch;
        return Huh;
    }

    while (isalpha(ch)) {
        if (isupper(ch))
            ch = tolower(ch);
        if (t < ((sizeof tokenb) - 2))
            tokenb[t++] = ch;
        ch = *cp++;
    }
    tokenb[t] = EOS;
    *icp = cp - 1;

    for (i = 0; i < ELEMENTS(cvocab); i++) {
        if (strcmp(tokenb, cvocab[i].cname) == 0) {
            return cvocab[i].ccode;
        }
    }
    **icp = EOS;
    *icp = tch;
    return Huh;
}

/*  CNS_RGB  --  Convert a CNS string to RGB intensities scaled from 0
                 to 1.  If an invalid specification is made,  0
                 is returned and an error message is pointed to by the
                 global character pointer  cnserr.   Otherwise,  1  is
                 returned.  */

static Boolean cns_rgb(cns, r, g, b)
  char *cns;
  ads_real *r, *g, *b;
{
    int i, j, k = 0, lightness, saturation;
    long tok = 0L, hue;
    colourvocab t;
    static char conflite[] = /*MSG31*/"Conflicting lightness specification.";
    /* Grey scale table */
    static int greyscale[] = {50, 17, 33, 50, 67, 83};
    /* Saturation percentage table */
    static int satab[] = {10000, 2500, 5000, 7500, 10000};
    /* Chromatic lightness table */
    static int litetab[] = {5000, 1300, 2500, 5000, 7500, 10000};

    cnserr = NULL;                    /* Initially no error in CNS string */
    j = strlen(cns);
    if (j == 0) {
        cnserr = /*MSG32*/"Void specification.";
        return False;
    }

    /* Scan string and parse tokens */

    while (True) {
        t = token(&cns);
        if (t == Huh) {
            V sprintf(cnserb, /*MSG33*/"Unrecognised symbol: `%s'.", cns);
            cnserr = cnserb;
            return False;
        }
        if (Tbit(t)) {
            V sprintf(cnserb, /*MSG34*/"Duplicate symbol: `%s'.", tokenb);
            cnserr = cnserb;
            return False;
        }
        if (t == Period)
            break;
        tok |= 1L << ((int) t);
    }

    /* Try to obtain lightness from specification */

    if (tok & (Tb(Very) | Tb(Dark) | Tb(Medium) | Tb(Light))) {
        if (Tbit(Medium)) {
            if (Tbit(Very)) {
                cnserr = /*MSG35*/"Very used with Medium.";
                return False;
            }
            if (Tbit(Light) || Tbit(Dark)) {
                cnserr = conflite;
                return False;
            }
            lightness = 3;
        } else if (Tbit(Dark)) {
            lightness = Tbit(Very) ? 1 : 2;
            if (Tbit(Light)) {
                cnserr = conflite;
                return False;
            }
        } else if (Tbit(Light)) {
            lightness = Tbit(Very) ? 5 : 4;
        } else {
            cnserr = /*MSG36*/"Very used without Light or Dark.";
            return False;
        }
    } else {
        lightness = 0;
    }

    /* Test for achromatic colour specification. */

    i = !!(Tbit(Black)) + !!(Tbit(Gray)) + !!(Tbit(White));
    if (i > 0) {

        /* Test for conflicting specification of more than
           one achromatic colour. */

        if (i != 1) {
            cnserr = /*MSG37*/"Conflicting black/gray/white specification.";
            return False;
        }

        /* Test for specification of chromatic colour with
           achromatic colour. */

        if (tok & (Tb(Red) | Tb(Orange) | Tb(Brown) | Tb(Yellow) |
                   Tb(Green) | Tb(Blue) | Tb(Purple))) {
            cnserr = /*MSG38*/"Chromatic and achromatic shade mixed.";
            return False;
        }

        /* Test for specification of chromatic colour ish form with
           achromatic colour. */

        if (tok & (Tb(Reddish) | Tb(Orangish) |
                   Tb(Brownish) | Tb(Yellowish) |
                   Tb(Greenish) | Tb(Bluish) | Tb(Purplish) |
                   Tb(Hyphen))) {
            cnserr = /*MSG39*/"Chromatic modifier and achromatic shade mixed.";
            return False;
        }

        /* Test for saturation specification with achromatic shade. */

        if (tok & (Tb(Grayish) | Tb(Moderate) | Tb(Strong) | Tb(Vivid))) {
            cnserr = /*MSG40*/"Saturation specified with achromatic shade.";
            return False;
        }

        /* Test for lightness specified with White or Black. */

        if (Tbit(White) || Tbit(Black)) {
            if (tok & (Tb(Very) | Tb(Dark) | Tb(Medium) | Tb(Light))) {
                cnserr = /*MSG41*/"Lightness specified with black or white.";
                return False;
            }
            if (Tbit(White)) {
                *r = *g = *b = 1.0;   /* White */
            } else {
                *r = *g = *b = 0;     /* Black */
            }
            return True;
        }

        /* Calculate grey scale value from lightness specification. */

        *r = *g = *b = greyscale[lightness] / 100.0;
        return True;
    }

    /* It isn't a grey scale, so it must be a chromatic colour
       specification.  Before we tear into the hue, let's try and
       determine the saturation. */

    i = (!!Tbit(Grayish)) + (!!Tbit(Moderate)) +
        (!!Tbit(Strong)) + (!!Tbit(Vivid));
    if (i > 0) {
        if (i > 1) {
            cnserr = /*MSG42*/"Conflicting saturation specification.";
            return False;
        }
        saturation = Tbit(Grayish) ? 1 :
                     (Tbit(Moderate) ? 2 :
                      (Tbit(Strong) ? 3 : 4));
    } else {
        saturation = 0;
    }

    /* Count primary hue specifications. */

    i = (!!Tbit(Red)) + (!!Tbit(Orange)) + (!!Tbit(Brown)) +
        (!!Tbit(Yellow)) +
        (!!Tbit(Green)) + (!!Tbit(Blue)) + (!!Tbit(Purple));

    if (i == 0) {
        cnserr = /*MSG43*/"No hue specified.";
        return False;
    }
    if (i > 2) {
        cnserr = /*MSG44*/"More than two hues specified.";
        return False;
    }

    /* Count secondary hue specifications. */

    j = (!!Tbit(Reddish)) + (!!Tbit(Orangish)) + (!!Tbit(Brownish)) +
        (!!Tbit(Yellowish)) +
        (!!Tbit(Greenish)) + (!!Tbit(Bluish)) + (!!Tbit(Purplish));

    if (j > 1) {
        cnserr = /*MSG45*/"More than one secondary hue specified.";
        return False;
    }
    if (i == 2 && j > 0) {
        cnserr = /*MSG46*/"Secondary hue specified with two primary hues.";
        return False;
    }

    /* Obtain hue based on form of specification we've determined
       is being made.

       Case 1.  Pure hue specified by a single primary hue. */

    hue = -1;
    if (i == 1 && j == 0) {
        for (i = 0; i < ELEMENTS(colhue); i++) {
            if (tok & colhue[i].cbit) {
                hue = abs(colhue[i].chue) * 100L;
                /* If it's brown, impute saturation and lightness
                   if none was explicitly specified. */
                if (colhue[i].chue < 0) {
                    if (lightness == 0)
                        lightness = BROWNLIGHT;
                    if (saturation == 0)
                        saturation = BROWNSAT;
                }
                break;
            }
        }
    } else if (i == 2) {

        /* Case 2.  Halfway hue specified by composing two adjacent
                    primary hues. */

        j = k = -1;
        for (i = 0; i < ELEMENTS(colhue); i++) {
            if (tok & colhue[i].cbit) {
                if (j < 0)
                    j = i;
                else {
                    k = i;
                    break;
                }
            }
        }
        if ((colhue[j].chue == -colhue[k].chue) ||
            (((j + 1) != k) &&
             !(j == 0 && k == 2) && !(j == 1 && k == 3) &&
             (!(j == 0 && k == (ELEMENTS(colhue) - 2))))) {
            cnserr = /*MSG47*/"Two primary hues are not adjacent.";
            return False;
        }

        if (Tbit(Red) && Tbit(Purple))
            j = ELEMENTS(colhue) - 1;

        hue = (abs(colhue[j].chue) + abs(colhue[k].chue)) * 50L;
        /* If either is brown, impute saturation and lightness
           if none was explicitly specified. */
        if (colhue[j].chue < 0 || colhue[k].chue < 0) {
            if (lightness == 0)
                lightness = BROWNLIGHT;
            if (saturation == 0)
                saturation = BROWNSAT;
        }
    } else {

        /* Case 3.  Quarterway hue specified by one primary hue
                    and one secondary hue. */

        for (i = 0; i < ELEMENTS(colhue); i++) {
            if (tok & colhue[i].cbit) {
                j = i;
                break;
            }
        }
        for (i = 0; i < ELEMENTS(ishhue); i++) {
            if (tok & ishhue[i].cbit) {
                k = i;
                break;
            }
        }
        if ((colhue[j].chue == -colhue[k].chue) || (
               ((j + 1) != k) && ((j - 1) != k) &&
               !(j == 0 && k == 2) && !(j == 1 && k == 3) &&
               !(k == 0 && j == 2) && !(k == 1 && j == 3) &&
               (!(j == 0 && k == (ELEMENTS(ishhue) - 2))) &&
               (!(k == 0 && j == (ELEMENTS(ishhue) - 2)))
              )
           ) {
            cnserr = /*MSG48*/"Primary and secondary hues are not adjacent.";
            return False;
        }

        if (Tbit(Red) && Tbit(Purplish))
            j = ELEMENTS(colhue) - 1;
        else if (Tbit(Purple) && Tbit(Reddish))
            k = ELEMENTS(ishhue) - 1;

        hue = (abs(colhue[j].chue) * 3 + abs(ishhue[k].chue)) * 25L;

        /* If either is brown, impute saturation and lightness
           if none was explicitly specified. */

        if (colhue[j].chue < 0 || ishhue[k].chue < 0) {
            if (lightness == 0)
                lightness = BROWNLIGHT;
            if (saturation == 0)
                saturation = BROWNSAT;
        }
    }

    if (hue < 0) {
        cnserr = /*MSG49*/"Internal error--cannot determine hue.";
        return False;
    }

    if (lightness == 0)
        k = defcnslit;
    else
        k = litetab[lightness];

    hsv_rgb(hue / 100.0, satab[saturation] / 10000.0, k / 10000.0,
            r, g, b);
    return True;
}

/*  CIXNAME  --  Find name of colour vocabulary word from its index.  */

static char *cixname(cx)
  colourvocab cx;
{
    int i;

    for (i = 0; i < ELEMENTS(cvocab); i++)
        if (cvocab[i].ccode == cx)
            break;
    return cvocab[i].cname;
}

/*  RGB_CNS  --  Find best CNS description for RGB colour expressed
                 in R, G, and B, from 0 to 1.  */

static void rgb_cns(r, g, b, cnstr)
  ads_real r, g, b;
  char *cnstr;
{
    int i, j = 0, k, d, s, v;
    long lh, ld, hd;
    ads_real rh, rs, rv;

#define C(x)  ((char) (x))
#define CV(x) ((colourvocab) (x))

    /* Grey scale name table */

    static struct {
       int intens;
       char gname[3];
    } gtab[] = {
       {0,     {C(Black),                  0}},
       {1700,  {C(Very),  C(Dark), C(Gray)  }},
       {3300,  {C(Dark),  C(Gray),         0}},
       {5000,  {C(Gray),                   0}},
       {6700,  {C(Light), C(Gray),         0}},
       {8300,  {C(Very),  C(Light), C(Gray) }},
       {10000, {C(White),                  0}}
      };

    /* Hue name table */

    static struct {
       long huecode;
       char purename,
            ishname;
    } huetab[] = {
       {0L,     C(Red),    C(Reddish)},
       {3000L,  C(Orange), C(Orangish)},
       {6000L,  C(Yellow), C(Yellowish)},
       {12000L, C(Green),  C(Greenish)},
       {24000L, C(Blue),   C(Bluish)},
       {30000L, C(Purple), C(Purplish)},
       {36000L, C(Red),    C(Reddish)}
      };

    /* Chromatic lightness table */

    static struct {
       int intens;
       char lname[2];
    } ltab[] = {
       {1250,  {C(Very), C(Dark)  }},
       {2500,  {C(Dark),         0}},
       {5000,  {C(Medium),       0}},
       {7500,  {C(Light),        0}},
       {10000, {C(Very), C(Light) }}
      };

    /* Chromatic saturation table */

    static struct {
       int satper;
       char sname;
    } stab[] = {
       {2500,  C(Grayish)  },
       {5000,  C(Moderate) },
       {7500,  C(Strong)   },
       {10000, C(Vivid)    }
      };

    cnstr[0] = EOS;

    rgb_hsv(r, g, b, &rh, &rs, &rv);
    lh = rh * 100L;
    s = rs * 10000;
    v = rv * 10000;

    if (s == 0) {

        /* Achromatic */

        d = 20000;
        for (i = 0; i < ELEMENTS(gtab); i++) {
            if (abs(gtab[i].intens - v) < d) {
                d = abs(gtab[i].intens - v);
                j = i;
            }
        }
        for (i = 0; i < 3; i++) {
            if (gtab[j].gname[i] == 0)
                break;
            if (strlen(cnstr))
                V strcat(cnstr, " ");
            V strcat(cnstr, cixname(CV(gtab[j].gname[i])));
        }
    } else {

        /* Chromatic.  */

        /* Locate intensity.   If  the  closest  intensity  is  the
           default  intensity  in  DEFCNSLIT,  we  don't  edit  any
           intensity.  You can disable this by setting DEFCNSLIT to
           -1.  */

        d = 20000;
        for (i = 0; i < ELEMENTS(ltab); i++) {
            if (abs(ltab[i].intens - v) < d) {
                d = abs(ltab[i].intens - v);
                j = i;
            }
        }
        if (ltab[j].intens != defcnslit) {
            for (i = 0; i < 2; i++) {
                if (ltab[j].lname[i] == 0)
                    break;
                if (strlen(cnstr))
                    V strcat(cnstr, " ");
                V strcat(cnstr, cixname(CV(ltab[j].lname[i])));
            }
        }

        /* Locate saturation.  If the saturation is vivid, nothing
           is edited. */

        d = 20000;
        for (i = 0; i < ELEMENTS(stab); i++) {
            if (abs(stab[i].satper - s) <= d) {
                d = abs(stab[i].satper - s);
                j = i;
            }
        }
        if (stab[j].satper != 10000) {
            if (strlen(cnstr))
                V strcat(cnstr, " ");
            V strcat(cnstr, cixname(CV(stab[j].sname)));
        }

        if (strlen(cnstr))
            V strcat(cnstr, " ");

        /* Find closest hue name. */

        ld = 100000L;
        if (lh == 36000L)
            lh = 0;
        for (i = 0; i < ELEMENTS(huetab); i++) {
            if (abs(huetab[i].huecode - lh) < ld) {
                ld = abs(huetab[i].huecode - lh);
                j = i;
            }
        }

        /* Now we'll find the next hue in the direction of the
           actual hue from the specified hue. */

        if (lh > huetab[j].huecode) {
            if (j == (ELEMENTS(huetab) - 1))
                k = 1;
            else
                k = j + 1;
        } else {
            if (j == 0)
                k = ELEMENTS(huetab) - 2;
            else
                k = j - 1;
        }

        /* Next, compute the distance between the hue and the next
           neighbour in the hue's direction.  */

        hd = abs(huetab[j].huecode - huetab[k].huecode);

        /* The  form of the hue then  depends upon the relationship
           between the actual distance, D, and the total  distance,
           HD,  from the closest pure hue, J, and the next pure hue
           in the direction of the hue supplied,  K.   We  generate
           the following based upon the relationship:

                 D / HD          Name
              ------------     --------
              0     - 0.125       J
              0.125 - 0.375     Kish J
              0.375 - 0.5        J-K
        */

        hd = (ld * 10000L) / hd;
        if (hd < 1250L) {
            V strcat(cnstr, cixname(CV(huetab[j].purename)));
        } else if (hd < 3750L) {
            V strcat(cnstr, cixname(CV(huetab[k].ishname)));
            V strcat(cnstr, " ");
            V strcat(cnstr, cixname(CV(huetab[j].purename)));
        } else {
            V strcat(cnstr, cixname(CV(huetab[j].purename)));
            V strcat(cnstr, "-");
            V strcat(cnstr, cixname(CV(huetab[k].purename)));
        }
    }
}

/*  ACADRGB  --  Takes  an  AutoCAD  colour  number in hsv and returns
                 red, green, and blue intensities in rgp in the  range
                 0.0 to 1.0 */

static void acadrgb(hsv, rgp)
  int  hsv;
  struct r_g_b *rgp;
{
    static ads_real brightfac[5] = {  /* Brightness levels */
       1.0, 0.65, 0.5, 0.3, 0.15
      }, halfsat = .5;                /* Halfway saturation */
    int ih, vs;
    ads_real h, s, f, value;

    assert(hsv > 0 || hsv < 256);

    switch (hsv) {
    case BLACK:
        rgp->red   = 0.0;
        rgp->blue  = 0.0;
        rgp->green = 0.0;
        value = 0.0;
        break;

    case RED:
        rgp->red   = SAT;
        rgp->green = 0.0;
        rgp->blue  = 0.0;
        value = 1.0;
        break;

    case YELLOW:
        rgp->red   = SAT;
        rgp->green = SAT;
        rgp->blue  = 0.0;
        value = 1.0;
        break;

    case GREEN:
        rgp->red   = 0.0;
        rgp->green = SAT;
        rgp->blue  = 0.0;
        value = 1.0;
        break;

    case CYAN:
        rgp->red   = 0.0;
        rgp->green = SAT;
        rgp->blue  = SAT;
        value = 1.0;
        break;

    case BLUE:
        rgp->red   = 0.0;
        rgp->green = 0.0;
        rgp->blue  = SAT;
        value = 1.0;
        break;

    case MAGENTA:
        rgp->red   = SAT;
        rgp->green = 0.0;
        rgp->blue  = SAT;
        value = 1.0;
        break;

    case WHITE:
    case 8:
    case 9:
        rgp->red   = SAT;
        rgp->green = SAT;
        rgp->blue  = SAT;
        value = 1.0;
        break;

    default:

        /*  The chromatic colors.  The  hue  resulting  from  an
            AutoCAD color 10-249 will be determined by its first
            two digits, and the saturation and  value  from  the
            last digit, as follows:

            Hues:

             10 -- Red
             50 -- Yellow
             90 -- Green
            130 -- Cyan
            170 -- Blue
            210 -- Magenta

            Between  each  of these are three intermediate hues,
            e.g., between red and yellow, we have:

             20 -- reddish orange
             30 -- orange
             40 -- yellowish orange

            To each hue number, 0, 2, 4, 6, or 8 can be added to
            give a different "value", or brightness, with 0  the
            brightest  and  8  the  weakest.   Finally, 1 can be
            added to  produce  a  "half-saturated",  or  pastel,
            color.  For example, color 18 is the dimmest red and
            10 the brightest red.  19 is the dimmest pink and 11
            the brightest pink.
        */

        if (hsv > 9 && hsv < 250) {

            /* Apply the algorithm from Foley & van Dam to turn
               HSV into RGB values */

            ih = (hsv - 10) / 10;     /* Integer hue value. */
            if (ih >= 24)             /* Range is 0-23. */
                ih -= 24;
            vs = hsv % 10;            /* Encoded value and saturation */
            h = ih / 4.;              /* Map into range [0.0,6.0) */
            ih = h;                   /* The integer part. */
            f = h - ih;               /* Fractional part. */
            value = brightfac[vs >> 1]; /* Value in [0,1] */
            s = vs & 1 ? halfsat : 1.0; /* Saturation */

            switch (ih) {
            case 0:
                rgp->red   = 1.0;
                rgp->green = (ads_real) (1.0 - s * (1.0 - f));
                rgp->blue  = (ads_real) (1.0 - s);
                break;

            case 1:
                rgp->red   = (ads_real) (1.0 - s * f);
                rgp->green = 1.0;
                rgp->blue  = (ads_real) (1 - s);
                break;

            case 2:
                rgp->red   = (ads_real) (1.0 - s);
                rgp->green = 1.0;
                rgp->blue  = (ads_real) (1.0 - s *(1.0 - f));
                break;

            case 3:
                rgp->red   = (ads_real) (1.0 - s);
                rgp->green = (ads_real) (1.0 - s * f);
                rgp->blue  = 1.0;
                break;

            case 4:
                rgp->red   = (ads_real) (1.0 - s * (1.0 - f));
                rgp->green = (ads_real) (1.0 - s);
                rgp->blue  = 1.0;
                break;

            case 5:
                rgp->red   = 1.0;
                rgp->green = (ads_real) (1.0 - s);
                rgp->blue  = (ads_real) (1.0 - s * f);
                break;
            }
        } else {
            /* Define some extra colours from dark grey to white
               in the 250 to 255 slots */
            value = 0.33 + (hsv - 250) * 0.134;
            rgp->red   = 1.0;
            rgp->green = 1.0;
            rgp->blue  = 1.0;
        }
        break;                        /* Default */
    }
    rgp->red   *= value;              /* Apply lightness scale factor */
    rgp->green *= value;              /* to components resulting from */
    rgp->blue  *= value;              /* hue and saturation. */
}

/*  RGBACAD  --  Find the AutoCAD colour closest to in RGB space to a
                 specified RGB triple.  */

static int rgbacad(r, g, b)
  ads_real r, g, b;
{
    int i, low, ccol;
    ads_real closest = 1000.0;
    struct r_g_b rc;

    assert(r >= 0.0 && r <= 1.0);
    assert(g >= 0.0 && g <= 1.0);
    assert(b >= 0.0 && b <= 1.0);

    /* If we're mapping to the 8 colour gamut, turn all grey scale
       colours into white and map the rest based on hue alone. */

    if (gamut == 8) {
        ads_real h, s, v;

        rgb_hsv(r, g, b, &h, &s, &v);
        return s == 0.0 ? WHITE :
               (RED + ((((int) (h + 30.0)) % 360) / 60));
    }

    /* Note  that we start with  colour 1 since 0 (black) is not a
       valid user-specified colour.  If this is a grey scale tone,
       map only to AutoCAD's grey scale indices.  */

    ccol = low = (r == g && r == b) ? 250 : 1;

    for (i = low; i < 256; i++) {
        ads_real cdist;

        acadrgb(i, &rc);
        rc.red -= r;
        rc.green -= g;
        rc.blue -= b;
        cdist = rc.red * rc.red + rc.green * rc.green +
                rc.blue * rc.blue;
        if (cdist < closest) {
            ccol = i;
            if ((closest = cdist) == 0.0)
                break;
        }
    }
    if (ccol == 255)                  /* If synonym for white... */
        ccol = 7;                     /* ...make simple white. */
    return ccol;
}

/*  RETRGB  --  Return an RGB triple as either an RGB point or
                the closest AutoCAD standard colour.  */

static void retrgb(acad, r, g, b)
  Boolean acad;
  ads_real r, g, b;
{
    if (acad) {
        ads_retint(rgbacad(r, g, b));
    } else {
        ads_point p;

        Spoint(p, r, g, b);
        ads_retpoint(p);
    }
}

/*  TRIPLE  --  Scan  a  triple  of  real  arguments  into an array of
                reals.  Integers are accepted and converted to  reals.
                True  is  returned  if  valid  arguments are obtained;
                False otherwise.  */

static Boolean triple(cdesc, rangecheck)
  ads_real cdesc[3];
  Boolean rangecheck;
{
    int nargs;
    struct resbuf *rb1 = ads_getargs();

    ads_retnil();
    for (nargs = 0; nargs < 3; nargs++) {
        if (rb1 == NULL)
            break;
        if (rb1->restype == RTSHORT) {
            cdesc[nargs] = rb1->resval.rint;
        } else if (rb1->restype == RTREAL) {
            cdesc[nargs] = rb1->resval.rreal;
        } else if (nargs == 0 && rb1->restype == RT3DPOINT) {
            Cpoint(cdesc, rb1->resval.rpoint);
            nargs = 2;
        } else {
            ads_fail(/*MSG50*/"incorrect argument type");
            return False;
        }
        rb1 = rb1->rbnext;
    }

    /* Make sure there were enough arguments. */

    if (nargs < 3) {
        ads_fail(/*MSG51*/"too few arguments");
        return False;
    }

    /* Make sure there are no more arguments. */

    if (rb1 != NULL) {
        ads_fail(/*MSG52*/"too many arguments");
        return False;
    }

    /* Range check arguments if requested. */

    if (rangecheck) {
        for (nargs = 0; nargs < 3; nargs++) {
            if (rangecheck && (cdesc[nargs] < 0.0 || cdesc[nargs] > 1.0)) {
                ads_fail(/*MSG53*/"argument out of range");
                return False;
            }
        }
    }

    return True;
}

/*  CMY  --  Specify colour as CMY triple.  */

static void cmy(acad)
  Boolean acad;
{
    ads_real cdesc[3];

    if (triple(cdesc, True)) {
        retrgb(acad, 1.0 - cdesc[0], 1.0 - cdesc[1],
               1.0 - cdesc[2]);
    }
}

/*  CTEMP  --  Specify colour as a colour temperature.  */

static void ctemp(acad)
  Boolean acad;
{
    struct resbuf *rb;

    ads_retnil();

    if ((rb = ads_getargs()) == NULL) {
        ads_fail(/*MSG63*/"too few arguments");
    } else {
        ads_real degrees, ir, ig, ib;

        if (rb->restype == RTSHORT) {
            degrees = rb->resval.rint;
        } else if (rb->restype == RTREAL) {
            degrees = rb->resval.rreal;
        } else {
            ads_fail(/*MSG64*/"incorrect argument type");
            return;
        }

        /* Make sure there are no more arguments. */

        if (rb->rbnext != NULL) {
            ads_fail(/*MSG65*/"too many arguments");
            return;
        }

        ctemp_rgb(degrees, &ir, &ig, &ib);
        retrgb(acad, ir, ig, ib);
    }
}

/*  CNS  --  Specify colour as a CNS string.  */

static void cns(acad)
  Boolean acad;
{
    struct resbuf *rb;

    ads_retnil();

    if ((rb = ads_getargs()) == NULL) {
        ads_fail(/*MSG54*/"too few arguments");
        return;
    } else {
        struct resbuf *rb1 = rb;
        ads_real ir, ig, ib;

        if (rb1->restype != RTSTR) {
            ads_fail(/*MSG55*/"incorrect argument type");
            return;
        }

        /* Make sure there are no more arguments. */

        if (rb1->rbnext != NULL) {
            ads_fail(/*MSG56*/"too many arguments");
            return;
        }

        if (cns_rgb(rb1->resval.rstring, &ir, &ig, &ib)) {
            retrgb(acad, ir, ig, ib);
        }
    }
}

/*  CNSER  --  Return string describing last CNS error, if any.  */

static void cnser()
{
    if (cnserr == NULL)
        ads_retnil();
    else
        ads_retstr(cnserr);
}

/*  HLS  --  Specify colour as HLS triple.  */

static void hls(acad)
  Boolean acad;
{
    ads_real cdesc[3];

    if (triple(cdesc, True)) {
        ads_real ir, ig, ib;

        hls_rgb(cdesc[0] * 360.0, cdesc[1], cdesc[2], &ir, &ig, &ib);
        retrgb(acad, ir, ig, ib);
    }
}

/*  HSV  --  Specify colour as HSV triple.  */

static void hsv(acad)
  Boolean acad;
{
    ads_real cdesc[3];

    if (triple(cdesc, True)) {
        ads_real ir, ig, ib;

        hsv_rgb(cdesc[0] * 360.0, cdesc[1], cdesc[2], &ir, &ig, &ib);
        retrgb(acad, ir, ig, ib);
    }
}

/*  RGB  --  Specify colour as RGB triple.  */

static void rgb(acad)
  Boolean acad;
{
    ads_real cdesc[3];

    if (triple(cdesc, True)) {
        retrgb(acad, cdesc[0], cdesc[1], cdesc[2]);
    }
}

/*  YIQ  --  Specify colour as YIQ triple.  */

static void yiq(acad)
  Boolean acad;
{
    ads_real cdesc[3];

    if (triple(cdesc, False)) {
        ads_real ir, ig, ib;

        if ((cdesc[0] < 0.0 || cdesc[0] > 1.0) &&
            (cdesc[1] < -0.6 || cdesc[0] > 0.6) &&
            (cdesc[2] < -0.52 || cdesc[2] > 0.52)) {
            ads_fail(/*MSG57*/"argument out of range");
        }

        yiq_rgb(cdesc[0], cdesc[1], cdesc[2], &ir, &ig, &ib);

        retrgb(acad, ir, ig, ib);
    }
}

/*  Colour system to AutoCAD colour functions. */

static void cmyac()   { cmy(True);   }
static void ctempac() { ctemp(True); }
static void yiqac()   { yiq(True);   }
static void hsvac()   { hsv(True);   }
static void rgbac()   { rgb(True);   }
static void hlsac()   { hls(True);   }
static void cnsac()   { cns(True);   }

/*  Colour system to RGB functions.  */

static void cmyrgb()   { cmy(False);   }
static void ctemprgb() { ctemp(False); }
static void yiqrgb()   { yiq(False);   }
static void hsvrgb()   { hsv(False);   }
static void hlsrgb()   { hls(False);   }
static void cnsrgb()   { cns(False);   }

/*  ACADCOL  --  Obtain AutoCAD colour.  We accept any of the following:

    1.  A single integer, representing an AutoCAD standard colour index.
    2.  A triple of reals and/or integers, representing RGB intensities.
    3.  A list of three reals and/or integers, representing RGB intensities.
*/

static Boolean acadcol(rp)
  struct r_g_b *rp;
{
    ads_real crgb[3];
    struct resbuf *rb = ads_getargs();

    ads_retnil();

    if (rb == NULL) {
        ads_fail(/*MSG58*/"too few arguments");
        return False;
    }

    if ((rb->restype == RTSHORT) && (rb->rbnext == NULL)) {
        int cindex = rb->resval.rint;

        if (cindex < 0 || cindex > 255) {
            ads_fail(/*MSG59*/"argument out of range");
            return False;
        }
        acadrgb(cindex, rp);
        return True;
    }

    if (triple(crgb, True)) {
        rp->red   = crgb[0];
        rp->green = crgb[1];
        rp->blue  = crgb[2];
    } else {
        return False;
    }

    return True;
}

/*  TORGB  --  Convert internal colour to RGB triple.  */

static void torgb()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        ads_point p;

        Spoint(p, rc.red, rc.green, rc.blue);
        ads_retpoint(p);
    }
}

/*  TOCMY  --  Convert internal colour to CMY triple.  */

static void tocmy()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        ads_point p;

        rgb_cmy(rc.red, rc.green, rc.blue, &p[X], &p[Y], &p[Z]);
        ads_retpoint(p);
    }
}

/*  TOYIQ  --  Convert internal colour to YIQ triple.  */

static void toyiq()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        ads_point p;

        rgb_yiq(rc.red, rc.green, rc.blue, &p[X], &p[Y], &p[Z]);
        ads_retpoint(p);
    }
}

/*  TOHSV  --  Convert internal colour to HSV triple.  */

static void tohsv()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        ads_point p;

        rgb_hsv(rc.red, rc.green, rc.blue, &p[X], &p[Y], &p[Z]);
        p[X] = (p[X] < 0.0) ? 0.0 : (p[X] / 360.0);
        ads_retpoint(p);
    }
}

/*  TOHLS  --  Convert internal colour to HLS triple.  */

static void tohls()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        ads_point p;

        rgb_hls(rc.red, rc.green, rc.blue, &p[X], &p[Y], &p[Z]);
        p[X] = (p[X] < 0.0) ? 0.0 : (p[X] / 360.0);
        ads_retpoint(p);
    }
}

/*  TOCNS  --  Convert internal colour to CNS string.  */

static void tocns()
{
    struct r_g_b rc;

    if (acadcol(&rc)) {
        char cnstr[40];

        rgb_cns(rc.red, rc.green, rc.blue, cnstr);
        ads_retstr(cnstr);
    }
}

/*  COLSET  --  Set colour gamut available.  */

static void colset()
{
    struct resbuf *rb = ads_getargs();

    ads_retnil();

    if (rb == NULL) {
        ads_retint(gamut);
        return;
    }

    if (rb->rbnext != NULL) {
        ads_fail(/*MSG60*/"too many arguments");
        return;
    }

    if (rb->restype == RTSHORT) {
        int colsys = rb->resval.rint;

        switch (colsys) {
        case 8:
        case 256:
            gamut = colsys;
            ads_retint(gamut);
            break;

        default:
            ads_fail(/*MSG61*/"argument out of range");
        }
        return;
    }
    ads_fail(/*MSG62*/"incorrect argument type");
}
