How can I use Kenneth Moreland's "Diverging Color Maps for Scientific Visualization" in plots?

I've taken the liberty of converting the pseudocode from Moreland's paper into a package. I had to change the numerical values of the RGB->XYZ transformation matrix to account for the fact that Mathematica uses different reference white points for the different color spaces.

Update This function is available in the function repository, and the source code is available on github. Much thanks to J.M., I've changed a few functions around to make them simpler - but I have kept all the color conversion functions because I like them for clarity, and they don't slow it down compared to the built-in ColorConvert function.

BeginPackage["DivergentColorMaps`"]

DivergentColorFunc::usage = "DivergentColorFunc[{r1,g1,b1},{r2,b2,g2}] returns a continuously diverging color map which interpolates between two RGB colors.\n
DivergentColorFunc[color1, color2] takes two color objecst as input and returns a continuously diverging color map."
CoolToWarm::usage = "Cool2Warm[n] gives the cool to warm color map, with n taking values between 0 and 1"
DivergentColorScheme::usage = "DivergentColorScheme[scheme] gives a diverging color map which interpolates between the starting and ending colors in a builtin scheme"
DivergentMaps::usage = "DivergentMaps is list of four divergent color maps used in http://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf . divergentMaps[[1]] is equivalent to Cool2Warm" 

Begin["`Private`"]



(* 
The reference white values and transformation matrix correspond to the 
fact that in Mathematica, the RGB white point uses the D65 standard, 
while the XYZ and LAB color spaces use the D50 white point.  This is 
different than in Moreland's paper or other color conversion websites 
*)

referenceWhite = {96.42, 100.0, 82.49};


transformation = {{0.436075, 0.385065, 0.14308}, 
   {0.222504, 0.716879, 0.0606169},
   {0.0139322, 0.0971045, 0.714173}};

(*Forward Transformations*)

rgb2xyz[r_, g_, b_] := Module[
   {transm, rl, gl, bl},
   {rl, gl, bl} = If[# > .04045,
       ((# + 0.055)/1.055)^2.4,
       #/12.92] & /@ {r, g, b};
   transm = transformation;
   100 transm.{rl, gl, bl}
   ];

xyz2lab[xi_, yi_, zi_] := Module[{f, refx, refy, refz, x, y, z},
   {refx, refy, refz} = referenceWhite;
   f = If[((#) > 0.008856),
      (#^(1/3)),
      (7.787 # + 4/29.)] &;
   {x, y, z} = f /@ ({xi, yi, zi}/{refx, refy, refz});
   {116.0 (y - 4./29), 500.0 (x - y), 200 (y - z)}
   ];

lab2msh[l_, a_, b_] := Module[{m = Norm[{l, a, b}]}, {m, 
   If[m==0, 0, ArcCos[l/m]], Arg[a + b I]}];
rgb2msh[r_, g_, b_] := lab2msh @@ xyz2lab @@ rgb2xyz @@ {r, g, b};

(* Backward Transformations *)

msh2lab[m_, s_, h_] := {m Cos[s], m Sin[s] Cos[h], m Sin[s] Sin[h]};

lab2xyz[l_, a_, b_] := Module[{x, y, z, refx, refy, refz},
   {refx, refy, refz} = referenceWhite;
   y = (l + 16)/116.;
   x = a/500. + y;
   z = y - b/200.;
   {x, y, z} = 
    If[#^3 > 0.008856, #^3, (# - 4./29)/7.787] & /@ {x, y, z};
   {x, y, z} {refx, refy, refz}
   ];

xyz2rgb[x_, y_, z_] := Module[{transm, r, g, b},
   transm = Inverse@transformation;
   {r, g, b} = {x, y, z}/100;
   {r, g, b} = transm.{r, g, b};
   If[# > 0.0031308, 1.055 #^(1/2.4) - 0.055, 12.92 #] & /@ {r, g, b}
   ];

msh2rgb[m_, s_, h_] := xyz2rgb @@ lab2xyz @@ msh2lab @@ {m, s, h};

adjusthue[msat_, ssat_, hsat_, munsat_] := Module[{hspin},
   If[msat >= munsat,
    hsat,
    hspin = ssat Sqrt[munsat^2 - msat^2]/(msat Sin[ssat]);
    If[hsat > -\[Pi]/3,
     hsat + hspin,
     hsat - hspin
     ]
    ]
   ];


interpolatecolor[rgb1_List, rgb2_List, interp_?NumericQ] := 
  Module[
   {m1, s1, h1, m2, s2, h2, interpvar, mmid, smid, hmid},
   (*If points are saturated and distinct, 
   place white in the middle *)
   {m1, s1, h1} = 
    rgb2msh @@ rgb1;
   {m2, s2, h2} = rgb2msh @@ rgb2;
   interpvar = interp;
   If[s1 > 0.05 && s2 > 0.05 && Abs[h1 - h2] > Pi/3,
    mmid = Max@{m1, m2, 88.};
    If[interp < 1/2,
     {m2, s2, h2, interpvar} = {mmid, 0, 0, 2 interp};,
     {m1, s1, h1, interpvar} = {mmid, 0, 0, 2 interp - 1};
     ];
    ];
   (* Adjust hue of unsaturated colors *)

   Which[s1 < 0.05 && s2 > 0.05,
    h1 = adjusthue[m2, s2, h2, m1];,
    s2 < 0.05 && s1 > 0.05,
    h2 = adjusthue[m1, s1, h1, m2];
    ];
   {mmid, smid, hmid} = (1 - interpvar) {m1, s1, h1} + 
     interpvar {m2, s2, h2};
   msh2rgb @@ {mmid, smid, hmid}

   ];

DivergentColorFunc[rgb1_, rgb2_] := 
  With[{interp = RGBColor @@@ Chop @ (interpolatecolor[rgb1, rgb2, #] &/@ Range[0,1,.05])},
      Blend[interp, #] & ];


DivergentColorFunc[col1_?ColorQ, col2_?ColorQ] := DivergentColorFunc @@ List @@@ (ColorConvert[{col1, col2}, RGBColor]) ;

DivergentColorScheme[scheme_String] := 
  DivergentColorFunc @@ ColorData[scheme] /@ {0, 1};




CoolToWarm = DivergentColorFunc[{0.23, 0.299, 0.754}, {0.706, 0.016, 0.150}];


DivergentMaps = 
  DivergentColorFunc[#1, #2] & @@@ {{{0.23, 0.299, 0.754}, {0.706, 
      0.016, 0.150}},
    {{0.436, 0.308, 0.631}, {0.759, 0.334, 0.046}}, {{0.085, 0.532, 
      0.201}, {0.436, 0.308, 0.631}}, {{0.217, 0.525, 0.910}, {0.677, 
      0.492, 0.093}}, {{0.085, 0.532, 0.201}, {0.758, 0.214, 0.233}}};

End[]
EndPackage[]

Now I can create a continuous divergent color map simply from two RGB colors,

newcolorfunc = DivergentColorFunc[{0, 0, .5}, {.5, 0, 0}];
showcolorfunction@newcolorfunc

enter image description here

Or, taking inspiration from the way J.M.'s function is defined, you can give color objects as the arguments - in any color space,

newcolorfunc2 = 
  DivergentColorFunc[Darker[XYZColor[1, 0.2, 1]], 
   LUVColor[.16, .5, 1]];
showcolorfunction@newcolorfunc2

enter image description here

I can use the cool2warm color scheme Moreland recommends,

showcolorfunction@CoolToWarm

enter image description here

or the other four examples listed at the bottom of his page

showcolorfunction /@ DivergentMaps[[2 ;;]]

enter image description here

I even have a function that will take a named color scheme, extract the two outer colors, and build a divergent scheme from them.

showcolorfunction /@ (DivergentColorScheme /@ {"RoseColors", "AvocadoColors"})

enter image description here

Just for comparison, here are those color schemes without the divergent function,

showcolorfunction /@ ({"RoseColors", "AvocadoColors"})

enter image description here


Here's my take (using some of the new version 10 functions):

adjusthue[msat_, ssat_, hsat_, munsat_] := hsat +
      (1 - 2 UnitStep[-hsat - π/3]) If[munsat == msat, 0., 
      Sqrt[Max[1, munsat/(msat + $MachineEpsilon)]^2 - 1]/Sinc[ssat]]

l2m = With[{m = Norm[{##}]}, {m, ArcCos[If[m == 0, 0, #1/m]], Arg[#2 + I #3]}] &;
m2l = #1 Prepend[Sin[#2] Through[{Cos, Sin}[#3]], Cos[#2]] &;

SetAttributes[interpolatecolor, Listable]; 
interpolatecolor[col1_?ColorQ, col2_?ColorQ, interp_?NumericQ] := 
     Module[{ m1, s1, h1, m2, s2, h2, mmid, t},

            {m1, s1, h1} = l2m @@ ColorConvert[col1, LABColor];
            {m2, s2, h2} = l2m @@ ColorConvert[col2, LABColor];

            t = interp;
            If[Min[s1, s2] > 0.05 && Abs[h1 - h2] > π/3,
               mmid = Max[m1, m2, 0.88];
               If[t < 1/2,
                  {m2, s2, h2, t} = {mmid, 0., 0., 2 t},
                  {m1, s1, h1, t} = {mmid, 0., 0., 2 t - 1}]];

            Which[s1 < 0.05 && s2 > 0.05, h1 = adjusthue[m2, s2, h2, m1],
                  s2 < 0.05 && s1 > 0.05, h2 = adjusthue[m1, s1, h1, m2]];

            ColorConvert[m2l @@ ({1 - t, t}.{{m1, s1, h1}, {m2, s2, h2}}),
                         LABColor -> RGBColor]]

divergentColorGradient[col1_?ColorQ, col2_?ColorQ, res_Integer: 20] := 
         With[{cl = interpolatecolor[col1, col2, Subdivide[res]]}, Blend[cl, #] &]

(N.B. the previous version of this answer used the built-in coordinate conversion functions, but they were too slow for this application. The handling for Black in adjusthue[] through a tiny perturbation is still hackish IMHO, but at least it's compact.)

Test:

cool2warm = divergentColorGradient[RGBColor[59/255, 76/255, 64/85],
                                   RGBColor[12/17, 4/255, 38/255]];

LinearGradientImage[cool2warm, {300, 30}]

cool2warm gradient