# What is the underlying interpolation in "BrightBands"/"DarkBands" color scheme?

Using the bookmarked conversation mentioned in this answer, I was able to extract the colors (and their parametric positions) of the Mathematica color scheme BrightBands

Cases[ColorData[{"BrightBands", "Reverse"}], {a_?NumericQ,
x_RGBColor} -> {1 - a, x}, inf]


resulting in$$^1$$ fig1.

Notice how the positions are 1/(noOfColors-1) apart. This is true for all other color schemes (in "Gradient") too.

Using linear interpolation between these colors$$^2$$ at those positions, I was able to reproduce the color scheme in Java (a different programming language), all except for "BrightBands" and "DarkBands", that is.

While for every other scheme the interpolation works as intended, it's too continuous for these two banded color schemes:

My reproduction

vs

Mathematica's actual rendering

As is clearly seen, linear interpolation doesn't 'band' like in Mathematica's rendering.

1. How to achieve this?

FAQs

Are you sure its not a coding problem at your end?

Yes. For all other gradients that don't involve bands, I am able to reproduce the Mathematica gradients.

Did you investigate further?

Of course, though I am not sure if these observations are of any help.

1. Let cd=ColorData@"BrightBands". The behavior expected on the basis of a linear interpolation is that on $$[1/11,2/11]$$, the color should change smoothly from pink (second entry of the array shown in fig1 ) to purple (third entry). Instead it switches abruptly near $$\approx 0.15293(1)$$, about 68% percent of the way.
2. The difference between the colors of ColorData@"scheme" and ColorData@{"scheme","Reverse"} is $$0$$ except for the banded color schemes
cls=ClearAll;
lgrid = Grid[#1, Alignment -> Left] &

(*extract color info*)
cls@getCols;
getCols[scheme_String] :=
Cases[ColorData[{scheme, "Reverse"}], {a_?NumericQ,
x_RGBColor} -> {a, x}, inf]

(*produce the difference in colors*)
cls@findDiffs;
findDiffs[scheme_String] := Module[{cd, cdr, cols, colsr, n, diff},
cd = ColorData@scheme;
cdr = ColorData@{scheme, "Reverse"};
colsr = getCols[scheme][[;; , 2]];
n = len@colsr - 1;
cols = cd /@ Range[0, 1, 1/n];
diff = List @@@ colsr - List @@@ cols
]

(*tabulate them*)
{#, findDiffs@# // Flatten // Abs // Total} & /@ names
] // lgrid


resulting in $$\begin{array}{ll} \text{AlpineColors} & 0. \\ \text{Aquamarine} & 0. \\ \text{ArmyColors} & 0. \\ \text{AtlanticColors} & 0. \\ \text{AuroraColors} & 0. \\ \text{AvocadoColors} & 0. \\ \text{BeachColors} & 0. \\ \text{BlueGreenYellow} & 0. \\ \text{BrassTones} & 0. \\ \text{BrightBands} & 2.26287 \\ \text{BrownCyanTones} & 0. \\ \text{CandyColors} & 0. \\ \text{CherryTones} & 0. \\ \text{CMYKColors} & 0. \\ \text{CoffeeTones} & 0. \\ \text{DarkBands} & 2.5635 \\ \text{DarkRainbow} & 0. \\ \text{DarkTerrain} & 0. \\ \text{DeepSeaColors} & 0. \\ \text{FallColors} & 0. \\ \text{FruitPunchColors} & 0. \\ \text{FuchsiaTones} & 0. \\ \text{GrayTones} & 0. \\ \text{GrayYellowTones} & 0. \\ \text{GreenBrownTerrain} & 0. \\ \text{GreenPinkTones} & 0. \\ \text{IslandColors} & 0. \\ \text{LakeColors} & 0. \\ \text{LightTemperatureMap} & 0. \\ \text{LightTerrain} & 0. \\ \text{MintColors} & 0. \\ \text{NeonColors} & 0. \\ \text{Pastel} & 0. \\ \text{PearlColors} & 0. \\ \text{PigeonTones} & 0. \\ \text{PlumColors} & 0. \\ \text{Rainbow} & 0. \\ \text{RedBlueTones} & 0. \\ \text{RedGreenSplit} & 0. \\ \text{RoseColors} & 0. \\ \text{RustTones} & 0. \\ \text{SandyTerrain} & 0. \\ \text{SiennaTones} & 0. \\ \text{SolarColors} & 0. \\ \text{SouthwestColors} & 0. \\ \text{StarryNightColors} & 0. \\ \text{SunsetColors} & 0. \\ \text{TemperatureMap} & 0. \\ \text{ThermometerColors} & 0. \\ \text{ValentineTones} & 0. \\ \text{WatermelonColors} & 0. \\ \end{array}$$

(as on September end '21)

Its a shame that I can't accept multiple answers. All three answers - one each from Domen, N.J.Evans and kglr - helped solved my question, especially the first two.

Domen's answer made me realize that Cases wasn't extracting all the information for the banded color schemes and Mr.Wizard's was the way. He also showed that the interpolation was linear after all, just discontinuous. The DataPaclets' result explicitly showed the position repetition at discontinuity.

N.J.Evans rightly pointed out that this was an expected behavior of Blend and was documented in its 'Possible Issues' section.

kglr summed up the efforts by providing direct one-liners that extracted the relevant info. He also highlighted that the reverse blend isn't using 1-# positions.

Besides, they (Domen and kglr) also established Blend as being the underlying blending mechanism in ColorData.

Appendix $$^1$$ with InputForm

{{0., RGBColor[0.90222, 0.101808, 0.198306]},
{0.09090909090909094, RGBColor[1., 0.602487, 0.750759]},
{0.18181818181818188, RGBColor[0.400595, 0.302083, 1.]},
{0.2727272727272727, RGBColor[0.80061, 0.74902, 0.999603]},
{0.36363636363636365, RGBColor[0.104631, 0.700359, 1.]},
{0.4545454545454546, RGBColor[0.65277, 0.932433, 1.]},
{0.5454545454545454, RGBColor[0.205646, 0.965652, 0.0889754]},
{0.6363636363636364, RGBColor[0.722118, 1., 0.558145]},
{0.7272727272727273, RGBColor[0.972274, 0.938231, 0.192309]},
{0.8181818181818182, RGBColor[1., 0.976715, 0.598215]},
{0.9090909090909092, RGBColor[1., 0.500511, 0.000244144]},
{1., RGBColor[1., 0.749752, 0.501183]}}


$$^2$$ By this we mean that first the parameter space $$[0,1]$$ of the argument of ColorData, say $$t$$, is divided into $$[x_i,x_{i+1}]$$ intervals where $$x_i$$ are the color positions of the colors in that scheme. Then as $$t$$ varies over $$[x_i,x_{i+1}]$$, the color varies over $$(1-\alpha)\text{color}_i+\alpha~ \text{color}_{i+1}$$, where $$\alpha$$ is a linear function of $$t$$ that varies over $$[0,1]$$

It is a linear interpolation between colors, obtained via Blend, the same as for the others. However, it seems to use some undocumented functionality of Blend where the first argument is a string ("BrightBands"):

ColorData["BrightBands"] // InputForm
(* ColorDataFunction["BrightBands", "Gradients", {0, 1}, Blend["BrightBands", #1] & ] *)

Blend["BrightBands", 0]
(* RGBColor[0.90222, 0.101808, 0.198306] *)


Let me convince you that the interpolation is piecewise linear:

bands[x_] := List @@ ColorData["BrightBands"][x]
Plot[{bands[x][[1]], bands[x][[2]], bands[x][[3]]}, {x, 0, 1},
PlotStyle -> {Red, Green, Blue}]


Furthermore, we can use DataPacletsColorDataDumpgetColorSchemeData to see that other gradients have equally spaced color markers while BrightBands has specified positions of the markers (and the two markers coincide):

DataPacletsColorDataDumpgetColorSchemeData["GrayTones"]
(* {{"GrayTones", "gray tones", {}}, {"Gradients"}, 1, {0, 1}, {RGBColor[
0.1, 0.1, 0.1], RGBColor[0.225356, 0.246889, 0.268731], RGBColor[
0.333565, 0.367922, 0.391532], RGBColor[
0.51593, 0.541278, 0.547958], RGBColor[
0.753644, 0.757794, 0.737969], RGBColor[
0.917794, 0.920966, 0.881936]}, ""} *)

DataPacletsColorDataDumpgetColorSchemeData["BrightBands"]
(* {{"BrightBands", "bright bands", {}}, {"Gradients"}, 1, {0,
1}, {{0., RGBColor[0.90222, 0.101808, 0.198306]}, {0.152941,
RGBColor[1., 0.602487, 0.750759]}, {0.152941, RGBColor[
0.400595, 0.302083, 1.]}, {0.321569, RGBColor[
0.80061, 0.74902, 0.999603]}, {0.321569, RGBColor[
0.104631, 0.700359, 1.]}, {0.490196, RGBColor[
0.65277, 0.932433, 1.]}, {0.490196, RGBColor[
0.205646, 0.965652, 0.0889754]}, {0.662745, RGBColor[
0.722118, 1., 0.558145]}, {0.662745, RGBColor[
0.972274, 0.938231, 0.192309]}, {0.831373, RGBColor[
1., 0.976715, 0.598215]}, {0.831373, RGBColor[
1., 0.500511, 0.000244144]}, {1., RGBColor[
1., 0.749752, 0.501183]}}, ""} *)


The resulting Blend function is equivalent to using ColorData["BrightBands"]:

brightBands =
Blend[DataPacletsColorDataDumpgetColorSchemeData[
"BrightBands"][[5]], #] &;

Row[DensityPlot[x, {x, 0, 1}, {y, 0, 1/2}, ColorFunction -> #[[1]],
PlotPoints -> 100, AspectRatio -> 1/2, ImageSize -> 200,
PlotLabel -> #[[2]]] & /@ {{brightBands,
"Blend[...]"}, {ColorData["BrightBands"],
"ColorData[\"BrightBands\"]"}}]


It looks like bright bands uses the 'feature' mentioned in 'possible issues' in the docs for Blend (With multiple colors for the same Subscript[x, i], the first one is used in (Subscript[x, i-1],Subscript[x, i]) interval and the last one in [Subscript[x, i],Subscript[x, i+1]):).

Define:

col = Cases[ColorData[{"BrightBands", "Reverse"}],
x_RGBColor -> x, inf];


then define the following so all the non-boundary colors are applied on overlapping intervals:

col2=Riffle[
Transpose[{Most@#, col[[;; ;; 2]]}],
Transpose[{Rest@#, col[[2 ;; ;; 2]]}]
] &@Range[0, 1, 1/6];

Graphics[
{
Blend[col2, #], Rectangle[{#, 0}, {# + 0.01, 1}],
ColorData["BrightBands"][#], Rectangle[{#, 1}, {# + 0.01, 2}]
} & /@ Range[0, 1, 0.01]
, AspectRatio -> 1/2
]


Gives the right color behavior - though obviously the bands are in slightly different spots for x<1/2, no idea why. The actual coordinates of the band spacing isn't even, and it isn't what you found using Cases either, so where that comes from I don't know. Even using Trace seems to skip a few steps when it gets to the point where Blend["BrightBands",#] is evaluated.

We can get the blending scheme directly using the function DataPacletsColorDataGetBlendArgument:

brightBandsBlendArg = DataPacletsColorDataGetBlendArgument["BrightBands"]


brightBandsRvrsBlendArg =
DataPacletsColorDataGetBlendArgument[{"BrightBands","Reverse"}][[2]]


We can get brightBandsBlendArg from brightBandsRvrsBlendArg using a rule obtained from the first part of DataPacletsColorDataGetBlendArgument[{"BrightBands", "Reverse"}]:

bbReverseToBB = Rule @@@
DataPacletsColorDataGetBlendArgument[{"BrightBands", "Reverse"}][[1]]


Chop[brightBandsRvrsBlendArg /. {x_, r_RGBColor} :> {x /. bbReverseToBB, r}]
== Chop[brightBandsBlendArg]

True


Blend[brightBandsBlendArg, #]& and ColorData["BrightBands"] (similarly,Blend[brightBandsRvrsBlendArg, #]& and ColorData[{"BrightBands", "Reverse"}]) give the same colors:

Graphics[{EdgeForm[{Thin, White}],
{ColorData["BrightBands"]@#, Rectangle[{#, 1.52}, {# + .1, 2}]} & /@
Subdivide[50],
{Blend[brightBandsBlendArg, #], Rectangle[{#, 1.02}, {# + .1, 1.50}]} & /@
Subdivide[50],
{ColorData[{"BrightBands", "Reverse"}]@#, Rectangle[{#, .50}, {# + .1, .98}]} & /@
Subdivide[50],
{Blend[brightBandsRvrsBlendArg, #], Rectangle[{#, 0}, {# + .1, 0.48}]} & /@
Subdivide[50]},
AspectRatio -> 1, Frame -> {{False, True}, {False, False}},
FrameTicks -> {{None, Thread[{{.25, .75, 1.25, 1.75},
Style[#, 16] & /@ {"Blend[brightBandsRvrsBlendArg,#]&",
"ColorData[{\"BrightBands\", \"Reverse\"}]",
"Blend[brightBandsBlendArg,#]&", ColorData[\"BrightBands\"]"}}]},
{None, None}},
ImageSize -> 800]


• Great, thorough answer! How do you find things like DataPacletsColorDataGetBlendArgument? When I try something like Trace it doesn't get deep enough to see what's going on with some of the stuff like Blend["BrightBands"] Sep 22, 2021 at 12:10
• @N.J.Evans, I bumped into it when I tried ??**Blend*
– kglr
Sep 22, 2021 at 12:14
• @N.J.Evans, you can also use Trace[Blend["BrightBands", .3], TraceInternal -> True]
– kglr
Sep 22, 2021 at 12:19
• @kglr hmm.....clearly, reverse colors aren't at 1-#..........DataPaclets'ColorData'GetBlendArgument has a differing output for non-banded color schemes Sep 23, 2021 at 9:59
• @lineage, you are right; the way "Reverse" works for BrightBands and DarkBands` is different from other gradient schemes.
– kglr
Sep 23, 2021 at 16:08