Click here to Skip to main content
15,885,767 members
Articles / Programming Languages / C
Article

Color gradient class

Rate me:
Please Sign up or sign in to vote.
1.80/5 (5 votes)
17 Jul 2008GPL3 40K   463   10   1
A C++ class for defining a color gradient and interpolating a value to produce a color.

Introduction

A class that interpolates between a single value (an unsigned 16 bit integer) and an RGB color value on a color scale that can be defined.

Source code

Due to the simplicity of the class, I'm displaying it here so that you don't have to download a zip file and extract that. This code should go into a file called Gradient.h.

C++
/*
 * A C++ class for defining a color gradient and interpolating a bound
 * value to produce a color value.
 * 
 * Copyright (C) 2008  Stefán Freyr Stefánsson, Arnar Birgisson
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef GRADIENT_H_
#define GRADIENT_H_

#include <vector>

/**
 * A representation of a color. Each channel is the standard 8-bit.
 */
typedef struct rgb {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    
    bool operator==(rgb lhr)
    {
        return this == &lhr;
    }
}rgb;

/**
 * A color instance that represents an invalid color
 */
static rgb INVALID_COLOR = {0, 0, 0}; 

/**
 * A helper class to map a numerical value to a gradient color scale. 
 * The gradient appearance is controlled by specifying a list of colors
 * (called stops) which will then be distributed across a specified
 * range of values. Users can get a color value for a specific numerical
 * value and this class will interpolate the correct color value from
 * its stop list.
 */ 
class Gradient
{
private:
  /**
   * The minimum value that this gradient will represent.
   */
  uint16_t m_min;
  
  /**
   * The color representing the minimum value.
   */
  rgb m_minColor;
  
  /**
   * The outlier color for min outliers.
   */
  rgb m_minOutlierColor;
  
  /**
   * The maximum value that this gradient will represent.
   */
  uint16_t m_max;

  /**
   * The color representing the maximum value.
   */
  rgb m_maxColor;
  
  /**
   * The outlier color for max outliers.
   */
  rgb m_maxOutlierColor;


  /**
   * The color values of the gradient.
   */
  std::vector<rgb> m_stops;
  
  /**
   * The workhorse of the class. This method calculates the color that
   * represents the interpolation of the two specified color values using
   * the specified normalized value.
   * @param c1  the color representing the normalized value 0.0
   * @param c2  the color representing the normalized value 1.0
   * @param normalized_value  a value between 0.0 and 1.0 representing
   *                          where on the color scale between c1 and c2
   *                          the returned color should be.
   * @return the interpolated color at normalized_value between c1 and c2.
   */
  rgb interpolate(rgb c1, rgb c2, float normalized_value){
    if( normalized_value <= 0.0 ){ return c1; }
    if( normalized_value >= 1.0 ){ return c2; }
    
    uint8_t red = (uint8_t)((1.0-normalized_value)*c1.red + 
                             normalized_value*c2.red);
    uint8_t green = (uint8_t)((1.0-normalized_value)*c1.green + 
                               normalized_value*c2.green);
    uint8_t blue = (uint8_t)((1.0-normalized_value)*c1.blue + 
                              normalized_value*c2.blue);
    
    return (rgb){red, green, blue};
  }

public:

  /**
   * Creates a new uninitialized instance of a Gradient. The caller must
   * initialize the instance with values before use by calling the 
   * initialize method.
   */
  Gradient(){}

  /**
   * Initializes this gradient by defining the range and intermediate stops as well as
   * optional outlier colors (both for values less that the minimum as well as values
   * more than the maximum. If outlier colors are not specified the first and last colors
   * of the stops will be used for all values
   * less than min and greater than max respectively.
   */
  void initialize(uint16_t min, uint16_t max, std::vector<rgb> stops, 
       rgb minOutlierColor=INVALID_COLOR, rgb maxOutlierColor=INVALID_COLOR){
    m_min = min;
    m_max = max;
    m_stops = stops;
    m_minOutlierColor = minOutlierColor;
    m_maxOutlierColor = maxOutlierColor;
  }

  /**
   * Creates a new instance of a Gradient and initializes it with the specified values.
   */
  Gradient(uint16_t min, uint16_t max, std::vector<rgb> stops, 
           rgb minOutlierColor=INVALID_COLOR, rgb maxOutlierColor=INVALID_COLOR){
    initialize(min, max, stops, minOutlierColor, maxOutlierColor);
  }

    /**
     * Destructor.
     */
    virtual ~Gradient(){}
    
    /**
     * Retrieve an RGB color struct for a specified value. Caller must have invoked
     * initialize before calling this method.
     */
    rgb getRgb(uint16_t value){
      // Handle outliers
      if( value < m_min ){ return m_minOutlierColor == 
           INVALID_COLOR ? m_stops.front() : m_minOutlierColor; }
      if( value > m_max ){ return m_maxOutlierColor == 
           INVALID_COLOR ? m_stops.back()  : m_maxOutlierColor; }
      
      // Find the "bin" that value falls in
      uint16_t range = m_max - m_min;
      uint16_t v = value - m_min;
      float step = range / (float)(m_stops.size()-1);
      int bin = (int)(v / step);
      
      // Normalize value in the interval (0,1]
      float normalized_v = (v - bin*step) / step;
      
      return interpolate(m_stops[bin], m_stops[bin+1], normalized_v);
    }
};

#endif /*GRADIENT_H_*/

Using the code

Here's an example of the code in action:

C++
#include <Gradient.h>

[...]

  std::vector<rgb> stops;

  stops.push_back((rgb){255,255,255}); // white is the "closest"
  stops.push_back((rgb){255,0,0}); // red
  stops.push_back((rgb){255,255,0}); // yellow
  stops.push_back((rgb){0,255,0}); // green
  stops.push_back((rgb){0,0,255}); // blue
  stops.push_back((rgb){75,25,150}); // purple is the "farthest"
  
  Gradient grad;
  // initialize with 0x00f as the min value, 0xfff0 as the max value,
  // the above vector of rgb values as the color space definition and
  // use black to represent any outliers (both <min and >max)
  grad.initialize(0x000f, 0xfff0, stops, (rgb){0,0,0}, (rgb){0,0,0});

  // Get the color value for 123456
  rgb color = grad.getRgb(123456);
  // use color.r, color.g, color.b to obtain the channel values

Known bugs

The Gradient class currently only works for interpolating uint16_t integral values to color. It's quite possible that this could be made more flexible (for example, by making the class a template class). There are some other potential bugs in it that have to do with an integer overflow, so any comments or improvements on the code are welcome.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Network Administrator Reykjavik University
Iceland Iceland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralBoundary condition Pin
spiderkarma4-Jan-11 8:18
spiderkarma4-Jan-11 8:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.