///////////////////////////////////////////////////////////////////////////////
// Pixel.cpp
// 
//     These functions implement th pixel class.  Each pixel object contains
// the color data for one pixel.  The color data may be stored in one of
// several different color models.  You may access the color data using
// any color model you choose -- the color data will be converted to this
// model.
//
//     Note that in all cases, color values range from 0.0 to 1.0
// inclusive.  Any values less than 0.0 are rounded up to 0.0, and any values
// greater than 1.0 are rounded down to 1.0.  Note that this has special
// meaning for the YIQ color model because it has some negative values.
// The valid range of values are mapped to [0.0 ... 1.0].
///////////////////////////////////////////////////////////////////////////////

#include "Pixel.h"

// No global data.


///////////////////////////////////////////////////////////////////////////////
// Pixel::Pixel ()                                          Default Constructor
//
//      This constructor initializes the pixel's color to black.
///////////////////////////////////////////////////////////////////////////////

Pixel::Pixel ()
: model (RGB), red (0.0), blue (0.0), green (0.0)
{
  // No code needed.
}


///////////////////////////////////////////////////////////////////////////////
// Pixel::~Pixel ()                                         Default Denstructor
//
//      Since no memory was allocated for this object, nothing needs to be
// done here.
///////////////////////////////////////////////////////////////////////////////

Pixel::~Pixel ()
{
  // No code needed.
}


///////////////////////////////////////////////////////////////////////////////
// double Pixel::getRed ()
// double Pixel::getGreen ()
// double Pixel::getBlue ()
//
//      These functions get at the color values of this pixel.  They first
// convert the pixel to store its color using the RGB format, and then
// they get at the individual values.
///////////////////////////////////////////////////////////////////////////////

double Pixel::getRed ()
{
  if (model != RGB) convertTo (RGB);
  return red;        
}

double Pixel::getGreen ()
{
  if (model != RGB) convertTo (RGB);
  return green;        
}

double Pixel::getBlue ()
{
  if (model != RGB) convertTo (RGB);
  return blue;        
}


///////////////////////////////////////////////////////////////////////////////
// double Pixel::getHue ()
// double Pixel::getSaturation ()
// double Pixel::getIntensity ()
//
//      These functions get at the color values of this pixel.  They first
// convert the pixel to store its color using the HSV format, and then
// they get at the individual values.
///////////////////////////////////////////////////////////////////////////////

double Pixel::getHue ()
{
  if (model != HSV) convertTo (HSV);
  return hue;        
}

double Pixel::getSaturation ()
{
  if (model != HSV) convertTo (HSV);
  return saturation;        
}

double Pixel::getIntensity ()
{
  if (model != HSV) convertTo (HSV);
  return intensity;        
}


///////////////////////////////////////////////////////////////////////////////
// double Pixel::getBrightness ()
// double Pixel::getChroma ()
// double Pixel::getPurity ()
//
//      These functions get at the color values of this pixel.  They first
// convert the pixel to store its color using the YIQ format, and then
// they get at the individual values.
///////////////////////////////////////////////////////////////////////////////

double Pixel::getBrightness ()
{
  if (model != YIQ) convertTo (YIQ);
  return brightness;        
}

double Pixel::getChroma ()
{
  if (model != YIQ) convertTo (YIQ);
  return chroma;        
}

double Pixel::getPurity ()
{
  if (model != YIQ) convertTo (YIQ);
  return purity;        
}


///////////////////////////////////////////////////////////////////////////////
// double Pixel::getCyan ()
// double Pixel::getMagenta ()
// double Pixel::getYellow ()
//
//      These functions get at the color values of this pixel.  They first
// convert the pixel to store its color using the CMY format, and then
// they get at the individual values.
///////////////////////////////////////////////////////////////////////////////

double Pixel::getCyan ()
{
  if (model != CMY) convertTo (CMY);
  return cyan;
}

double Pixel::getMagenta ()
{
  if (model != CMY) convertTo (CMY);
  return magenta;
}

double Pixel::getYellow ()
{
  if (model != CMY) convertTo (CMY);
  return yellow;
}


///////////////////////////////////////////////////////////////////////////////
// void Pixel::setRGBColor (double red, double green, double blue)
// void Pixel::setHSVColor (double hue, double saturation, double intensity)
// void Pixel::setYIQColor (double brightness, double chroma, double purity)
// void Pixel::setCMYColor (double cyan, double magenta, double yellow)
//
//      These functions set the color (and color model) of this pixel.  The
// parameters are rounded (clipped) after the values are set.
///////////////////////////////////////////////////////////////////////////////

void Pixel::setRGBColor (double _red, double _green, double _blue)
{
  model = RGB;
  red = _red;
  green = _green;
  blue = _blue;
  clipValues ();
}

void Pixel::setHSVColor (double _hue, double _saturation, double _intensity)
{
  model = HSV;
  hue = _hue;
  saturation = _saturation;
  intensity = _intensity;
  clipValues ();
}

void Pixel::setYIQColor (double _brightness, double _chroma, double _purity)
{
  model = YIQ;
  brightness = _brightness;
  chroma = _chroma;
  purity = _purity;
  clipValues ();
}

void Pixel::setCMYColor (double _cyan, double _magenta, double _yellow)
{
  model = CMY;
  cyan = _cyan;
  magenta = _magenta;
  yellow = _yellow;
  clipValues ();
}


///////////////////////////////////////////////////////////////////////////////
// void Pixel::adjustRed (double newValue)
//   .    .       .
//   .    .       .
//   .    .       .
// void Pixel::adjustYellow (double newValue)
//
//      These functions tweak the color of the pixel.  You may adjust
// a single value using these functions.  Note that the current color
// model is changed if needed.  The values are clipped to [0.0 ... 1.0].
///////////////////////////////////////////////////////////////////////////////

void Pixel::adjustRed (double newValue)
{
  convertTo (RGB);
  red = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustGreen (double newValue)
{
  convertTo (RGB);
  green = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustBlue (double newValue)
{
  convertTo (RGB);
  blue = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustHue (double newValue)
{
  convertTo (HSV);
  hue = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustSaturation (double newValue)
{
  convertTo (HSV);
  saturation = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustIntensity (double newValue)
{
  convertTo (HSV);
  intensity = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustBrightness (double newValue)
{
  convertTo (YIQ);
  brightness = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustChroma (double newValue)
{
  convertTo (YIQ);
  chroma = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustPurity (double newValue)
{
  convertTo (YIQ);
  purity = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustCyan (double newValue)
{
  convertTo (CMY);
  cyan = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustMagenta (double newValue)
{
  convertTo (CMY);
  magenta = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}

void Pixel::adjustYellow (double newValue)
{
  convertTo (CMY);
  yellow = (newValue < 0.0) ? 0.0 : ((newValue > 1.0) ? 1.0 : newValue);
}


///////////////////////////////////////////////////////////////////////////////
// void Pixel::clipValues ()
//
//      This function makes sure that all the values for this pixel are
// in the range [0.0 ... 1.0].
///////////////////////////////////////////////////////////////////////////////

void Pixel::clipValues ()
{
  if (value1 < 0.0) value1 = 0.0;
  else if (value1 > 1.0) value1 = 1.0;

  if (value2 < 0.0) value2 = 0.0;
  else if (value2 > 1.0) value2 = 1.0;

  if (value3 < 0.0) value3 = 0.0;
  else if (value3 > 1.0) value3 = 1.0;
}


///////////////////////////////////////////////////////////////////////////////
// void Pixel::convertTo (ColorModel newModel)
//
//      This function changes the way this pixel stores its color.  There
// are four color models that this pixel can use: RGB, HSV, YIQ, and CMY.
// This function changes this pixel from the current color model to the
// specified color model.
///////////////////////////////////////////////////////////////////////////////

void Pixel::convertTo (ColorModel newModel)
{
  double tempRed, tempGreen, tempBlue;

  // If the color model is already correct, bail.

  if (model == newModel)
    return;
  
  // First, convert the current color to RGB and store it locally.

  switch (model)
  {
    case RGB:
      tempRed = red;
      tempGreen = green;
      tempBlue = blue;
      break;
      
    case HSV:
      if (saturation == 0)
	tempRed = tempGreen = tempBlue = intensity;
      else
      {
	int i;
        double a, b, c, f;
	hue *= 6.0;
        if (hue >= 6.0)
	  hue -= 6.0;
	i = (int) hue;
	f = hue - i;
	a = intensity * (1.0 - saturation);
	b = intensity * (1.0 - (saturation * f));
	c = intensity * (1.0 - (saturation * (1.0 - f)));
	switch (i)
	{
  	  case 0: tempRed = intensity; tempGreen = c; tempBlue = a; break;
  	  case 1: tempRed = b; tempGreen = intensity; tempBlue = a; break;
  	  case 2: tempRed = a; tempGreen = intensity; tempBlue = c; break;
  	  case 3: tempRed = a; tempGreen = b; tempBlue = intensity; break;
  	  case 4: tempRed = c; tempGreen = a; tempBlue = intensity; break;
  	  case 5: tempRed = intensity; tempGreen = a; tempBlue = b; break;
	}
      }
      break;

    case YIQ:
      brightness *= 1.03;
      chroma = chroma * 1.192 - 0.596;
      purity = purity * 1.046 - 0.523;
      tempRed   = (brightness + 0.9556880604 * chroma + 0.6198580945 * purity);
      tempGreen = (brightness - 0.2715817969 * chroma - 0.6468738161 * purity);
      tempBlue  = (brightness - 1.108177327  * chroma + 1.705064560 * purity);
      break;
      
    case CMY:
      tempRed = 1.0 - cyan;
      tempGreen = 1.0 - magenta;
      tempBlue = 1.0 - yellow;
      break;
  }

  // Next, switch color models and convert the temporary RGB to the correct
  //   model.

  model = newModel;
  
  switch (model)
  {
    case RGB:
      red = tempRed;
      green = tempGreen;
      blue = tempBlue;
      break;
      
    case HSV:
      double cmin, cmax, delta, rc, gc, bc;
      cmax = (tempRed > tempGreen) ? tempRed : tempGreen;
      cmax = (cmax > tempBlue) ? cmax : tempBlue;
      cmin = (tempRed < tempGreen) ? tempRed : tempGreen;
      cmin = (cmin < tempBlue) ? cmin : tempBlue;
      delta = cmax - cmin;
      intensity = cmax;
      saturation = (cmax != 0.0) ? delta / cmax : 0.0;
      if (saturation == 0.0)
	hue = 0.0;  // No hue.
      else
      {
	if (tempRed == cmax)
	{
	  hue = (((tempBlue > tempGreen) ? 6.0 : 0.0)
		 + (tempGreen - tempBlue) / delta) / 6.0;
	}
	else if (tempGreen == cmax)
	  hue = (2.0 + (tempBlue - tempRed) / delta) / 6.0;
	else
	  hue = (4.0 + (tempRed - tempGreen) / delta) / 6.0;
      }
      break;
      
    case YIQ:
      brightness = (0.299 * tempRed + 0.587 * tempGreen + 0.114 * tempBlue)
	           / 1.03;
      chroma = ((0.596 * tempRed - 0.275 * tempGreen - 0.321 * tempBlue)
		+ 0.596) / 1.192;
      purity = ((0.212 * tempRed - 0.523 * tempGreen + 0.311 * tempBlue)
		+ 0.523) / 1.046;
      break;
      
    case CMY:
      cyan = 1.0 - tempRed;
      magenta = 1.0 - tempGreen;
      yellow = 1.0 - tempBlue;
      break;
  }

  // Done, clip the values just in case there was a rounding error.

  clipValues ();
}








