[comp.graphics] Star Color Summary

rww@esl.UUCP (Richard W. Webb) (12/03/88)

Greetings, Greetings, Star Buffs,

	Well, thanks to all that responded.  I now have a running program
    that displays colored stars.  I runs on a Silicon Graphics, and uses
    a graphical data satructure that is PHIGS-like and NOT AVAILALE FOR
    DISTRIBUTION.  (Sorry, but I have spent months on it, it isn't polished
    at all, it is full of hacks, and you don't really need it to display
    colored stars)

	Below, you will find some of the responses I got, what my responses
    were to them.  The last part contains the REAL code for all to enjoy.

	My thanks to Steve Allen, Stephen Walton, and Johnathan Leech for
    making this code available to me.  And, of course, a big thanks to
    Jim Blinn of JPL for writing the code in the first place.


Richard W. Webb                           ecvax!decwrl!borealis!\
ESL Inc.  MS/302                                 sdcsvax!seismo!- ames!esl!rww
495 Java Drive           (408) 738-2888 x5729     ucbcad!ucbvax!/     /
Sunnyvale, CA  94088     SMAIL: rww@esl.ESL.COM         ihnp4!lll-lcc!

--------------------------------------------------
>From: Matthew McGranaghan  <matt@uhccux.uhcc.Hawaii.Edu>
>Subject: star color in RGB
>
>I do not doubt that someone has done this but...
>I suspect that differences in DACs and phosphor chromaticities will mean that
>identical RGB numbers will give different colors on different displays.  If
>you only want things to look right relatively (and I suspect that is what you
>are after) just trust your eyes. 

        That is why I wanted also considered chromaticity coordinates.
    These are, in some sense, the true color coordinates, in that a
    stanardizing body has established how various colors are to be
    represented as (X,Y,Z) triplets.  The projection from this space
    onto my monitor's RGB space is a straight-forward linear trans-
    formation.  You are right though, the transformation is QUITE
    different from one color system to the next.  There is also the
    problem of gamma correction, that is the inherent non-linear
    response of a phosphor's brightness as a function of pixel value.
 
        All of these effects can be accounted for by "inverse transforming"
    someone else's RGB values to get XYZ and the applying the appropriate
    transformation to get the corresponding RGB values on my system.
    If they used AEDs, or Suns, or something else that I can get data for,
    then I will have no problems.  Otherwise I will have to use a rough
    guess.  A few values either way on any given component will not be
    noticeable.

        I also have the suspicion that most stars are very close to white
    anyway, so I can just use white.  I am planning on "exagerratng" the
    color deviations from white to "bring out the color" more.

--------------------------------------------------
>From: Steve Allen <sla@helios.ucsc.edu>
>
>Jim Blinn and the JPL Voyager animations used such a table.
>
>It is WRONG to base the color of a star on it's spectral type.  There are
>type O stars which look Red as seen from earth because of the interstellar
>extinction of mostly blue light by dust.  This means that the only "Right"
>way to color stars would be to use the (B-V) and (U-B) and (V-R) color
>indices (as much as they are available), and only using the spectral
>type as a last resort.  I never worked out a scheme to convert these
>into CIE coordinates (hence, into RGB, assuming you understand your 
>monitor).  
>
>I'll send you Jim Blinn's look up table if you like.

	I asked him to send this table to me.  After all, if it is good
    enough for Jim Blinn, then it is good enought for me!

--------------------------------------------------
>From: ecphssrw@afws.ARPA (Stephen R. Walton)
>
>> I wrote:
>>       O stars are "blue-white" while M stars are "reddish."  I would
>>    like to be a bit more precise than this.  Possibly, I could
>>    guess at a mapping from spectral class to temperature,
>
>You don't have to guess.  The surface temperatures for various spectral
>types are printed in most astronomy books.
>
>>   and then use
>>    the blackbody color curve (CIE chromaticity as a function of
>>    temperature) to produce a color for each spectral type.
>
>Sounds like the way to go.

	I still think temperature would be a good conversion method, but...

---------------------------------------------------
>From: Jonathan Leech <leech@cs.unc.edu>
>
>    The stuff about blackbody & CIE you posted seemed reasonably
>correct, but here's a quicker approach. I got this data from Jim Blinn
>2 years ago, it's been used in his space movies. Blame any errors on
>me though.
>    What you do is call star_color(char *class, double color[3]).
>'class' is a string with the desired spectral type e.g.  "G2", and the
>RGB intensities are returned in the array 'color'. The routine returns
>0 on success and -1 on failure (unknown spectral type). This is
>adequate for casual use, especially given the variance of color
>displays.  One thing you'll notice right off is that the dynamic range
>of a single pixel is *terrible* - you get maybe 2 magnitudes at most.
>Two solutions: (i) brighter stars occupy >1 pixel (ii) redo the
>magnitude-intensity scaling so the desired range just fits the
>perceptual intentsity limits of your display (this works reasonably well).
>    Ad astra,
>    Jon

	The code he provided is identical to the code sten by the next person.

>From: sla@ucscloa.UCSC.EDU (Steve Allen)
>Subject: Jim Blinn's scheme for coloring stars
>
>This was obtained from Jon Leech who got it thru Jim Kajiya
>THIS SCHEME DOES NOT ACCOUNT FOR INTERSTELLAR EXTINCTION
>------------------------cut here------------------------------------------
>    The star_color() subroutine, which should be FORTRAN-callable
>something like:
>
>    SUBROUTINE STAR_COLOR(CHARACTER*2 NAME, REAL*8 VALUE(3))
>
>    just looks up a 2-character string and returns the color
>(normalized so that the brightest channel is full intensity). I have
>never heard of some of these spectral types (PE? ++???), but I left
>them in anyway.  I haven't heard of 'OA' and its friends either, but
>since I didn't see any other O-type stars, I took the liberty of
>assuming they all looked like 'O' 'OA' 'OE' and 'OD', which are all
>the same color in this table anyway. I arbitrarily decided that any
>type not in the table would be the same color as the closest brighter
>type which was in the table, e.g. A1 gets the same color as the
>listed A0. I'm sure you'll tell me what incorrect assumptions I've
>made here.  (Hey, I only cranked this out last night).
>
>    When the colors are plotted from O down to M stars, a reasonable
>looking `blackbody spectrum' emerged. I think.
>
>    The biggest problem I encountered trying to plot the Yale catalog
>is that there is simply not enough dynamic range on an RGB monitor.
>At best, you get maybe 4 perceptible magnitudes. More realistically,
>you get less than 2 unless the monitor is in a darkened room (glare
>off the monitor face wipes out all the dim stars). I had to kludge it:
>clamp the maximum magnitude at -1, and assume a brightness variation
>of 1.2/magnitude. I suspect the maximum magnitude should be clamped a
>little lower, say ~1 or so (really need to figure out the maximum
>brightness displayable on a monitor/pixel), which would allow a more
>reasonable brightness scaling/mag. Another thing to try would be
>varying the physical size on the screen of the star, but this is
>harder since I'd have to generate antialiased 1-3 pixel radius circles
>to do it right and I just want something quick and dirty for this
>particular project.
>
>    Of course, this table subsumes information about the monitor
>phosphors & so on. Since we end up going to video tape (now that our
>Ampex 1" video recorder is working), it's pretty hopeless to correct
>properly anyway. I won't even talk about what NTSC does to a video
>signal.
>
>    -- Jon
>    __@/
>
>Here's the code:
>-------------------------------------------------------------------------------
#include <ctype.h>

static double Data[44][3] = {
    { 0, 0, 0 },    /* To correct for FORTRASH 1-based array indices below */
    /*  Red             Green           Blue    Name by which type is known */
    { 0.38937,        0.46526,        0.79493 },    /* B0 */
    { 0.39501,        0.47146,        0.78847 },    /* B1 */
    { 0.40103,        0.47792,        0.78151 },    /* B2 */
    { 0.40640,        0.48355,        0.77526 },    /* B3 */
    { 0.41341,        0.49071,        0.76701 },    /* B5 */
    { 0.43251,        0.50914,        0.74412 },    /* B8 */
    { 0.44342,        0.51897,        0.73079 },    /* B9 */
    { 0.45181,        0.52618,        0.72042 },    /* A0 */
    { 0.46931,        0.54026,        0.69847 },    /* A2 */
    { 0.47958,        0.54792,        0.68541 },    /* A3 */
    { 0.48538,        0.55205,        0.67797 },    /* A5 */
    { 0.50879,        0.56731,        0.64752 },    /* F0 */
    { 0.51732,        0.57231,        0.63627 },    /* F2 */
    { 0.52348,        0.57573,        0.62810 },    /* F5 */
    { 0.54076,        0.58447,        0.60496 },    /* F8 */
    { 0.54853,        0.58799,        0.59446 },    /* G0 */
    { 0.56951,        0.59623,        0.56584 },    /* G5 */
    { 0.58992,        0.60244,        0.53765 },    /* K0 */
    { 0.61098,        0.60693,        0.50828 },    /* K2 */
    { 0.63856,        0.60977,        0.46950 },    /* K5 */
    { 0.68698,        0.60595,        0.40110 },    /* M0 */
    { 0.72528,        0.59434,        0.34744 },    /* M2 */
    { 0.75182,        0.58144,        0.31097 },    /* M3 */
    { 0.78033,        0.56272,        0.27282 },    /* M4 */
    { 0.81066,        0.53676,        0.23394 },    /* M5 */
    { 0.84247,        0.50195,        0.19570 },    /* M6 */
    { 0.87512,        0.45667,        0.16004 },    /* M7 */
    { 0.71033,        0.59983,        0.36829 },    /* N0 */
    { 0.78625,        0.55816,        0.26507 },    /* N3 */
    { 0.93792,        0.33011,        0.10649 },    /* N8 */
    { 0.94897,        0.29906,        0.10012 },    /* N9 */
    { 0.79832,        0.54811,        0.24950 },    /* S4 */
    { 0.92615,        0.35935,        0.11454 },    /* ++ */
    { 0.81066,        0.53676,        0.23394 },    /* MB */
    { 0.76299,        0.57472,        0.29587 },    /* NB */
    { 0.81066,        0.53676,        0.23394 },    /* MC */
    { 0.65749,        0.60965,        0.44276 },    /* K+ */
    { 0.48538,        0.55205,        0.67797 },    /* PE */
    { 0.76299,        0.57472,        0.29587 },    /* N  */
    { 0.38241,        0.45743,        0.80282 },    /* O  */
    { 0.38241,        0.45743,        0.80282 },    /* OA */
    { 0.38241,        0.45743,        0.80282 },    /* OE */
    { 0.38241,        0.45743,        0.80282 },    /* OD */
};

typedef struct {
    char class;     /* Letter class */
    int  temp[10];  /* Indices into RGB table for numeric subclasses */
} SPECTRA;

static SPECTRA Known_stars[] = {
	/*      0   1   2   3   4   5   6   7   8   9   */
    { 'O',  {    40, 40, 40, 40, 40, 40, 40, 40, 40, 40  } },
    { 'B',  {    1,  2,  3,  4,  4,  5,  5,  5,  6,  7   } },
    { 'A',  {    8,  8,  9,  10, 10, 11, 11, 11, 11, 11  } },
    { 'F',  {    12, 12, 13, 13, 13, 14, 14, 14, 15, 15  } },
    { 'G',  {    16, 16, 16, 16, 16, 17, 17, 17, 17, 17  } },
    { 'K',  {    18, 18, 19, 19, 19, 20, 20, 20, 20, 20  } },
    { 'M',  {    21, 21, 22, 23, 24, 25, 26, 27, 27, 27  } },
    /* NB What happened to R-type? Kludge it as N-type */
    { 'R',  {    28, 28, 28, 29, 29, 29, 29, 29, 30, 31  } },
    { 'N',  {    28, 28, 28, 29, 29, 29, 29, 29, 30, 31  } },
    { 'S',  {    31, 31, 31, 31, 32, 32, 32, 32, 32, 32  } }
};

typedef struct {
    char name[3];   /* All the wierdo types in the original table, listed */
    int  temp;      /*  by full name and index into the RGB table */
} ODD_SPECTRA;

static ODD_SPECTRA Odd_stars[] = {
    { "++", 33 },   /* ??? */
    { "MB", 34 },
    { "NB", 35 },
    { "MC", 36 },
    { "K+", 37 },
    { "PE", 38 },
    { "N ", 39 },
    { "O ", 40 },
    { "OA", 41 },
    { "OE", 42 },
    { "OD", 43 }
};

static int Num_known_stars = sizeof(Known_stars) / sizeof(Known_stars[0]),
	   Num_odd_stars = sizeof(Odd_stars) / sizeof(Odd_stars[0]);

/* Given a spectral class name in classname, return the
 *  scaled RGB color of a star of that class in col[].
 *  The table we use only contains entries for some of
 *  the 'normal' spectral range, so it's set up to interpolate.
 */

int star_color(classname, col)
char *classname;
double col[3];
{
    int i, index = -1;
    char class[3];

    /* Normal spectral types - O0 .. S9 */
    if (isalpha(classname[0]) && isdigit(classname[1])) {
	int subclass = classname[1] - '0';

	class[0] = classname[0];

	if (islower(class[0]))
	    class[0] = toupper(class[0]);

	for (i = 0; i < Num_known_stars; i++) {
	    if (class[0] == Known_stars[i].class) {
		index = Known_stars[i].temp[subclass];
		break;
	    }
	}
    }

    /* Otherwise case out on the remaining wierdos.
     * Convert the class name to upper case for comparison purposes.
     */
    if (index == -1) {
	class[0] = classname[0];
	if (isalpha(class[0]) && islower(class[0]))
	    class[0] = toupper(class[0]);

	class[1] = classname[1];
	if (isalpha(class[1]) && islower(class[1]))
	    class[1] = toupper(class[1]);

	class[2] = '\0';

	for (i = 0; i < Num_odd_stars; i++) {
	    if (class[0] == Odd_stars[i].name[0] &&
		class[1] == Odd_stars[i].name[1]) {

		index = Odd_stars[i].temp;
		break;
	    }
	}
    }

    if (index != -1) {
	double max, fmax3();

    /*
	printf("Index = %d\n", index);
     */
	max = fmax3(Data[index][0], Data[index][1], Data[index][2]);

	col[0] = Data[index][0] / max;
	col[1] = Data[index][1] / max;
	col[2] = Data[index][2] / max;

	return 0;
    }

    col[0] = col[1] = col[2] = 0.0;
    return -1;
}

double fmax3(a,b,c)
double a, b, c;
{
    double max = a;

    if (b > max)
	max = b;
    if (c > max)
	max = c;

    return max;
}

-- 
Richard W. Webb                           ecvax!decwrl!borealis!\
ESL Inc.  MS/302                                 sdcsvax!seismo!- ames!esl!rww
495 Java Drive           (408) 738-2888 x5729     ucbcad!ucbvax!/     /
Sunnyvale, CA  94088     SMAIL: rww@esl.ESL.COM         ihnp4!lll-lcc!