I was given a data set that is essentially an image, however each pixel in the image is represented as a value from -1 to 1 inclusive. I am writing an application that need
Seems like you have hue values of an HSL system and the saturation and lightness are implicit. Search for HSL to RGB conversion on the internet and you will find a lot of explanations, code etc. (Here is one link)
In your particular case, though, let's assume you are defaulting all color saturations to 1 and lightness to 0.5. Here is the formula you can use to get the RGB values:
Imagine for every pixel, you have h
the value you read from your data.
hue = (h+1.0)/2; // This is to make it in range [0, 1]
temp[3] = {hue+1.0/3, hue, hue-1.0/3};
if (temp[0] > 1.0)
temp[0] -= 1.0;
if (temp[2] < 0.0)
temp[2] += 1.0;
float RGB[3];
for (int i = 0; i < 3; ++i)
{
if (temp[i]*6.0 < 1.0)
RGB[i] = 6.0f*temp[i];
else if (temp[i]*2.0 < 1.0)
RGB[i] = 1;
else if (temp[i]*3.0 < 2.0)
RGB[i] = ((2.0/3.0)-temp[i])*6.0f;
else
RGB[i] = 0;
}
And there you have the RGB values in RGB
all in the range [0, 1]. Note that the original conversion is more complex, I simplified it based on values of saturation=1 and lightness=0.5
Why this formula? See this wikipedia entry
The other answers treat the interpolation as a piecewise linear function. This can be simplified by using a clamped triangular basis function for interpolation. We need a clamp function that maps its input to the closed unit interval:
And a basis function for interpolation:
Then the color becomes:
Plotting this from -1 to 1 gives:
Which is the same as provided in this answer. Using an efficient clamp implementation:
double clamp(double v)
{
const double t = v < 0 ? 0 : v;
return t > 1.0 ? 1.0 : t;
}
and ensuring your value t is in [-1, 1], then jet color is simply:
double red = clamp(1.5 - std::abs(2.0 * t - 1.0));
double green = clamp(1.5 - std::abs(2.0 * t));
double blue = clamp(1.5 - std::abs(2.0 * t + 1.0));
As shown in the above link on implementing clamp
, the compiler may optimize out branches. The compiler may also use intrinsics to set the sign bit for std::abs
eliminating another branch.
A similar treatment can be used for the "hot-to-cold" color mapping. In this case the basis and color functions are:
And the hot-to-cold plot for [-1, 1]:
Eliminating explicit branches makes this approach efficient for implementing as an OpenGL shader program. GLSL provides built-in functions for both abs
and clamp
that operate on 3D vectors. Vectorizing the color calculation and preferring built-in functions over branching can provide significant performance gains. Below is an implementation in GLSL that returns the RGB jet color as a vec3
. Note that the basis function was modified such that t must lie in [0,1] rather than the range used in the other examples.
vec3 jet(float t)
{
return clamp(vec3(1.5) - abs(4.0 * vec3(t) + vec3(-3, -2, -1)), vec3(0), vec3(1));
}
This probably isn't exactly the same, but it may be close enough for your needs:
if (-0.75 > value) {
blue = 1.75 + value;
} else if (0.25 > value) {
blue = 0.25 - value;
} else {
blue = 0;
}
if ( -0.5 > value) {
green = 0;
} else if (0.5 > value) {
green = 1 - 2*abs(value);
} else {
green = 0;
}
if ( -0.25 > value) {
red = 0;
} else if (0.75 > value) {
red = 0.25 + value;
} else {
red = 1.75 - value;
}
I'm not really sure why there are so many complex answers to this simple equation. Based on the MatLab JET Hot-to-Cold color map chart and graph plot posted above in Amro's comment (thank you), the logic is very simple to calculate the RGB values using high-speed/basic math.
I use the following function for live-rendering normalized data to display spectrograms and it's incredibly fast and efficient with no complex math outside double precision multiplication and division, simplified by ternary logic chaining. This code is C# but very easily ported to almost any other language (sorry PHP programmers, you're out of luck thanks to abnormal ternary chain order).
public byte[] GetMatlabRgb(double ordinal)
{
byte[] triplet = new byte[3];
triplet[0] = (ordinal < 0.0) ? (byte)0 : (ordinal >= 0.5) ? (byte)255 : (byte)(ordinal / 0.5 * 255);
triplet[1] = (ordinal < -0.5) ? (byte)((ordinal + 1) / 0.5 * 255) : (ordinal > 0.5) ? (byte)(255 - ((ordinal - 0.5) / 0.5 * 255)) : (byte)255;
triplet[2] = (ordinal > 0.0) ? (byte)0 : (ordinal <= -0.5) ? (byte)255 : (byte)(ordinal * -1.0 / 0.5 * 255);
return triplet;
}
The function takes an ordinal range from -1.0 to 1.0 per the JET color specification, though this function does no sanity checking if you're outside that range (I do that before my call here).
So make sure you do sanity/bounds checking prior to calling this function or simply add your own limiting to cap the value when you implement it yourself.
This implementation does not take luminosity into consideration so may not be considered a purist implementation but gets you in the ballpark fairly well and is much faster.
Java(Processing) code that will generate Jet and HotAndCold RGB. I created this code following the RGB distribution scheme in the post of Amro above.
color JetColor(float v,float vmin,float vmax){
float r=0, g=0, b=0;
float x = (v-vmin)/(vmax-vmin);
r = 255*constrain(-4*abs(x-0.75) + 1.5,0,1);
g = 255*constrain(-4*abs(x-0.50) + 1.5,0,1);
b = 255*constrain(-4*abs(x-0.25) + 1.5,0,1);
return color(r,g,b);
}
color HeatColor(float v,float vmin,float vmax){
float r=0, g=0, b=0;
float x = (v-vmin)/(vmax-vmin);
r = 255*constrain(-4*abs(x-0.75) + 2,0,1);
g = 255*constrain(-4*abs(x-0.50) + 2,0,1);
b = 255*constrain(-4*abs(x) + 2,0,1);
return color(r,g,b);
}
//Values are calculated on trapezoid cutoff points in format y=constrain(a(x-t)+b,0,1)
//Where a=((delta)y/(delta)x), t=x-offset value to symetric middle of trapezoid, and b=y-a(x-t) for the last peak point (x,y)
I hope this is what you're looking for:
double interpolate( double val, double y0, double x0, double y1, double x1 ) {
return (val-x0)*(y1-y0)/(x1-x0) + y0;
}
double blue( double grayscale ) {
if ( grayscale < -0.33 ) return 1.0;
else if ( grayscale < 0.33 ) return interpolate( grayscale, 1.0, -0.33, 0.0, 0.33 );
else return 0.0;
}
double green( double grayscale ) {
if ( grayscale < -1.0 ) return 0.0; // unexpected grayscale value
if ( grayscale < -0.33 ) return interpolate( grayscale, 0.0, -1.0, 1.0, -0.33 );
else if ( grayscale < 0.33 ) return 1.0;
else if ( grayscale <= 1.0 ) return interpolate( grayscale, 1.0, 0.33, 0.0, 1.0 );
else return 1.0; // unexpected grayscale value
}
double red( double grayscale ) {
if ( grayscale < -0.33 ) return 0.0;
else if ( grayscale < 0.33 ) return interpolate( grayscale, 0.0, -0.33, 1.0, 0.33 );
else return 1.0;
}
I'm not sure if this scale is 100% identical to the image you linked but it should look very similar.
UPDATE I've rewritten the code according to the description of MatLab's Jet palette found here
double interpolate( double val, double y0, double x0, double y1, double x1 ) {
return (val-x0)*(y1-y0)/(x1-x0) + y0;
}
double base( double val ) {
if ( val <= -0.75 ) return 0;
else if ( val <= -0.25 ) return interpolate( val, 0.0, -0.75, 1.0, -0.25 );
else if ( val <= 0.25 ) return 1.0;
else if ( val <= 0.75 ) return interpolate( val, 1.0, 0.25, 0.0, 0.75 );
else return 0.0;
}
double red( double gray ) {
return base( gray - 0.5 );
}
double green( double gray ) {
return base( gray );
}
double blue( double gray ) {
return base( gray + 0.5 );
}