
/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. 
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information. 
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-* 
-* 
-*
Z* -------------------------------------------------------------------
*/
#include"os_python.h"
#include"os_predef.h"
#include"os_std.h"

#include"Base.h"
#include"MemoryDebug.h"
#include"Ortho.h"
#include"Word.h"

#include"Color.h"
#include"PConv.h"
#include"ObjectGadgetRamp.h"
#include"Util.h"
#include"Executive.h"
#include"MyPNG.h"
#include"Scene.h"
#include "Feedback.h"

static int AutoColor[] = {
  26,                           /* carbon */
  5,                            /* cyan */
  154,                          /* lightmagenta */
  6,                            /* yellow */
  9,                            /* salmon */
  29,                           /* hydrogen */
  11,                           /* slate */
  13,                           /* orange */
  10,                           /* lime */
  5262,                         /* deepteal */
  12,                           /* hotpink */
  36,                           /* yelloworange */
  5271,                         /* violetpurple */
  124,                          /* grey70 */
  17,                           /* marine */
  18,                           /* olive */
  5270,                         /* smudge */
  20,                           /* teal */
  5272,                         /* dirtyviolet */
  52,                           /* wheat */
  5258,                         /* deepsalmon */
  5274,                         /* lightpink */
  5257,                         /* aquamarine */
  5256,                         /* paleyellow */
  15,                           /* limegreen */
  5277,                         /* skyblue */
  5279,                         /* warmpink */
  5276,                         /* limon */
  53,                           /* violet */
  5278,                         /* bluewhite */
  5275,                         /* greencyan */
  5269,                         /* sand */
  22,                           /* forest */
  5266,                         /* lightteal */
  5280,                         /* darksalmon */
  5267,                         /* splitpea */
  5268,                         /* raspberry */
  104,                          /* grey50 */
  23,                           /* deepblue */
  51,                           /* brown */
};

static int nAutoColor = 40;
static void lookup_color(CColor * I, const float *in, float *out, int big_endian);

void ColorGetBkrdContColor(PyMOLGlobals * G, float *rgb, int invert_flag)
{
  const float *bkrd = ColorGet(G, SettingGet_color(G, nullptr, nullptr, cSetting_bg_rgb));

  if(!invert_flag) {
    if((bkrd[0] + bkrd[1] + bkrd[2]) > 0.5F) {
      rgb[0] = 1.0F;
      rgb[1] = 1.0F;
      rgb[2] = 1.0F;
    } else {
      rgb[0] = 0.0F;
      rgb[1] = 0.0F;
      rgb[2] = 0.0F;
    }
  }

  {
    int a;
    for(a = 0; a < 3; a++)
      if(fabs(bkrd[a] - rgb[a]) < 0.5F) {
        rgb[a] = 1.0F - rgb[a];
        if(fabs(bkrd[a] - rgb[a]) < 0.5F) {
          if(bkrd[a] > 0.5F)
            rgb[a] = 0.0F;
          else
            rgb[a] = 1.0F;
        }
      }
  }
}

unsigned int ColorGet32BitWord(PyMOLGlobals * G, const float *rgba)
{
  CColor *I = G->Color;
  unsigned int rc, gc, bc, ac;
  unsigned int result;

  rc = (int) (255 * rgba[0] + 0.49999F);
  gc = (int) (255 * rgba[1] + 0.49999F);
  bc = (int) (255 * rgba[2] + 0.49999F);
  ac = (int) (255 * rgba[3] + 0.49999F);

  if(rc > 255)
    rc = 255;
  if(bc > 255)
    bc = 255;
  if(gc > 255)
    gc = 255;
  if(ac > 255)
    ac = 255;

  if(I->BigEndian) {
    result = (rc << 24) | (gc << 16) | (bc << 8) | ac;
  } else {
    result = (ac << 24) | (bc << 16) | (gc << 8) | rc;
  }
  return result;
}

int ColorGetNext(PyMOLGlobals * G)
{
  int result;
  int next;
  next = SettingGetGlobal_i(G, cSetting_auto_color_next);

  if(next >= nAutoColor)
    next = 0;
  result = AutoColor[next];
  next++;
  if(next >= nAutoColor)
    next = 0;
  SettingSetGlobal_i(G, cSetting_auto_color_next, next);
  return (result);
}

int ColorGetCurrent(PyMOLGlobals * G)
{
  int result;
  int next;
  next = SettingGetGlobal_i(G, cSetting_auto_color_next);
  next--;
  if(next < 0)
    next = (nAutoColor - 1);
  result = AutoColor[next];
  return (result);
}

int ColorCheckRamped(PyMOLGlobals * G, int index)
{
  return (index <= (cColorExtCutoff));
}

ObjectGadgetRamp *ColorGetRamp(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  if(index <= cColorExtCutoff) {
    index = cColorExtCutoff - index;
    if (index < I->Ext.size()) {
      auto& ext = I->Ext[index];
      if (!ext.Ptr && ext.Name) {
        ext.Ptr = ExecutiveFindObject<ObjectGadgetRamp>(G, ext.Name);
      }
      return ext.Ptr;
    }
  }
  return nullptr;
}

int ColorGetRamped(PyMOLGlobals * G, int index, const float *vertex, float *color, int state)
{
  CColor *I = G->Color;
  int ok = false;
  if (auto* ptr = ColorGetRamp(G, index)) {
    ok = ObjectGadgetRampInterVertex(ptr, vertex, color, state);
  }
  if(!ok) {
    color[0] = 1.0;
    color[1] = 1.0;
    color[2] = 1.0;
  } else if(I->LUTActive) {
    lookup_color(I, color, color, I->BigEndian);
  }
  return (ok);
}

/**
 * Gets a color as 3 floats from an index and writes it into
 * the color argument.  If the index is a ramp, then it uses the vertex and state arguments to lookup the
 * color value in the ramp.
 * NOTES: does not support index values cColorObject(-5) or cColorAtomic(-4) color since the object
 *        or atom color is not passed in.
 *
 * @param index - color index value
 * @param vertex - x/y/z used for ramp lookup (if color index is a ramp)
 * @param[out] color - output color array of 3 floats
 * @param state - state lookup if ramp
 *
 * @return whether the color index is dependent on a ramp.
 */
bool ColorGetCheckRamped(PyMOLGlobals * G, int index, const float *vertex, float *color, int state)
{
  bool isRamped = false;
  if(ColorCheckRamped(G, index)) {
    ColorGetRamped(G, index, vertex, color, state);
    isRamped = true;
  } else {
    copy3f(ColorGet(G, index), color);
  }
  return isRamped;
}

/**
 * Find a record by case-insensitive name
 *
 * @param seq Indexable container (Color or Ext)
 * @param name Color name
 * @return seq index or -1 if not found
 */
template <typename Sequence>
static int findByCaseInsensitiveName(
    PyMOLGlobals* G, const Sequence& seq, const char* name)
{
  for (int a = 0; a < seq.size(); ++a) {
    auto* color_name = seq[a].Name;
    if (color_name) {
      int wm = WordMatch(G, name, color_name, true);
      if (wm < 0) {
        return a;
      }
    }
  }

  return -1;
}

/**
 * Find a record by case-insensitive and/or partial name
 *
 * @param seq Indexable container (Color or Ext)
 * @param name Color name
 * @param[in,out] best Word match score (0 for perfect match), must not be
 * negative
 * @return seq index or -1 if not found
 */
template <typename Sequence>
static int findByCaseInsensitivePrefix(
    PyMOLGlobals* G, const Sequence& seq, const char* name, int& best)
{
  int best_a = -1;
  assert(best >= 0);

  // search for an imperfect match
  for (int a = 0; a < seq.size(); ++a) {
    auto* color_name = seq[a].Name;
    if (color_name) {
      auto wm = WordMatch(G, name, color_name, true);
      if (wm < 0) {
        // perfect case-insensitive match
        best = 0;
        return a;
      }

      if (best < wm) {
        // prefix match
        best = wm;
        best_a = a;
      }
    }
  }

  return best_a;
}

/**
 * Find a color ramp by case-insensitive name
 *
 * @param name Color name (ramp name)
 * @return Ext index or -1 if not found
 */
static int ColorFindExtByName(PyMOLGlobals* G, const char* name)
{
  return findByCaseInsensitiveName(G, G->Color->Ext, name);
}

/**
 * Map name to index (idx[name] = index)
 *
 * If the name is already in use and the index can't be reused, then clear the
 * name on the existing color or ext record.
 *
 * @param index Color index
 * @param name Color name
 * @param reuse If the name already exists, reuse the existing index if possible
 * (not possible to reuse a ramp index for a color or vice versa)
 *
 * @return pointer to stored name string
 */
static const char* reg_name(CColor* const I, CColor::ColorIdx const index,
    const char* name, bool reuse = false)
{
  auto handle = I->Idx.emplace(name, index);
  auto& handle_name = handle.first->first;
  auto& handle_index = handle.first->second;

  if (handle_index != index &&
      (!reuse || bool(cColorExtCutoff < handle_index) !=
                     bool(cColorExtCutoff < index))) {
    assert(!handle.second);

    // if we're stealing a name to a new index, clear the name on the old record
    if (handle_index <= cColorExtCutoff) {
      auto& ext = I->Ext[cColorExtCutoff - handle_index];
      assert(ext.Name == handle_name.c_str());
      ext.Name = nullptr;
    } else if (handle_index >= 0) {
      auto& col = I->Color[handle_index];
      assert(col.Name == handle_name.c_str());
      col.Name = nullptr;
    }

    handle_index = index;
  }

  return handle_name.c_str();
}

void ColorRegisterExt(PyMOLGlobals* G, const char* name, ObjectGadgetRamp* ptr)
{
  CColor *I = G->Color;
  int a;

  a = ColorFindExtByName(G, name);
  if(a < 0) {
    a = I->Ext.size();

    I->Ext.emplace_back();
    auto& ext = I->Ext.back();

    ext.Name = reg_name(I, cColorExtCutoff - a, name);
    assert(I->Idx[ext.Name] == cColorExtCutoff - a);
  }
  if(a >= 0) {
    I->Ext[a].Ptr = ptr;
  }
}

void ColorForgetExt(PyMOLGlobals * G, const char *name)
{
  CColor *I = G->Color;
  auto a = ColorFindExtByName(G, name);

  if (a < 0)
    return;

  // currently leaks memory in I->Ext array
  auto& ext = I->Ext[a];
  ext.Ptr = nullptr;

  // HaveOldSessionExtColors should only be true while we're loading a partial
  // session, and ColorForgetExt probably means that we're replacing the ramp
  // object with a another one, so we don't want to lose the name+index
  // relationship.
  if (ext.Name && !I->HaveOldSessionExtColors) {
    I->Idx.erase(ext.Name);
    ext.Name = nullptr;
  }
}

PyObject *ColorExtAsPyList(PyMOLGlobals * G)
{
  CColor *I = G->Color;

  auto* result = PyList_New(I->Ext.size());

  size_t a = 0;

  for (const auto& ext : I->Ext) {
    auto* list = PyList_New(2);
    const char* name = ext.Name ? ext.Name : "";
    PyList_SetItem(list, 0, PyString_FromString(name));

    // obsolete since PyMOL 2.5, store for backwards compatibility
    PyList_SetItem(list, 1, PyInt_FromLong(cColorGadgetRamp));

    PyList_SetItem(result, a++, list);
  }

  assert(a == I->Ext.size());

  return result;
}


/*========================================================================*/
PyObject *ColorAsPyList(PyMOLGlobals * G)
{
  CColor *I = G->Color;

  size_t n_custom = 0;
  for (const auto& color : I->Color) {
    if (color.Custom || color.LutColorFlag) {
      n_custom++;
    }
  }

  auto* result = PyList_New(n_custom);

  size_t a = 0;
  size_t c = 0;

  for (const auto& color : I->Color) {
    if (color.Custom || color.LutColorFlag) {
      auto* list = PyList_New(7);
      PyList_SetItem(list, 0, PyString_FromString(color.Name ? color.Name : ""));
      PyList_SetItem(list, 1, PyInt_FromLong(a));
      PyList_SetItem(list, 2, PConvFloatArrayToPyList(color.Color, 3));
      PyList_SetItem(list, 3, PyInt_FromLong(color.Custom));
      PyList_SetItem(list, 4, PyInt_FromLong(color.LutColorFlag));
      PyList_SetItem(list, 5, PConvFloatArrayToPyList(color.LutColor, 3));
      PyList_SetItem(list, 6, PyInt_FromLong(color.Fixed));
      PyList_SetItem(result, c++, list);
    }
    ++a;
  }

  assert(c == n_custom);

  return result;
}

/*========================================================================*/
int ColorConvertOldSessionIndex(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  if(index > cColorExtCutoff) {
    if(I->HaveOldSessionColors) {
      for (int a = int(I->Color.size()) - 1; a >= 0; --a) {
        if (index == I->Color[a].old_session_index) {
          return a;
        }
      }
    }
  } else if(I->HaveOldSessionExtColors) {
    for (int a = int(I->Ext.size()) - 1; a >= 0; --a) {
      if (index == I->Ext[a].old_session_index) {
        return cColorExtCutoff - a;
      }
    }
  }
  return index;                 /* failsafe */
}

#define return_error_if_fail(e) p_return_val_if_fail((e), false);

int ColorExtFromPyList(PyMOLGlobals * G, PyObject * list, int partial_restore)
{
  CColor *I = G->Color;
  size_t n_ext = 0;

  assert(!I->HaveOldSessionExtColors);

  if (list && PyList_Check(list)) {
    n_ext = PyList_Size(list);
  }

  /* TO SUPPORT BACKWARDS COMPATIBILITY...
     Always check ll when adding new PyList_GetItem's */

  if (partial_restore) {
    I->HaveOldSessionExtColors = I->Ext.size() > 0;

    for (auto& ext : I->Ext) {
      ext.old_session_index = 0;
    }
  } else {
    I->Ext.clear();
  }

  for (int a = 0; a < n_ext; ++a) {
    auto* rec = PyList_GetItem(list, a);

    return_error_if_fail(rec != nullptr);
    return_error_if_fail(PyList_Check(rec));

    std::string name;
    return_error_if_fail(PConvFromPyListItem(G, rec, 0, name));

    char const* name_ptr =
        reg_name(I, cColorExtCutoff - I->Ext.size(), name.c_str(), true);
    int const a_new = cColorExtCutoff - I->Idx[name];

    assert(a_new >= 0);
    assert(a_new <= I->Ext.size());
    assert(a_new == a || partial_restore);

    if (a_new == I->Ext.size()) {
      I->Ext.emplace_back();
    } else {
      assert(partial_restore);
    }

    auto& ext = I->Ext[a_new];
    ext.Name = name_ptr;
    ext.old_session_index = cColorExtCutoff - a;

    CPythonVal_Free(rec);
  }

  return true;
}


/*========================================================================*/
int ColorFromPyList(PyMOLGlobals * G, PyObject * list, int partial_restore)
{
  CColor* I = G->Color;

  assert(!I->HaveOldSessionColors);

  if (partial_restore) {
    for (auto& color : I->Color) {
      color.old_session_index = 0;
    }
  }

  return_error_if_fail(list != nullptr );
  return_error_if_fail(PyList_Check(list));

  int const n_custom = PyList_Size(list);

  for (int a = 0; a < n_custom; ++a) {
    auto rec = PyList_GetItem(list, a);

    return_error_if_fail(rec && PyList_Check(rec));

    auto const ll = PyList_Size(rec);
    /* TO SUPPORT BACKWARDS COMPATIBILITY...
       Always check ll when adding new PyList_GetItem's */

    int index = 0;
    return_error_if_fail(PConvFromPyListItem(G, rec, 1, index));

    std::string name;
    return_error_if_fail(PConvFromPyListItem(G, rec, 0, name));

    int const old_session_index = index;
    if (partial_restore && I->Color.size() > index) {
      // conflicts with an existing color
      index = I->Color.size();
      I->HaveOldSessionColors = true;
    }

    if (index >= I->Color.size()) {
      assert(I->Color.size() == index);
      I->Color.emplace_back(reg_name(I, index, name.c_str()));
    }

    auto& color = I->Color[index];
    color.old_session_index = old_session_index;

    assert(name == color.Name);
    assert(index == I->Idx[name]);

    return_error_if_fail(CPythonVal_PConvPyListToFloatArrayInPlace_From_List(
        G, rec, 2, color.Color, 3));

    if (PyList_Size(rec) >= 6) {
      return_error_if_fail(PConvFromPyListItem(G, rec, 3, color.Custom));
      return_error_if_fail(PConvFromPyListItem(G, rec, 4, color.LutColorFlag));
      return_error_if_fail(CPythonVal_PConvPyListToFloatArrayInPlace_From_List(
          G, rec, 5, color.LutColor, 3));
    } else {
      color.Custom = true;
    }

    if (ll > 6) {
      PConvFromPyListItem(G, rec, 6, color.Fixed);
    } else {
      color.Fixed = false;
    }

    CPythonVal_Free(rec);
  }

  return true;
}

/*========================================================================*/
void ColorDef(PyMOLGlobals * G, const char *name, const float *v, int mode, int quiet)
{
  CColor *I = G->Color;
  int color = -1;

  // Search for a perfect case-sensitive match
  {
    auto it = I->Idx.find(name);
    if (it != I->Idx.end()) {
      color = it->second;
    }
  }

  if (color < 0) {
    // Not found or ramp index -> do slow search
    color = findByCaseInsensitiveName(G, I->Color, name);

    if (color < 0) {
      // Not found -> new entry
      color = I->Color.size();
      I->Color.emplace_back(reg_name(I, color, name));
      assert(I->Idx[name] == color);
    }
  }

  copy3f(v, I->Color[color].Color);

  switch (mode) {
  case 1:
    I->Color[color].Fixed = true;
    break;
  default:
    I->Color[color].Fixed = false;
    break;
  }

  I->Color[color].Custom = true;
  ColorUpdateFromLut(G, color);

  if(!quiet) {
    PRINTFB(G, FB_Executive, FB_Actions)
      " Color: \"%s\" defined as [ %3.3f, %3.3f, %3.3f ].\n", name, v[0], v[1], v[2]
      ENDFB(G);

  }

  PRINTFD(G, FB_Color)
    " Color: and assigned number %d.\n", color ENDFD;
}


/*========================================================================*/
int ColorGetIndex(PyMOLGlobals * G, const char *name)
{
  CColor *I = G->Color;
  int i;
  int is_numeric = true;

  {
    const char *c;
    c = name;
    while(*c) {
      if((((*c) < '0') || ((*c) > '9')) && ((*c) != '-')) {
        is_numeric = false;
        break;
      }
      c++;
    }
  }

  if(is_numeric) {
    if(sscanf(name, "%d", &i)) {
      if((i < I->Color.size()) && (i >= 0))
        return (i);
      else if(i == cColorNewAuto)
        return (ColorGetNext(G));
      else if(i == cColorCurAuto)
        return (ColorGetCurrent(G));
      else if(i == cColorAtomic)
        return cColorAtomic;
      else if(i == cColorObject)
        return cColorObject;
      else if(i == cColorFront)
        return cColorFront;
      else if(i == cColorBack)
        return cColorBack;
      else if(i == cColorDefault)
        return cColorDefault;
      if (i & cColor_TRGB_Bits)
        return i;
    }
  }
  if((name[0] == '0') && (name[1] == 'x')) {    /* explicit hex RGB 0x000000 */
    int tmp_color;
    if(sscanf(name + 2, "%x", (unsigned int *) &tmp_color) == 1) {
      tmp_color = (cColor_TRGB_Bits |
                   (tmp_color & 0x00FFFFFF) | ((tmp_color >> 2) & 0x3F000000));
      return tmp_color;
    }
  }

  // the following block used to allow prefix matches (before PyMOL 2.5)
  if(WordMatch(G, name, "default", true) < 0)
    return cColorDefault;
  if(WordMatch(G, name, "auto", true) < 0)
    return (ColorGetNext(G));
  if(WordMatch(G, name, "current", true) < 0)
    return (ColorGetCurrent(G));
  if(WordMatch(G, name, "atomic", true) < 0)
    return (cColorAtomic);
  if(WordMatch(G, name, "object", true) < 0)
    return (cColorObject);
  if(WordMatch(G, name, "front", true) < 0)
    return (cColorFront);
  if(WordMatch(G, name, "back", true) < 0)
    return (cColorBack);

  // search for a perfect case-sensitive match (fast!)
  {
    auto it = I->Idx.find(name);
    if (it != I->Idx.end()) {
      return it->second;
    }
  }

  // search for case-insensitive or partial match
  // TODO does this even make sense? What's the use case? Should this be
  // restricted to non-ambiguous matches? Note that the Python cmd.color()
  // function does its own prefix lookup and rejects ambiguous matches.
  int best = 0;
  int color = findByCaseInsensitivePrefix(G, I->Color, name, best);
  if (best != 0 || color < 0) {
    int const ext_color = findByCaseInsensitivePrefix(G, I->Ext, name, best);
    if (ext_color >= 0) {
      color = cColorExtCutoff - ext_color;
    }
  }

  return color;
}


/*========================================================================*/
const float *ColorGetNamed(PyMOLGlobals * G, const char *name)
{
  return (ColorGet(G, ColorGetIndex(G, name)));
}


/*========================================================================*/
const char *ColorGetName(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  if((index >= 0) && (index < I->Color.size())) {
    return I->Color[index].Name;
  } else if((index & cColor_TRGB_Mask) == cColor_TRGB_Bits) {
    index = (((index & 0xFFFFFF) | ((index << 2) & 0xFC000000) |        /* convert 6 bits of trans into 8 */
              ((index >> 4) & 0x03000000)));
    if(index & 0xFF000000)      /* if transparent */
      sprintf(I->RGBName, "0x%08x", index);
    else                        /* else */
      sprintf(I->RGBName, "0x%06x", index);
    return I->RGBName;
  } else if(index <= cColorExtCutoff) {
    int a = cColorExtCutoff - index;
    if (a < I->Ext.size()) {
      return I->Ext[a].Name;
    } else
      return nullptr;
  }
  return (nullptr);
}


/*========================================================================*/
int ColorGetStatus(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  /* return 0 if color is invalid, -1 if hidden; 
     1 otherwise */
  int result = 0;
  if((index >= 0) && (index < I->Color.size())) {
    auto* color_name = I->Color[index].Name;
    if(color_name) {
      const char* c = color_name;
      result = 1;
      while(*c) {
        if(((*c) >= '0') && ((*c) <= '9')) {
          result = -1;
          break;
        }
        c++;
      }
    }
  }
  return (result);
}


/*========================================================================*/
int ColorGetNColor(PyMOLGlobals * G)
{
  CColor *I = G->Color;
  return (I->Color.size());
}


/*========================================================================*/
void ColorFree(PyMOLGlobals * G)
{
  DeleteP(G->Color);
}


/*========================================================================*/

void ColorReset(PyMOLGlobals * G)
{

/* PyMOL core color names

  1   1   1   white
 .5  .5  .5   grey/gray
  0   0   0   black 

  1   0   0   red
  0   1   0   green
  0   0   1   blue

  1   1   0   yellow
  1   0   1   magenta
  0   1   1   cyan

  1   1  .5   paleyellow  .
  1  .5   1   violet      .
 .5   1   1   aquamarine  .

  1  .5  .5   deepsalmon  .
 .5   1  .5   palegreen   .
 .5  .5   1   slate       .

 .75 .75  0   olive       .
 .75  0  .75  purple      .
  0  .75 .75  teal        .

 .6  .6  .1   deepolive   .
 .6  .1  .6   deeppurple  .
 .1  .6  .6   deepteal    .

  1  .5   0   orange      .
  1   0  .5   hotpink     .
 .5   1   0   chartreuse  .
  0   1  .5   limegreen   .
  0  .5   1   marine      .
 .5   0   1   purpleblue  .

*/

  CColor *I = G->Color;

  I->Idx.clear();
  I->Ext.clear();

  auto& Color = I->Color;
  Color.clear();
  Color.reserve(5500);

  char name[10];
  int a;
  int set1;
  float f;
  float spectrumS[13][3] = {
    {1.0, 0.0, 1.0},            /* magenta - 0 */
    {0.5, 0.0, 1.0},
    {0.0, 0.0, 1.0},            /* blue - 166.66  */
    {0.0, 0.5, 1.0},
    {0.0, 1.0, 1.0},            /* cyan - 333.33 */

    {0.0, 1.0, 0.5},
    {0.0, 1.0, 0.0},            /* green - 500 */
    {0.5, 1.0, 0.0},
    {1.0, 1.0, 0.0},            /* yellow - 666.66 */
    {1.0, 0.5, 0.0},

    {1.0, 0.0, 0.0},            /* red - 833.33 */
    {1.0, 0.0, 0.5},
    {1.0, 0.0, 1.0},            /* magenta - 999 */
  };

  float spectrumR[13][3] = {
    {1.0, 1.0, 0.0},            /* yellow - 0 */
    {0.5, 1.0, 0.0},            /* chartreuse */
    {0.0, 1.0, 0.0},            /* green - 166.66 */
    {0.0, 1.0, 0.5},            /* limegreen */
    {0.0, 1.0, 1.0},            /* cyan - 333.33 */

    {0.0, 0.5, 1.0},            /* marine */
    {0.0, 0.0, 1.0},            /* blue - 500 */
    {0.5, 0.0, 1.0},            /* purpleblue */
    {1.0, 0.0, 1.0},            /* magenta - 666.66 */
    {1.0, 0.0, 0.5},            /* hotpink */

    {1.0, 0.0, 0.0},            /* red - 833.33 */
    {1.0, 0.5, 0.0},            /* orange */
    {1.0, 1.0, 0.0},            /* yellow - 999 */
  };

  float spectrumC[][3] = {
    {1.0, 1.0, 0.0},            /* yellow - 0 */
    {0.0, 0.0, 1.0},            /* blue - 83.333 */
    {1.0, 0.0, 0.0},            /* red - 167.67 */
    {0.0, 1.0, 0.0},            /* green - 250.00 */
    {1.0, 0.0, 1.0},            /* magenta - 333.33 */

    {0.0, 1.0, 1.0},            /* cyan - 416.67 */
    {1.0, 1.0, 0.0},            /* yellow - 500.00 */
    {0.0, 1.0, 0.0},            /* green - 583.33 */
    {0.0, 0.0, 1.0},            /* blue - 666.67 */
    {1.0, 0.0, 1.0},            /* magenta - 750.00 */

    {1.0, 1.0, 0.0},            /* yellow - 833.33 */
    {1.0, 0.0, 0.0},            /* red - 916.67 */
    {0.0, 1.0, 1.0},            /* cyan - 999 */
  };

  float spectrumW[][3] = {
    {1.0, 1.0, 0.0},            /* yellow - 0 */
    {1.0, 1.0, 1.0},            /* white */
    {0.0, 0.0, 1.0},            /* blue  - 83.333 */
    {1.0, 1.0, 1.0},            /* white */
    {1.0, 0.0, 0.0},            /* red - 166.67 */

    {1.0, 1.0, 1.0},            /* white */
    {0.0, 1.0, 0.0},            /* green - 250.00 */
    {1.0, 1.0, 1.0},            /* white */
    {1.0, 0.0, 1.0},            /* magenta - 333.33 */
    {1.0, 1.0, 1.0},            /* white */

    {0.0, 1.0, 1.0},            /* cyan - 416.67 */
    {1.0, 1.0, 1.0},            /* white */
    {1.0, 1.0, 0.0},            /* yellow - 500.00 */
    {1.0, 1.0, 1.0},            /* white */
    {0.0, 1.0, 0.0},            /* green - 583.33 */

    {1.0, 1.0, 1.0},            /* white */
    {0.0, 0.0, 1.0},            /* blue - 666.67 */
    {1.0, 1.0, 1.0},            /* white */
    {1.0, 0.0, 1.0},            /* magenta - 750.00 */
    {1.0, 1.0, 1.0},            /* white */

    {1.0, 1.0, 0.0},            /* yellow - 833.33 */
    {1.0, 1.0, 1.0},            /* white */
    {1.0, 0.0, 0.0},            /* red - 916.67 */
    {1.0, 1.0, 1.0},            /* white */
    {0.0, 1.0, 1.0},            /* cyan - 999 */
  };

  float spectrumO[29][3] = {
    /* a rainbow with perceptive color balancing and extra blue/red at the ends */
    {1.0, 0.0, 1.0},            /* violet */
    {0.8F, 0.0, 1.0},

    {0.5F, 0.0, 1.0},           /* blend */

    {0.0, 0.0, 1.0},            /* blue */
    {0.0, 0.0, 1.0},            /* blue */
    {0.0, 0.2F, 1.0},

    {0.0, 0.5F, 1.0},           /* blend */

    {0.0, 0.8F, 1.0},
    {0.0, 1.0, 1.0},            /* cyan */
    {0.0, 1.0, 0.8F},

    {0.0, 1.0, 0.5F},           /* blend */

    {0.0, 1.0, 0.2F},
    {0.0, 1.0, 0.0},            /* green */
    {0.2F, 1.0, 0.0},

    {0.5F, 1.0, 0.0},           /* blend */

    {0.8F, 1.0, 0.0},
    {1.0, 1.0, 0.0},            /* yellow */
    {1.0, 0.9F, 0.0},

    {1.0, 0.75F, 0.0},          /* blend */

    {1.0, 0.6F, 0.0},
    {1.0, 0.5F, 0.0},           /* orange */
    {1.0, 0.4F, 0.0},

    {1.0, 0.3F, 0.0},           /* blend */

    {1.0, 0.2F, 0.0},
    {1.0, 0.0, 0.0},            /* red */
    {1.0, 0.0, 0.0},            /* red */

    {1.0, 0.0, 0.5F},           /* blend */

    {1.0, 0.0, 0.8F},           /* violet */
    {1.0, 0.0, 1.0},            /* violet */
  };

  /* BLUE->VIOLET->RED r546 to r909 */
  /* BLUE->CYAN->GREEN->YELLOW->RED s182 to s909 */
  /* BLUE->WHITE->RED w00 to */

#define reg_named_color(name, R, G, B)                                         \
  {                                                                            \
    Color.emplace_back(reg_name(I, Color.size(), name));                       \
    set3f(Color.back().Color, R, G, B);                                        \
    assert(I->Idx[name] == Color.size() - 1);                                  \
  }

  reg_named_color("white", 1.F, 1.F, 1.F);
  reg_named_color("black", 0.F, 0.F, 0.F);
  reg_named_color("blue", 0.F, 0.F, 1.F);
  reg_named_color("green", 0.F, 1.F, 0.F);
  reg_named_color("red", 1.F, 0.F, 0.F);
  reg_named_color("cyan", 0.F, 1.F, 1.F);
  reg_named_color("yellow", 1.F, 1.F, 0.F);
  reg_named_color("dash", 1.F, 1.F, 0.F);
  reg_named_color("magenta", 1.F, 0.F, 1.F);
  reg_named_color("salmon", 1.F, 0.6F, 0.6F);
  reg_named_color("lime", 0.5F, 1.F, 0.5F);
  reg_named_color("slate", 0.5F, 0.5F, 1.F);
  reg_named_color("hotpink", 1.F, 0.F, 0.5F);
  reg_named_color("orange", 1.F, 0.5F, 0.F);
  reg_named_color("chartreuse", 0.5F, 1.F, 0.F); /* AKA puke green */
  reg_named_color("limegreen", 0.F, 1.F, 0.5F);
  reg_named_color("purpleblue", 0.5F, 0.F, 1.F); /* legacy name */
  reg_named_color("marine", 0.F, 0.5F, 1.F);
  reg_named_color("olive", 0.77F, 0.7F, 0.F);
  reg_named_color("purple", 0.75F, 0.F, 0.75F);
  reg_named_color("teal", 0.F, 0.75F, 0.75F);
  reg_named_color("ruby", 0.6F, 0.2F, 0.2F);
  reg_named_color("forest", 0.2F, 0.6F, 0.2F);
  reg_named_color("deepblue", 0.25F, 0.25F, 0.65F); /* was "deep" */
  reg_named_color("grey", 0.5F, 0.5F, 0.5F); /* english spelling */
  reg_named_color("gray", 0.5F, 0.5F, 0.5F); /* american spelling */
  reg_named_color("carbon", 0.2F, 1.F, 0.2F);
  reg_named_color("nitrogen", 0.2F, 0.2F, 1.F);
  reg_named_color("oxygen", 1.F, 0.3F, 0.3F);
  reg_named_color("hydrogen", 0.9F, 0.9F, 0.9F);
  reg_named_color("brightorange", 1.F, 0.7F, 0.2F);
  reg_named_color("sulfur", 0.9F, 0.775F, 0.25F);
  reg_named_color("tv_red", 1.F, 0.2F, 0.2F);
  reg_named_color("tv_green", 0.2F, 1.F, 0.2F);
  reg_named_color("tv_blue", 0.3F, 0.3F, 1.F);
  reg_named_color("tv_yellow", 1.F, 1.F, 0.2F);
  reg_named_color("yelloworange", 1.F, 0.87F, 0.37F);
  reg_named_color("tv_orange", 1.F, 0.55F, 0.15F);
  reg_named_color("br0", 0.1F, 0.1F, 1.F);
  reg_named_color("br1", 0.2F, 0.1F, 0.9F);
  reg_named_color("br2", 0.3F, 0.1F, 0.8F);
  reg_named_color("br3", 0.4F, 0.1F, 0.7F);
  reg_named_color("br4", 0.5F, 0.1F, 0.6F);
  reg_named_color("br5", 0.6F, 0.1F, 0.5F);
  reg_named_color("br6", 0.7F, 0.1F, 0.4F);
  reg_named_color("br7", 0.8F, 0.1F, 0.3F);
  reg_named_color("br8", 0.9F, 0.1F, 0.2F);
  reg_named_color("br9", 1.F, 0.1F, 0.1F);
  reg_named_color("pink", 1.F, 0.65F, 0.85F);
  reg_named_color("firebrick", 0.698F, 0.13F, 0.13F);
  reg_named_color("chocolate", 0.555F, 0.222F, 0.111F);
  reg_named_color("brown", 0.65F, 0.32F, 0.17F);
  reg_named_color("wheat", 0.99F, 0.82F, 0.65F);
  reg_named_color("violet", 1.F, 0.5F, 1.F);

  /* greybow */

  strcpy(name, "grey00");       /* english spelling */
  for(a = 0; a < 100; a = a + 1) {
    name[5] = (a % 10) + '0';
    name[4] = ((a % 100) / 10) + '0';
    /* sprintf(color->Name,"grey%02d",a); */
    reg_named_color(name, a / 99.F, a / 99.F, a / 99.F);
  }

  reg_named_color("lightmagenta", 1.F, 0.2F, 0.8F);

#define A_DIV 83.333333333F

  /* full spectrum (s000-s999) */

  strcpy(name, "s000");
  for(a = 0; a < 1000; a = a + 1) {
    set1 = (int) (a / A_DIV);
    name[3] = (a % 10) + '0';
    name[2] = ((a % 100) / 10) + '0';
    name[1] = ((a % 1000) / 100) + '0';
    /* sprintf(color->Name,"s%03d",a); */
    f = 1.0F - (a - (set1 * A_DIV)) / A_DIV;
    reg_named_color(name,
        f * spectrumS[set1][0] + (1.F - f) * spectrumS[set1 + 1][0],
        f * spectrumS[set1][1] + (1.F - f) * spectrumS[set1 + 1][1],
        f * spectrumS[set1][2] + (1.F - f) * spectrumS[set1 + 1][2]);
  }

  /* offset & reversed full spectrum (r000-r999) */

  strcpy(name, "r000");
  for(a = 0; a < 1000; a = a + 1) {
    set1 = (int) (a / A_DIV);
    /* sprintf(color->Name,"r%03d",a); */
    name[3] = (a % 10) + '0';
    name[2] = ((a % 100) / 10) + '0';
    name[1] = ((a % 1000) / 100) + '0';
    f = 1.0F - (a - (set1 * A_DIV)) / A_DIV;
    reg_named_color(name,
        f * spectrumR[set1][0] + (1.F - f) * spectrumR[set1 + 1][0],
        f * spectrumR[set1][1] + (1.F - f) * spectrumR[set1 + 1][1],
        f * spectrumR[set1][2] + (1.F - f) * spectrumR[set1 + 1][2]);
  }

  /* complementary spectra (c000-c999) */

  strcpy(name, "c000");
  for(a = 0; a < 1000; a = a + 1) {
    set1 = (int) (a / A_DIV);
    /*     sprintf(color->Name,"c%03d",a); */
    name[3] = (a % 10) + '0';
    name[2] = ((a % 100) / 10) + '0';
    name[1] = ((a % 1000) / 100) + '0';
    f = 1.0F - (a - (set1 * A_DIV)) / A_DIV;
    reg_named_color(name,
        f * spectrumC[set1][0] + (1.F - f) * spectrumC[set1 + 1][0],
        f * spectrumC[set1][1] + (1.F - f) * spectrumC[set1 + 1][1],
        f * spectrumC[set1][2] + (1.F - f) * spectrumC[set1 + 1][2]);
  }

#define W_DIV 41.666666667F

  /* complementary spectra separated by white (w000-w999) */

  strcpy(name, "w000");
  for(a = 0; a < 1000; a = a + 1) {
    set1 = (int) (a / W_DIV);
    /* sprintf(color->Name,"w%03d",a); */
    name[3] = (a % 10) + '0';
    name[2] = ((a % 100) / 10) + '0';
    name[1] = ((a % 1000) / 100) + '0';
    f = 1.0F - (a - (set1 * W_DIV)) / W_DIV;
    reg_named_color(name,
        f * spectrumW[set1][0] + (1.F - f) * spectrumW[set1 + 1][0],
        f * spectrumW[set1][1] + (1.F - f) * spectrumW[set1 + 1][1],
        f * spectrumW[set1][2] + (1.F - f) * spectrumW[set1 + 1][2]);
  }

  reg_named_color("density", 0.1F, 0.1F, 0.6F);

  strcpy(name, "gray00");       /* american */
  for(a = 0; a < 100; a = a + 1) {
    name[5] = (a % 10) + '0';
    name[4] = ((a % 100) / 10) + '0';
    /* sprintf(color->Name,"gray%02d",a); */
    reg_named_color(name, a / 99.F, a / 99.F, a / 99.F);
  }

  /* original full spectrum, with extra blue and red at the ends (o000-o999) */

#define B_DIV 35.7143F

  strcpy(name, "o000");
  for(a = 0; a < 1000; a = a + 1) {
    set1 = (int) (a / B_DIV);
    name[3] = (a % 10) + '0';
    name[2] = ((a % 100) / 10) + '0';
    name[1] = ((a % 1000) / 100) + '0';
    /* sprintf(color->Name,"o%03d",a); */
    f = 1.0F - (a - (set1 * B_DIV)) / B_DIV;
    reg_named_color(name,
        f * spectrumO[set1][0] + (1.F - f) * spectrumO[set1 + 1][0],
        f * spectrumO[set1][1] + (1.F - f) * spectrumO[set1 + 1][1],
        f * spectrumO[set1][2] + (1.F - f) * spectrumO[set1 + 1][2]);
  }

  reg_named_color("paleyellow", 1.F, 1.F, 0.5F);
  reg_named_color("aquamarine", 0.5F, 1.F, 1.F);
  reg_named_color("deepsalmon", 1.F, 0.5F, 0.5F);
  reg_named_color("palegreen", 0.65F, 0.9F, 0.65F);
  reg_named_color("deepolive", 0.6F, 0.6F, 0.1F);
  reg_named_color("deeppurple", 0.6F, 0.1F, 0.6F);
  reg_named_color("deepteal", 0.1F, 0.6F, 0.6F);
  reg_named_color("lightblue", 0.75F, 0.75F, 1.F);
  reg_named_color("lightorange", 1.F, 0.8F, 0.5F);
  reg_named_color("palecyan", 0.8F, 1.F, 1.F);
  reg_named_color("lightteal", 0.4F, 0.7F, 0.7F);
  reg_named_color("splitpea", 0.52F, 0.75F, 0.F);
  reg_named_color("raspberry", 0.7F, 0.3F, 0.4F);
  reg_named_color("sand", 0.72F, 0.55F, 0.3F);
  reg_named_color("smudge", 0.55F, 0.7F, 0.4F);
  reg_named_color("violetpurple", 0.55F, 0.25F, 0.6F);
  reg_named_color("dirtyviolet", 0.7F, 0.5F, 0.5F);

  // was deepsalmon (duplicated name!)
  reg_named_color("_deepsalmon", 1.F, 0.42F, 0.42F);

  reg_named_color("lightpink", 1.F, 0.75F, 0.87F);
  reg_named_color("greencyan", 0.25F, 1.F, 0.75F);
  reg_named_color("limon", 0.75F, 1.F, 0.25F);
  reg_named_color("skyblue", 0.2F, 0.5F, 0.8F);
  reg_named_color("bluewhite", 0.85F, 0.85F, 1.F);
  reg_named_color("warmpink", 0.85F, 0.2F, 0.5F);
  reg_named_color("darksalmon", 0.73F, 0.55F, 0.52F);
  reg_named_color("helium", 0.850980392F, 1.F, 1.F);
  reg_named_color("lithium", 0.8F, 0.501960784F, 1.F);
  reg_named_color("beryllium", 0.760784314F, 1.F, 0.F);
  reg_named_color("boron", 1.F, 0.709803922F, 0.709803922F);
  reg_named_color("fluorine", 0.701960784F, 1.F, 1.F);
  reg_named_color("neon", 0.701960784F, 0.890196078F, 0.960784314F);
  reg_named_color("sodium", 0.670588235F, 0.360784314F, 0.949019608F);
  reg_named_color("magnesium", 0.541176471F, 1.F, 0.F);
  reg_named_color("aluminum", 0.749019608F, 0.650980392F, 0.650980392F);
  reg_named_color("silicon", 0.941176471F, 0.784313725F, 0.62745098F);
  reg_named_color("phosphorus", 1.F, 0.501960784F, 0.F);
  reg_named_color("chlorine", 0.121568627F, 0.941176471F, 0.121568627F);
  reg_named_color("argon", 0.501960784F, 0.819607843F, 0.890196078F);
  reg_named_color("potassium", 0.560784314F, 0.250980392F, 0.831372549F);
  reg_named_color("calcium", 0.239215686F, 1.F, 0.F);
  reg_named_color("scandium", 0.901960784F, 0.901960784F, 0.901960784F);
  reg_named_color("titanium", 0.749019608F, 0.760784314F, 0.780392157F);
  reg_named_color("vanadium", 0.650980392F, 0.650980392F, 0.670588235F);
  reg_named_color("chromium", 0.541176471F, 0.6F, 0.780392157F);
  reg_named_color("manganese", 0.611764706F, 0.478431373F, 0.780392157F);
  reg_named_color("iron", 0.878431373F, 0.4F, 0.2F);
  reg_named_color("cobalt", 0.941176471F, 0.564705882F, 0.62745098F);
  reg_named_color("nickel", 0.31372549F, 0.815686275F, 0.31372549F);
  reg_named_color("copper", 0.784313725F, 0.501960784F, 0.2F);
  reg_named_color("zinc", 0.490196078F, 0.501960784F, 0.690196078F);
  reg_named_color("gallium", 0.760784314F, 0.560784314F, 0.560784314F);
  reg_named_color("germanium", 0.4F, 0.560784314F, 0.560784314F);
  reg_named_color("arsenic", 0.741176471F, 0.501960784F, 0.890196078F);
  reg_named_color("selenium", 1.F, 0.631372549F, 0.F);
  reg_named_color("bromine", 0.650980392F, 0.160784314F, 0.160784314F);
  reg_named_color("krypton", 0.360784314F, 0.721568627F, 0.819607843F);
  reg_named_color("rubidium", 0.439215686F, 0.180392157F, 0.690196078F);
  reg_named_color("strontium", 0.F, 1.F, 0.F);
  reg_named_color("yttrium", 0.580392157F, 1.F, 1.F);
  reg_named_color("zirconium", 0.580392157F, 0.878431373F, 0.878431373F);
  reg_named_color("niobium", 0.450980392F, 0.760784314F, 0.788235294F);
  reg_named_color("molybdenum", 0.329411765F, 0.709803922F, 0.709803922F);
  reg_named_color("technetium", 0.231372549F, 0.619607843F, 0.619607843F);
  reg_named_color("ruthenium", 0.141176471F, 0.560784314F, 0.560784314F);
  reg_named_color("rhodium", 0.039215686F, 0.490196078F, 0.549019608F);
  reg_named_color("palladium", 0.F, 0.411764706F, 0.521568627F);
  reg_named_color("silver", 0.752941176F, 0.752941176F, 0.752941176F);
  reg_named_color("cadmium", 1.F, 0.850980392F, 0.560784314F);
  reg_named_color("indium", 0.650980392F, 0.458823529F, 0.450980392F);
  reg_named_color("tin", 0.4F, 0.501960784F, 0.501960784F);
  reg_named_color("antimony", 0.619607843F, 0.388235294F, 0.709803922F);
  reg_named_color("tellurium", 0.831372549F, 0.478431373F, 0.F);
  reg_named_color("iodine", 0.580392157F, 0.F, 0.580392157F);
  reg_named_color("xenon", 0.258823529F, 0.619607843F, 0.690196078F);
  reg_named_color("cesium", 0.341176471F, 0.090196078F, 0.560784314F);
  reg_named_color("barium", 0.F, 0.788235294F, 0.F);
  reg_named_color("lanthanum", 0.439215686F, 0.831372549F, 1.F);
  reg_named_color("cerium", 1.F, 1.F, 0.780392157F);
  reg_named_color("praseodymium", 0.850980392F, 1.F, 0.780392157F);
  reg_named_color("neodymium", 0.780392157F, 1.F, 0.780392157F);
  reg_named_color("promethium", 0.639215686F, 1.F, 0.780392157F);
  reg_named_color("samarium", 0.560784314F, 1.F, 0.780392157F);
  reg_named_color("europium", 0.380392157F, 1.F, 0.780392157F);
  reg_named_color("gadolinium", 0.270588235F, 1.F, 0.780392157F);
  reg_named_color("terbium", 0.188235294F, 1.F, 0.780392157F);
  reg_named_color("dysprosium", 0.121568627F, 1.F, 0.780392157F);
  reg_named_color("holmium", 0.F, 1.F, 0.611764706F);
  reg_named_color("erbium", 0.F, 0.901960784F, 0.458823529F);
  reg_named_color("thulium", 0.F, 0.831372549F, 0.321568627F);
  reg_named_color("ytterbium", 0.F, 0.749019608F, 0.219607843F);
  reg_named_color("lutetium", 0.F, 0.670588235F, 0.141176471F);
  reg_named_color("hafnium", 0.301960784F, 0.760784314F, 1.F);
  reg_named_color("tantalum", 0.301960784F, 0.650980392F, 1.F);
  reg_named_color("tungsten", 0.129411765F, 0.580392157F, 0.839215686F);
  reg_named_color("rhenium", 0.149019608F, 0.490196078F, 0.670588235F);
  reg_named_color("osmium", 0.149019608F, 0.4F, 0.588235294F);
  reg_named_color("iridium", 0.090196078F, 0.329411765F, 0.529411765F);
  reg_named_color("platinum", 0.815686275F, 0.815686275F, 0.878431373F);
  reg_named_color("gold", 1.F, 0.819607843F, 0.137254902F);
  reg_named_color("mercury", 0.721568627F, 0.721568627F, 0.815686275F);
  reg_named_color("thallium", 0.650980392F, 0.329411765F, 0.301960784F);
  reg_named_color("lead", 0.341176471F, 0.349019608F, 0.380392157F);
  reg_named_color("bismuth", 0.619607843F, 0.309803922F, 0.709803922F);
  reg_named_color("polonium", 0.670588235F, 0.360784314F, 0.F);
  reg_named_color("astatine", 0.458823529F, 0.309803922F, 0.270588235F);
  reg_named_color("radon", 0.258823529F, 0.509803922F, 0.588235294F);
  reg_named_color("francium", 0.258823529F, 0.F, 0.4F);
  reg_named_color("radium", 0.F, 0.490196078F, 0.F);
  reg_named_color("actinium", 0.439215686F, 0.670588235F, 0.980392157F);
  reg_named_color("thorium", 0.F, 0.729411765F, 1.F);
  reg_named_color("protactinium", 0.F, 0.631372549F, 1.F);
  reg_named_color("uranium", 0.F, 0.560784314F, 1.F);
  reg_named_color("neptunium", 0.F, 0.501960784F, 1.F);
  reg_named_color("plutonium", 0.F, 0.419607843F, 1.F);
  reg_named_color("americium", 0.329411765F, 0.360784314F, 0.949019608F);
  reg_named_color("curium", 0.470588235F, 0.360784314F, 0.890196078F);
  reg_named_color("berkelium", 0.541176471F, 0.309803922F, 0.890196078F);
  reg_named_color("californium", 0.631372549F, 0.211764706F, 0.831372549F);
  reg_named_color("einsteinium", 0.701960784F, 0.121568627F, 0.831372549F);
  reg_named_color("fermium", 0.701960784F, 0.121568627F, 0.729411765F);
  reg_named_color("mendelevium", 0.701960784F, 0.050980392F, 0.650980392F);
  reg_named_color("nobelium", 0.741176471F, 0.050980392F, 0.529411765F);
  reg_named_color("lawrencium", 0.780392157F, 0.F, 0.4F);
  reg_named_color("rutherfordium", 0.8F, 0.F, 0.349019608F);
  reg_named_color("dubnium", 0.819607843F, 0.F, 0.309803922F);
  reg_named_color("seaborgium", 0.850980392F, 0.F, 0.270588235F);
  reg_named_color("bohrium", 0.878431373F, 0.F, 0.219607843F);
  reg_named_color("hassium", 0.901960784F, 0.F, 0.180392157F);
  reg_named_color("meitnerium", 0.921568627F, 0.F, 0.149019608F);
  reg_named_color("deuterium", 0.9F, 0.9F, 0.9F);
  reg_named_color("lonepair", 0.5F, 0.5F, 0.5F);
  reg_named_color("pseudoatom", 0.9F, 0.9F, 0.9F);
}

int ColorTableLoad(PyMOLGlobals * G, const char *fname, float gamma, int quiet)
{
  CColor *I = G->Color;
  int ok = true;

  I->Gamma = gamma;
  if(!fname[0]) {
    ColorUpdateFromLut(G, -1);
  } else {
    int width = 512, height = 512;
    if(!strcmp(fname, "rgb")) {
      if(!I->ColorTable.empty()) {
        I->ColorTable.clear();
        PRINTFB(G, FB_Color, FB_Actions)
          " Color: purged table; restoring RGB colors.\n" ENDFB(G);
      }
      ColorUpdateFromLut(G, -1);
    } else if(!strcmp(fname, "greyscale")) {

      int x, y;
      unsigned int r = 0, g = 0, b = 0;
      unsigned int *pixel, mask, *p;
      unsigned int rc;

      if(I->BigEndian)
        mask = 0x000000FF;
      else
        mask = 0xFF000000;

      I->ColorTable.resize(512 * 512);

      p = I->ColorTable.data();
      for(x = 0; x < width; x++)
        for(y = 0; y < height; y++)
          *(p++) = mask;

      for(y = 0; y < height; y++)
        for(x = 0; x < width; x++) {
          rc = (r + g + b)/3;

          pixel = I->ColorTable.data() + ((width) * y) + x;
          if(I->BigEndian) {
            *(pixel) = mask | (rc << 24) | (rc << 16) | (rc << 8);
          } else {
            *(pixel) = mask | (rc << 16) | (rc << 8) | rc;
          }
          b = b + 4;
          if(!(0xFF & b)) {
            b = 0;
            g = g + 4;
            if(!(0xFF & g)) {
              g = 0;
              r = r + 4;
            }
          }
        }

      if(!quiet) {
        PRINTFB(G, FB_Color, FB_Actions)
          " Color: defined table '%s'.\n", fname ENDFB(G);
      }

      ColorUpdateFromLut(G, -1);
      ExecutiveInvalidateRep(G, cKeywordAll, cRepAll, cRepInvColor);
      SceneChanged(G);


    } else if(!strcmp(fname, "pymol")) {

      int x, y;
      unsigned int r = 0, g = 0, b = 0;
      unsigned int *pixel, mask, *p;
      unsigned int rc, bc, gc;
      unsigned int gf, bf, rf;

      float green_max = 0.75F;
      float red_max = 0.95F;
      float blue_max = 0.97F;

      float min_factor = 0.15F;

      red_max = SettingGetGlobal_f(G, cSetting_pymol_space_max_red);
      green_max = SettingGetGlobal_f(G, cSetting_pymol_space_max_green);
      blue_max = SettingGetGlobal_f(G, cSetting_pymol_space_max_blue);
      min_factor = SettingGetGlobal_f(G, cSetting_pymol_space_min_factor);

      if(I->BigEndian)
        mask = 0x000000FF;
      else
        mask = 0xFF000000;

      I->ColorTable.resize(512 * 512);

      p = I->ColorTable.data();
      for(x = 0; x < width; x++)
        for(y = 0; y < height; y++)
          *(p++) = mask;

      for(y = 0; y < height; y++)
        for(x = 0; x < width; x++) {
          rc = r;
          gc = g;
          bc = b;

          if((r >= g) && (r >= b)) {
            if(rc > 255 * red_max) {
              rc = (unsigned int) (red_max * 255);
              bc = bc * rc / r;
              gc = gc * rc / r;
            }
          } else if((g >= b) && (g >= r)) {
            if(gc > 255 * green_max) {
              gc = (unsigned int) (green_max * 255);
              bc = bc * gc / g;
              rc = rc * gc / g;
            }
          } else if((b >= g) && (b >= r)) {
            if(bc > 255 * blue_max) {
              bc = (unsigned int) (blue_max * 255);
              gc = gc * bc / b;
              rc = rc * bc / b;
            }
          }

          rf = (int) (min_factor * rc + 0.49999F);
          gf = (int) (min_factor * gc + 0.49999F);
          bf = (int) (min_factor * bc + 0.49999F);

          if(rc < gf)
            rc = (int) gf;
          if(bc < gf)
            bc = (int) gf;

          if(rc < bf)
            rc = (int) bf;
          if(gc < bf)
            gc = (int) bf;

          if(gc < rf)
            gc = (int) rf;
          if(bc < rf)
            bc = (int) rf;

          if(rc > 255)
            rc = 255;
          if(bc > 255)
            bc = 255;
          if(gc > 255)
            gc = 255;

          pixel = I->ColorTable.data() + ((width) * y) + x;
          if(I->BigEndian) {
            *(pixel) = mask | (rc << 24) | (gc << 16) | (bc << 8);
          } else {
            *(pixel) = mask | (bc << 16) | (gc << 8) | rc;
          }
          b = b + 4;
          if(!(0xFF & b)) {
            b = 0;
            g = g + 4;
            if(!(0xFF & g)) {
              g = 0;
              r = r + 4;
            }
          }
        }

      if(!quiet) {
        PRINTFB(G, FB_Color, FB_Actions)
          " Color: defined table '%s'.\n", fname ENDFB(G);
      }

      ColorUpdateFromLut(G, -1);
      ExecutiveInvalidateRep(G, cKeywordAll, cRepAll, cRepInvColor);
      SceneChanged(G);

    } else {
      if(strlen(fname)) {

        auto image = MyPNGRead(fname);
        if(image) {
          std::tie(width, height) = image->getSize();
          if((width == 512) && (height == 512)) {
            auto imageSize = width * height;
            I->ColorTable.resize(imageSize);
            std::copy(image->pixels(), image->pixels() + imageSize, I->ColorTable.data());

            if(!quiet) {
              PRINTFB(G, FB_Color, FB_Actions)
                " Color: loaded table '%s'.\n", fname ENDFB(G);
            }

            ColorUpdateFromLut(G, -1);

          } else {
            PRINTFB(G, FB_Color, FB_Errors)
              " ColorTableLoad-Error: invalid dimensions w x h  = %d x %d; should be 512 x 512.\n",
              width, height ENDFB(G);

            ok = false;
          }
        } else {
          PRINTFB(G, FB_Color, FB_Errors)
            " ColorTableLoad-Error: unable to load '%s'.\n", fname ENDFB(G);
          ok = false;
        }
      } else {
        PRINTFB(G, FB_Color, FB_Actions)
          " Color: purged table; colors unchanged.\n" ENDFB(G);
        I->ColorTable.clear();
      }
    }
  }
  if(ok) {
    ExecutiveInvalidateRep(G, cKeywordAll, cRepAll, cRepInvColor);
    SceneChanged(G);
  }
  return (ok);
}

static void lookup_color(CColor * I, const float *in, float *out, int big_endian)
{
  const float _1 = 1.0F;
  unsigned int *table = I->ColorTable.data();
  if(table) {
    unsigned int r, g, b, rr, gr, br;
    unsigned int ra, ga, ba;
    unsigned int rc[2][2][2], gc[2][2][2], bc[2][2][2];
    unsigned int *entry;
    int x, y, z;
    float fr, fg, fb, frm1x, fgm1, fbm1, rct, gct, bct;
    const float _2 = 2.0F, _0 = 0.0F, _05 = 0.5F, _04999 = 0.4999F;
    const float inv255 = 1.0F / 255.0F;

    r = ((int) (255 * in[0] + _05)) & 0xFF;
    g = ((int) (255 * in[1] + _05)) & 0xFF;
    b = ((int) (255 * in[2] + _05)) & 0xFF;

    rr = r & 0x3;
    gr = g & 0x3;
    br = b & 0x3;

    r = (r >> 2);
    g = (g >> 2);
    b = (b >> 2);

    /* now for a crude little trilinear */

    for(x = 0; x < 2; x++) {
      ra = r + x;
      if(ra > 63)
        ra = 63;
      for(y = 0; y < 2; y++) {
        ga = g + y;
        if(ga > 63)
          ga = 63;
        for(z = 0; z < 2; z++) {
          ba = b + z;
          if(ba > 63)
            ba = 63;

          entry = table + (ra << 12) + (ga << 6) + ba;

          if(big_endian) {
            rc[x][y][z] = 0xFF & ((*entry) >> 24);
            gc[x][y][z] = 0xFF & ((*entry) >> 16);
            bc[x][y][z] = 0xFF & ((*entry) >> 8);
          } else {
            rc[x][y][z] = 0xFF & ((*entry));
            gc[x][y][z] = 0xFF & ((*entry) >> 8);
            bc[x][y][z] = 0xFF & ((*entry) >> 16);
          }
        }
      }
    }

    frm1x = rr / 4.0F;
    fgm1 = gr / 4.0F;
    fbm1 = br / 4.0F;

    fr = 1.0F - frm1x;
    fg = 1.0F - fgm1;
    fb = 1.0F - fbm1;

    rct = _04999 +
      (fr * fg * fb * rc[0][0][0]) +
      (frm1x * fg * fb * rc[1][0][0]) +
      (fr * fgm1 * fb * rc[0][1][0]) +
      (fr * fg * fbm1 * rc[0][0][1]) +
      (frm1x * fgm1 * fb * rc[1][1][0]) +
      (fr * fgm1 * fbm1 * rc[0][1][1]) +
      (frm1x * fg * fbm1 * rc[1][0][1]) + (frm1x * fgm1 * fbm1 * rc[1][1][1]);

    gct = _04999 +
      (fr * fg * fb * gc[0][0][0]) +
      (frm1x * fg * fb * gc[1][0][0]) +
      (fr * fgm1 * fb * gc[0][1][0]) +
      (fr * fg * fbm1 * gc[0][0][1]) +
      (frm1x * fgm1 * fb * gc[1][1][0]) +
      (fr * fgm1 * fbm1 * gc[0][1][1]) +
      (frm1x * fg * fbm1 * gc[1][0][1]) + (frm1x * fgm1 * fbm1 * gc[1][1][1]);

    bct = _04999 +
      (fr * fg * fb * bc[0][0][0]) +
      (frm1x * fg * fb * bc[1][0][0]) +
      (fr * fgm1 * fb * bc[0][1][0]) +
      (fr * fg * fbm1 * bc[0][0][1]) +
      (frm1x * fgm1 * fb * bc[1][1][0]) +
      (fr * fgm1 * fbm1 * bc[0][1][1]) +
      (frm1x * fg * fbm1 * bc[1][0][1]) + (frm1x * fgm1 * fbm1 * bc[1][1][1]);

    if(r >= 63)
      rct += rr;
    if(g >= 63)
      gct += gr;
    if(b >= 63)
      bct += br;

    if(rct <= _2)
      rct = _0;                 /* make sure black is black */
    if(gct <= _2)
      gct = _0;
    if(bct <= _2)
      bct = _0;

    out[0] = rct * inv255;
    out[1] = gct * inv255;
    out[2] = bct * inv255;
  } else {
    out[0] = in[0];
    out[1] = in[1];
    out[2] = in[2];
  }

  if((I->Gamma != 1.0F) && (I->Gamma > R_SMALL4)) {
    float inv_gamma = 1.0F / I->Gamma;
    float inp = (out[0] + out[1] + out[2]) * (1 / 3.0F);
    if(inp >= R_SMALL4) {
      float sig = (float) (pow(inp, inv_gamma)) / inp;
      out[0] *= sig;
      out[1] *= sig;
      out[2] *= sig;
    }
  }

  if(out[0] > _1)
    out[0] = _1;
  if(out[1] > _1)
    out[1] = _1;
  if(out[2] > _1)
    out[2] = _1;

}


/*========================================================================*/
void ColorUpdateFromLut(PyMOLGlobals * G, int index)
{
  int i;
  int once = false;
  CColor *I = G->Color;
  float *color, *new_color;

  I->LUTActive = (!I->ColorTable.empty() || (I->Gamma != 1.0F));

  i = index;
  if(index >= 0) {
    once = true;
  }
  for(i = 0; i < I->Color.size(); i++) {
    if(!once)
      index = i;

    if(index < I->Color.size()) {
      if(!I->LUTActive) {
        I->Color[index].LutColorFlag = false;
      } else if(!I->Color[index].Fixed) {
        color = I->Color[index].Color;
        new_color = I->Color[index].LutColor;
        lookup_color(I, color, new_color, I->BigEndian);

        PRINTFD(G, FB_Color)
          "%5.3f %5.3f %5.3f -> %5.3f %5.3f %5.3f\n",
          color[0], color[1], color[2], new_color[0], new_color[1], new_color[2]
          ENDFD;

        I->Color[index].LutColorFlag = true;
      }
    }

    if(once)
      break;
  }
}


/*========================================================================*/
int ColorLookupColor(PyMOLGlobals * G, float *color)
{
  CColor *I = G->Color;
  if(I->LUTActive) {
    lookup_color(I, color, color, I->BigEndian);
    return true;
  } else {
    return false;
  }
}


/*========================================================================*/
int ColorInit(PyMOLGlobals * G)
{
  CColor *I = nullptr;

  if ((G->Color = new CColor())) {
    I = G->Color;
    unsigned int test;
    unsigned char *testPtr;

    test = 0xFF000000;
    testPtr = (unsigned char *) &test;
    I->BigEndian = (*testPtr) & 0x01;

    ColorReset(G);              /* will alloc I->Idx and I->Lex */
    return 1;
  } else {
    return 0;
  }
}

void ColorUpdateFront(PyMOLGlobals * G, const float *back)
{
  CColor *I = G->Color;
  copy3f(back, I->Back);
  I->Front[0] = 1.0F - back[0];
  I->Front[1] = 1.0F - back[1];
  I->Front[2] = 1.0F - back[2];
  if(diff3f(I->Front, back) < 0.5F)
    zero3f(I->Front);
}

void ColorUpdateFrontFromSettings(PyMOLGlobals * G){
  int bg_gradient = SettingGet_b(G, nullptr, nullptr, cSetting_bg_gradient);
  const char * bg_image_filename = SettingGet_s(G, nullptr, nullptr, cSetting_bg_image_filename);
  short bg_image = bg_image_filename && bg_image_filename[0];
  
  if (!bg_gradient){
    if (!bg_image && !OrthoBackgroundDataIsSet(*G->Ortho)){
      const float *v = ColorGet(G, SettingGet_color(G, nullptr, nullptr, cSetting_bg_rgb));
      ColorUpdateFront(G, v);
    } else {
      float v[] = { 0.f, 0.f, 0.f };
      ColorUpdateFront(G, v);
    }
  } else {
    float vv[3];
    const float *v = ColorGet(G, SettingGet_color(G, nullptr, nullptr, cSetting_bg_rgb_bottom));
    const float *vb = ColorGet(G, SettingGet_color(G, nullptr, nullptr, cSetting_bg_rgb_top));
    average3f(v, vb, vv);
    ColorUpdateFront(G, vv);    
  }
}


/*========================================================================*/
const float *ColorGetSpecial(PyMOLGlobals * G, int index)
{
  if(index >= 0)
    return ColorGet(G, index);
  else {
    CColor *I = G->Color;
    I->RGBColor[0] = (float) index;
    I->RGBColor[1] = -1.0F;
    I->RGBColor[2] = -1.0F;
    return I->RGBColor;
  }
}

const float *ColorGet(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  const float *ptr;
  if((index >= 0) && (index < I->Color.size())) {
    if(I->Color[index].LutColorFlag && SettingGetGlobal_b(G, cSetting_clamp_colors))
      ptr = I->Color[index].LutColor;
    else
      ptr = I->Color[index].Color;
    return (ptr);
  } else if((index & cColor_TRGB_Mask) == cColor_TRGB_Bits) {   /* a 24-bit RGB color */
    I->RGBColor[0] = ((index & 0x00FF0000) >> 16) / 255.0F;
    I->RGBColor[1] = ((index & 0x0000FF00) >> 8) / 255.0F;
    I->RGBColor[2] = ((index & 0x000000FF)) / 255.0F;
    if(I->LUTActive)
      lookup_color(I, I->RGBColor, I->RGBColor, I->BigEndian);
    return I->RGBColor;
  } else if(index == cColorFront) {
    return I->Front;
  } else if(index == cColorBack) {
    return I->Back;
  } else {
    /* invalid color id, then simply return white */
    return (I->Color[0].Color);
  }
}

const float *ColorGetRaw(PyMOLGlobals * G, int index)
{
  CColor *I = G->Color;
  const float *ptr;
  if((index >= 0) && (index < I->Color.size())) {
    ptr = I->Color[index].Color;
    return (ptr);
  } else if((index & cColor_TRGB_Mask) == cColor_TRGB_Bits) {   /* a 24-bit RGB color */
    I->RGBColor[0] = ((index & 0x00FF0000) >> 16) / 255.0F;
    I->RGBColor[1] = ((index & 0x0000FF00) >> 8) / 255.0F;
    I->RGBColor[2] = ((index & 0x000000FF)) / 255.0F;
    return I->RGBColor;
  } else {
    /* invalid color id, then simply return white */
    return (I->Color[0].Color);
  }
}

int ColorGetEncoded(PyMOLGlobals * G, int index, float *color)
{
  CColor *I = G->Color;
  float *ptr;
  if((index >= 0) && (index < I->Color.size())) {
    if(I->Color[index].LutColorFlag && SettingGetGlobal_b(G, cSetting_clamp_colors))
      ptr = I->Color[index].LutColor;
    else
      ptr = I->Color[index].Color;
    copy3f(ptr, color);
  } else if((index & cColor_TRGB_Mask) == cColor_TRGB_Bits) {   /* a 24-bit RGB color */
    float rgb_color[3];
    rgb_color[0] = ((index & 0x00FF0000) >> 16) / 255.0F;
    rgb_color[1] = ((index & 0x0000FF00) >> 8) / 255.0F;
    rgb_color[2] = ((index & 0x000000FF)) / 255.0F;
    if(I->LUTActive)
      lookup_color(I, rgb_color, rgb_color, I->BigEndian);
    copy3f(rgb_color, color);
  } else if(index <= cColorExtCutoff) {
    color[0] = (float) index;
    color[1] = 0.0F;
    color[2] = 0.0F;
  } else if(index == cColorFront) {
    copy3f(I->Front, color);
  } else if(index == cColorBack) {
    copy3f(I->Back, color);
  } else {
    color[0] = 1.0F;
    color[1] = 1.0F;
    color[2] = 1.0F;
    /* invalid color id, then simply return white */
    return 0;
  }
  return 1;
}

int Color3fToInt(PyMOLGlobals * G, const float *rgb){
  unsigned int rc, gc, bc;
  rc = pymol_roundf(rgb[0] * 255.);
  gc = pymol_roundf(rgb[1] * 255.);
  bc = pymol_roundf(rgb[2] * 255.);
  return ( ( cColor_TRGB_Bits & 0xFF000000) | 
	   ( ( rc << 16 ) & 0x00FF0000) |
	   ( ( gc << 8 ) & 0x0000FF00) |
	   ( ( bc & 0x000000FF ) ) );
}

void ColorRenameExt(
    PyMOLGlobals* G, pymol::zstring_view oldName, pymol::zstring_view newName)
{
  auto I = G->Color;

  // Find color idx
  auto oldNameIt = I->Idx.find(oldName.c_str());
  if (oldNameIt == I->Idx.end()) {
    return;
  }
  auto idx = oldNameIt->second;
  reg_name(I, idx, newName.c_str(), true);
  if (ExecutiveFindObject<ObjectGadgetRamp>(G, newName) == nullptr) {
    return;
  }

  // Find corresponding color ext and provide it the new name
  auto extIt = std::find_if(I->Ext.begin(), I->Ext.end(), [oldName](const ExtRec& rec) {
    return oldName == rec.Name;
  });
  if (extIt == I->Ext.end()) {
    return;
  }
  auto newNameit = I->Idx.find(newName.c_str());
  if (newNameit == I->Idx.end()) {
   return;
  }
  auto& ext = *extIt;
  ext.Name = newNameit->first.c_str();
}
