//
// modify hue of a photograph.
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//
//
//
//
//
//
//
//



#ifdef NEED_PCH
   #include "pch.h"
#else
   #include "stdafx.h"
#endif // !NEED_PCH

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "globs.h"
#include "rehue.h"





// qxReHue()

ReHue::ReHue()
   : m_pHueMap(NULL)
   , m_pLumGate(NULL)
   , m_pSatGate(NULL)
{}



// qxReHue()

ReHue::ReHue(const ReHue &rs)
   : m_pHueMap(NULL)
   , m_pLumGate(NULL)
   , m_pSatGate(NULL)
{
   if(rs.m_pHueMap != NULL)
      m_pHueMap = new ShapingAnch(*rs.m_pHueMap);

   if(rs.m_pLumGate != NULL)
      m_pLumGate = new ShapingAnch(*rs.m_pLumGate);

   if(rs.m_pSatGate != NULL)
      m_pSatGate = new ShapingAnch(*rs.m_pSatGate);

}





// qx~ReHue()

ReHue::~ReHue() {
   if(m_pHueMap  != NULL) {delete m_pHueMap;  m_pHueMap=NULL;}
   if(m_pLumGate != NULL) {delete m_pLumGate; m_pLumGate=NULL;}
   if(m_pSatGate != NULL) {delete m_pSatGate; m_pSatGate=NULL;}




} // end of ReHue::~ReHue()...




// qxReHue::operator=()

const ReHue& ReHue::operator=(const ReHue &rs) {
   if(this != &rs) {
      if(m_pHueMap  != NULL) {delete m_pHueMap;  m_pHueMap=NULL;}
      if(m_pLumGate != NULL) {delete m_pLumGate; m_pLumGate=NULL;}
      if(m_pSatGate != NULL) {delete m_pSatGate; m_pSatGate=NULL;}

      if(rs.m_pHueMap != NULL)
         m_pHueMap = new ShapingAnch(*rs.m_pHueMap);

      if(rs.m_pLumGate != NULL)
         m_pLumGate = new ShapingAnch(*rs.m_pLumGate);

      if(rs.m_pSatGate != NULL)
         m_pSatGate = new ShapingAnch(*rs.m_pSatGate);

   } // if(this != &rs)

   return *this;
} // end of const ReHue& ReHue::operator=()...




// qxLumToFact()

double ReHue::LumToFact(double luminosityIn) {
   double dRet = 1.0;

   if(m_pLumGate == NULL)
      return dRet;

   dRet = m_pLumGate->inToOut(luminosityIn);

   return dRet;
} // end of ReHue::LumToFact(double luminosityIn)...





// qxSatToFact()

double ReHue::SatToFact(double saturationIn) {
   double dRet = 1.0;

   if(m_pSatGate == NULL)
      return dRet;

   dRet = m_pSatGate->inToOut(saturationIn);

   return dRet;
} // end of ReHue::SatToFact(double saturationIn)...






// qxAssignShape()

int ReHue::AssignShape(const shapeType shT, const char *fileName) {
   int iRet = 0;

   switch(shT) {
      case shapeHue:
         if(m_pHueMap  != NULL) {delete m_pHueMap;  m_pHueMap=NULL;}
         m_pHueMap = new ShapingAnch;
         iRet = m_pHueMap->read_list(fileName);
         if(iRet < 0) {
            delete m_pHueMap;  m_pHueMap=NULL;
         } // if(iRet < 0)
         break;
      case shapeLum:
         if(m_pLumGate  != NULL) {delete m_pLumGate;  m_pLumGate=NULL;}
         m_pLumGate = new ShapingAnch;
         iRet = m_pLumGate->read_list(fileName);
         if(iRet < 0) {
            delete m_pLumGate;  m_pLumGate=NULL;
         } // if(iRet < 0)
         break;
      case shapeSat:
         if(m_pSatGate  != NULL) {delete m_pSatGate;  m_pSatGate=NULL;}
         m_pSatGate = new ShapingAnch;
         iRet = m_pSatGate->read_list(fileName);
         if(iRet < 0) {
            delete m_pSatGate;  m_pSatGate=NULL;
         } // if(iRet < 0)
         break;
      default:
         iRet = -1;
         break;
   } // switch(shT)



   return iRet;
} // end of ReHue::AssignShape()...






// qxAssignShapes()

int ReHue::AssignShapes(const char *fileHue, const char *fileSat, const char *fileLum) {
   int iRet = 0;   // unless we encounter an error...
   int iTmp;

   // this one must be present:
   iTmp = this->AssignShape(shapeHue, fileHue);
   if(iTmp < 0) iRet = iTmp;

   // these others are optional, so don't show bad return if they're NULL:
   iTmp = this->AssignShape(shapeSat, fileSat);
   if((fileSat != NULL) && (iTmp < 0)) iRet = iTmp;

   iTmp = this->AssignShape(shapeLum, fileLum);
   if((fileLum != NULL) && (iTmp < 0)) iRet = iTmp;

   // qxtemp:
   if(m_pHueMap  != NULL)
      m_pHueMap->write_list("blahHueMap.shp");

   return iRet;
} // end of ReHue::AssignShapes()...




// the following is adapted from $jimwtl/pp_misc.cpp:



// qxRGBtoHSL()

int ReHue::RGBtoHSL(const double R, const double G, const double B, double &hOut, double &sOut, double &lOut) {
// DWORD RGBtoHSL(DWORD rgb)
   // BYTE R,G,B;             // input RGB values 
   // BYTE H,L,S;             // output HSL values 
   double cMax,cMin;          // max and min RGB values 
   double Rdelta,Gdelta,Bdelta;  // intermediate value: % of spread from max

   // R = GetRValue(rgb);
   // G = GetGValue(rgb);
   // B = GetBValue(rgb);

   cMax = MAX( MAX(R,G), B);  // calculate lightness 
   cMin = MIN( MIN(R,G), B);
   lOut = ((((cMax+cMin)*HSLMAX)+RGBMAX)/(2*RGBMAX));
   // L = (BYTE)((((cMax+cMin)*HSLMAX)+RGBMAX)/(2*RGBMAX));

   if(cMax==cMin){         // r=g=b --> achromatic case 
      sOut = 0.0;             // saturation 
      hOut = UNDEFINED;       // hue 
   }else{               // chromatic case 
      if (lOut <= (HSLMAX/2.0)) // saturation 
         sOut = ((((cMax-cMin)*HSLMAX)+((cMax+cMin)/2.0))/(cMax+cMin));
      else
         sOut = ((((cMax-cMin)*HSLMAX)+((2.0*RGBMAX-cMax-cMin)/2.0))/(2.0*RGBMAX-cMax-cMin));
      // hue 
      Rdelta = ((((cMax-R)*(HSLMAX/6.)) + ((cMax-cMin)/2.) ) / (cMax-cMin));
      Gdelta = ((((cMax-G)*(HSLMAX/6.)) + ((cMax-cMin)/2.) ) / (cMax-cMin));
      Bdelta = ((((cMax-B)*(HSLMAX/6.)) + ((cMax-cMin)/2.) ) / (cMax-cMin));

      if (R == cMax)
         hOut = (Bdelta - Gdelta);
      else if (G == cMax)
         hOut = ((HSLMAX/3.) + Rdelta - Bdelta);
      else // B == cMax 
         hOut = (((2.*HSLMAX)/3.) + Gdelta - Rdelta);

      if (hOut < 0.0)    hOut += HSLMAX;
      if (hOut > HSLMAX) hOut -= HSLMAX;
   }

   return 0;
}




// end of code adapted from $jimwtl/pp_misc.cpp...



// the following is adapted from $jim4/color.cpp:

/* utility routine for HSLtoRGB */ 
double HueToRGB(double n1, double n2, double hue) {
   /* range check: note values passed add/subtract thirds of range */ 
   if (hue < 0.0)
      hue += HSLMAX;
 
   if (hue > HSLMAX)
      hue -= HSLMAX;

   /* return r,g, or b value from this tridrant */ 
   if (hue < (HSLMAX/6.0))
       return ( n1 + (((n2-n1)*hue+(HSLMAX/12.0))/(HSLMAX/6.0)) );
   if (hue < (HSLMAX/2.0))
      return ( n2 );
   if (hue < ((HSLMAX*2.0)/3.0))
      return ( n1 +    (((n2-n1)*(((HSLMAX*2.0)/3.0)-hue)+(HSLMAX/12.0))/(HSLMAX/6.0)));
   else
      return ( n1 );
}






// qxHSLtoRGB()

int ReHue::HSLtoRGB(double hue, double sat, double lum, double &Rout, double &Gout, double &Bout) {

   // 05-29-13: make sure hue is in range:
   while(hue < 0.0)
      hue += HSLMAX;

   while(hue > HSLMAX)          // note that we allow both 0.0 and 240.0, though in some ways that's unappealing.
      hue -= HSLMAX;

   // end of 05-29-13 changes...


   // WORD R,G,B;                /* RGB component values */ 
   double  Magic1,Magic2;       /* calculated magic numbers (really!) */ 

   if (sat == 0.0) {            /* achromatic case */ 
      Rout=Gout=Bout=(lum*RGBMAX)/HSLMAX;
      if (hue != UNDEFINED) {
         /* ERROR */ 
      }
   } else  {                    /* chromatic case */ 
      /* set up magic numbers */ 
      if (lum <= (HSLMAX/2.0))
         Magic2 = (lum*(HSLMAX + sat) + (HSLMAX/2.0))/HSLMAX;
      else
         Magic2 = lum + sat - ((lum*sat) + (HSLMAX/2.0))/HSLMAX;
      Magic1 = 2.0*lum-Magic2;

      /* get RGB, change units from HSLMAX to RGBMAX */ 
      Rout = (HueToRGB(Magic1,Magic2,hue+(HSLMAX/3))*RGBMAX + (HSLMAX/2))/HSLMAX;
      Gout = (HueToRGB(Magic1,Magic2,hue)*RGBMAX + (HSLMAX/2)) / HSLMAX;
      Bout = (HueToRGB(Magic1,Magic2,hue-(HSLMAX/3))*RGBMAX + (HSLMAX/2))/HSLMAX;
   }
   return 0;
} 

// end of code adapted from $jim4/color.cpp...





//
// new 03-17-12 (jdl): this is the main function of the class.
//
//
//
//
//
//
// qxRemapHue()
//

int ReHue::RemapHue(double R, double G, double B, double &Rout, double &Gout, double &Bout) {
   // default behavior:
   Rout = R;
   Gout = G;
   Bout = B;

   // sanity check:
   if(m_pHueMap == NULL)
      return -1;

   double Hold, Sold, Lold;
   double Hnew, Snew, Lnew;
   ReHue::RGBtoHSL(R, G, B, Hold, Sold, Lold);

   // we don't modify S or L:
   Snew = Sold;
   Lnew = Lold;

   // but they may gate the extent of hue shift:
   double sFact = this->SatToFact(Sold);
   double lFact = this->LumToFact(Lold);

   double Htemp = m_pHueMap->inToOut(Hold);
   double HshiftOld = Htemp - Hold;
   double HshiftNew = HshiftOld * sFact * lFact;

   if(HshiftNew == 0.0)
      return 0;              // no shifting necessary...

   Hnew = Hold + HshiftNew;
   ReHue::HSLtoRGB(Hnew, Snew, Lnew, Rout, Gout, Bout);


   return 0;
} // end of ReHue::RemapHue()...
















