//
// jgrlil.cpp (John's graphing .bmp class)
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//
//
// modification history:
//    10-23-23: new, split off from jgraph.cpp
//
//
//
//
//
//



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

#include <string>
#include <vector>











#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <sys/stat.h>

#include "globs.h"
#include "jgrlil.h"
#include "randd.h"
#include "bufin.h"
#include "shaping.h"
#include "fiwrap.h"
// #include "mprintf.h"
// #include "random32.h"
#include "rehue.h"
#include "ssprint.h"


#ifdef _WIN32
#include <process.h>
#include <windows.h>
#include <TCHAR.h>           // 04-26-06 (jdl): is this what we need???
#endif // _WIN32





#pragma warning( disable : 4100 )      
#pragma warning( disable : 4189 )      
#pragma warning( disable : 4996 )      


static inline void oswap(int &i, int &j) {int k = i; i = j; j = k;}
static inline void oswap(double &i, double &j) {double k = i; i = j; j = k;}
static inline void oswap(char *&cpx, char *&cpy) {char *cpt=cpx; cpx=cpy; cpy=cpt;}

// #define NO_MONTE   // 04-24-05: take monte carlo out of empty fill, etc.; out (hard-coded to true) 05-09-09 (jdl)










//
// new 08-30-08.  Returns an array of 256 which maps values (0 .. 255) to number of bits (0 .. 8)
//
//
//
//
//
//
// qxgetnBitsArr256()
//

static const int *getnBitsArr256() {
   static int first_time = 1;
   static int nBits[256];

   if(first_time) {
      int i;
      for(i=0; i<256; i++) {
         if(i>=128) {
            nBits[i] = 8;
         } else if(i>=64) {
            nBits[i] = 7;
         } else if(i>=32) {
            nBits[i] = 6;
         } else if(i>=16) {
            nBits[i] = 5;
         } else if(i>=8) {
            nBits[i] = 4;
         } else if(i>=4) {
            nBits[i] = 3;
         } else if(i>=2) {
            nBits[i] = 2;
         } else if(i>=1) {
            nBits[i] = 1;
         } else {
            nBits[i] = 0;
         }
         int j = i;
         int shiftCount = 0;
         while(j != 0) {
            j >>= 1;
            shiftCount++;
         } // while(j != 0)

         JASSERT(shiftCount == nBits[i]);
      } // for(i=0; i<256; i++)

      first_time = 0;
   } // if(first_time)

   return (const int *)nBits;
} // end of static const int *getnBitsArr256()...





//
// new 07-04-13.  Returns an array of 256 which maps values (0 .. 255) to secondary number of bits (0 .. 7)
//
// Note: we're "0-based", like the functions in rwe.exe, not "1-based", like the function just above us.
//
//
//
//
// qxgetqBitsArr256()
//

static const int *getqBitsArr256() {
   static int first_time = 1;
   static int qBits[256];

   if(first_time) {
      int i;
      for(i=0; i<256; i++) {
         if(i<128) {
            // no chance of confusion: highest bit isn't set.
            qBits[i] = 7;
         } else if(i<192) {
            qBits[i] = 6;
         } else if(i<224) {
            qBits[i] = 5;
         } else if(i<240) {
            qBits[i] = 4;
         } else if(i<248) {
            qBits[i] = 3;
         } else if(i<252) {
            qBits[i] = 2;
         } else if(i<254) {
            qBits[i] = 1;
         } else if(i<255) {
            qBits[i] = 0;
         } else {
            qBits[i] = 0;   // I'd like to say -1, but that might confuse the caller.
         }
      } // for(i=0; i<256; i++)

      first_time = 0;
   } // if(first_time)

   return (const int *)qBits;
} // end of static const int *getqBitsArr256()...




// qxjColor()

jColor::jColor() {
   m_redVal = 0;
   m_grnVal = 0;
   m_bluVal = 0;
}



jColor::jColor(int oldColor) {
   setColorFromOld(oldColor);
}




jColor::jColor(int iRed, int iGrn, int iBlu) {
   setRed(iRed);
   setGrn(iGrn);
   setBlu(iBlu);
}




jColor::jColor(const jColor& rs) {  // copy constructor...
   m_redVal = rs.m_redVal;
   m_grnVal = rs.m_grnVal;
   m_bluVal = rs.m_bluVal;
} // end of jColor() copy constructor





// qx~jColor()

jColor::~jColor() {
} // end of ~jColor()





//
//
//
//
//
// qxjColor::operator=()
//

const jColor& jColor::operator=(const jColor& rs) {

   if(&rs != this) {
      m_redVal = rs.m_redVal;
      m_grnVal = rs.m_grnVal;
      m_bluVal = rs.m_bluVal;
   } // if(&rs != this)

   return *this;
} // end of jColor::operator=()...





//
// new 01-30-02
//
//
//
// qxjColor::operator+=()
//

const jColor& jColor::operator+=(const jColor& rs) {

   m_redVal = (UCHAR)(MIN(255, ((int)rs.m_redVal + (int)m_redVal)));
   m_grnVal = (UCHAR)(MIN(255, ((int)rs.m_grnVal + (int)m_grnVal)));
   m_bluVal = (UCHAR)(MIN(255, ((int)rs.m_bluVal + (int)m_bluVal)));

   return *this;
} // end of jColor::operator+=()...





// qxjColor::dup_me()

jColor *jColor::dup_me() {
   jColor *newjColor = new jColor(*this);
   return newjColor;
}



//
// new 12-28-07
//
//
//
//
//
// qxgetCol()
//

int jColor::getCol(int iCol) const {
   int iRet = NADA;

   switch(iCol) {
      case COL_RED:
         iRet = (int)m_redVal;
         break;
      case COL_GRN:
         iRet = (int)m_grnVal;
         break;
      case COL_BLU:
         iRet = (int)m_bluVal;
         break;
      default:
         // bite me!  Leave it at NADA;
         break;
   } // switch(iCol)

   return iRet;
} // end of getCol()...




//
// new 12-28-07
//
//
//
//
//
// qxsetCol()
//

void jColor::setCol(int iCol, int newVal) {
   UCHAR uVal = (UCHAR)(RANGE(newVal, 0, 255));

   switch(iCol) {
      case COL_RED:
         m_redVal = uVal;
         break;
      case COL_GRN:
         m_grnVal = uVal;
         break;
      case COL_BLU:
         m_bluVal = uVal;
         break;
      default:
         // bite me!  Don't do anything.
         break;
   } // switch(iCol)

   return;
} // end of setCol()...





//
// new 05-28-12
//
//
//
//
//
// qxchgCol()
//

void jColor::chgCol(int iCol, int chgVal) {

   switch(iCol) {
      case COL_RED:
         m_redVal = (UCHAR)(RANGE(chgVal+m_redVal, 0, 255));
         break;
      case COL_GRN:
         m_grnVal = (UCHAR)(RANGE(chgVal+m_grnVal, 0, 255));
         break;
      case COL_BLU:
         m_bluVal = (UCHAR)(RANGE(chgVal+m_bluVal, 0, 255));
         break;
      default:
         // bite me!  Don't do anything.
         break;
   } // switch(iCol)

   return;
} // end of chgCol()...



//
// new 12-28-07 (jdl)
//
// Input:
//    minOver: defaults to 255
//
//
//
//
// qxnOverBright()
//

int jColor::nOverBright(int minOver) const {
   int nOver = 0;
   if(m_redVal >= minOver) nOver++;
   if(m_grnVal >= minOver) nOver++;
   if(m_bluVal >= minOver) nOver++;

   return nOver;
} // end of nOverBright()...





//
// new 04-23-02, from inline, to include doSqrTot
//
// (we don't think anybody actually uses this function 04-23-02)
//
//
// 05-10-02: doSqrTot of 2 (max val)
// 04-15-03: doSqrTot of 3 (max val; square energy if combining)
// 04-17-03: doSqrTot of 4, 5.
// 04-28-03: call fnBrite()
// 09-04-04: fractTwo (defaults to 0.0).
// 03-26-05: slope variables (default to 1.0).
//
// qxgetTotBrite()
//

double jColor::getTotBrite(int doSqrTot, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu) const {
   double dRet;
   int iRed = this->getRed();
   int iGrn = this->getGrn();
   int iBlu = this->getBlu();

   dRet = fnBrite((double)iRed, (double)iGrn, (double)iBlu, doSqrTot, fractTwo, slopeRed, slopeGrn, slopeBlu);

   return dRet;
} // end of getTotBrite()...





//
// newVal should be >= 0.0...
//
// Input:
//    unSaturate: 0.0 no change; 1.0 grey
//
//
//
// 12-26-01: don't allow over-saturation.
// 12-26-01: explicit unsaturate
// 01-01-02: experiment w/ taking out over-saturation check.
// 04-23-02: doSqrTot
// 05-10-02: doSqrTot of 2 (max val)
// 02-12-03: call new unsatCorrect()
// 04-12-03: possibly call new setTotBrit2()
// 04-13-03: unsatEarly flag (defaults to 0). (out 04-17-03)
// 04-13-03: new doFixOverS options.
// 04-14-03: pRedOut, pGrnOut, pBluOut (default to NULL)
// 04-14-03: mix methods here.
// 04-15-03: doSqrTot of 3 (max val; square energy if combining)
// 04-17-03: doSqrTot of 4, 5.
// 08-26-03: add fractFilu
// 04-12-04: logOnly (defaults to 0)
// 09-04-04: fractTwo (defaults to 0.0).
// 03-26-05: slope variables (default to 1.0).
// 09-03-07: pass unSat 0.0 to setTotBrit2().
// 09-03-07: doFixOverS of 4: call unsatCorrect() instead of setTotBrit2().
// 07-29-08 (jdl): call roundy().
// 07-29-08 (jdl): receive oldValIn (defaults to -1.0).
// 08-02-08 (jdl): receive prebFact.
// 08-16-08 (jdl): xptl: don't set iRet -1 if newVal is high.
// 10-01-08 (jdl): allow overflow in unsatCorrect() (first call ONLY).
//
// qxsetTotBrite()
//

int jColor::setTotBrite(double newVal, double unSaturate, int doFixOverS, int doSqrTot, double fractFilt, double fractFilu, double *pRedOut, double *pGrnOut, double *pBluOut, int logOnly, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu, double oldValIn, double prebFactIn) {
   int allowOverflow = 1;        // 10-01-08 (jdl)
   int iRet = 0;                 // unless we get pissed off.
   static double maxOver = 0.0;  // highest overflow (for doFixOverS of 3)
   // const double maxAllow = 255.49;
   double dRed2, dGrn2, dBlu2;
   // double unSatUse;
   // double dFrac2;

   // JASSERT(unSaturate >= 0.0);   // out 08-10-07; we can now add saturation w/ negative unSaturate.


   // sanity check(s):
   // JASSERT(newVal < (MAX_BRITE + 0.5)); // 02-02-02 // out 05-10-02
   // 05-10-02: print value if out of range:
   if(newVal >= (MAX_BRITE + 0.5)) {
      // qprintf(fp_dbg, "BAD???  setTotBrite() newVal %8.4f\n", newVal);
      // iRet = NADA;                            // out (xptl) 08-16-08 (jdl)
   } // if(newVal >= (MAX_BRITE + 0.5))

   double dRed, dGrn, dBlu;
   double dRedNu(0.0), dGrnNu(0.0), dBluNu(0.0);

   int iRed = this->getRed(); dRed = (double)iRed;
   int iGrn = this->getGrn(); dGrn = (double)iGrn;
   int iBlu = this->getBlu(); dBlu = (double)iBlu;

   double oldVal = oldValIn;  // 07-29-08 (jdl)
   if(oldVal < 0.0) oldVal = this->getTotBrite(doSqrTot, fractTwo, slopeRed, slopeGrn, slopeBlu);  // 05-11-02; 09-04-04; 03-26-05
   // 08-26-03: decide which fractFilt to use
   double lractFilt = fractFilt;    // local value
   if((fractFilu >= 0.0) && (oldVal < newVal)) {
      // we are going up, and a valid fractFilu specifies up fraction:
      lractFilt = fractFilu;
   } // if((fractFilu >= 0.0) && (oldVal < newVal))

   // lines moved from above 08-26-03, to use lractFilt:
   if(lractFilt > 0.0) {
      // mix:
      jColor ajc2 = *this;
      ajc2.setTotBrit2(newVal, unSaturate, doFixOverS, doSqrTot, &dRed2, &dGrn2, &dBlu2, fractTwo, slopeRed, slopeGrn, slopeBlu, oldVal, prebFactIn);  // 08-26-03: remove lractFilt; 09-04-04 fractTwo; slopes 03-26-05; oldVal 07-29-08 (jdl)
      // int stb2Inner(double dRed, double dGrn, double dBlu, double newVal, double unSaturate, int doFixOverS, int doSqrTot, double &newRed, double &newGrn, double &newBlu, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu, double oldValIn)
   } else {
      dRed2 = dGrn2 = dBlu2 = 0.0;  // no neet to waste time calling any functions (may call below if overflow)
   } // !if(lractFilt > 0.0)

   // 04-17-03: always unsaturate first:
   // double dRedHold=dRed, dGrnHold=dGrn, dBluHold=dBlu;              // 10-01-08 (jdl); qxtemp!
   unsatCorrect(dRed, dGrn, dBlu, unSaturate, doSqrTot, fractTwo, (allowOverflow=1));  // 09-04-04 fractTwo; allowOverflow 10-01-08 (jdl)

   if(doSqrTot == 0) {
      // always, before 04-23-02:
      // double oldVal = (double)(iRed + iGrn + iBlu);
      if(oldVal < 0.8) {
         // the pixel was completely black!
         dRedNu = dGrnNu = dBluNu = newVal / 3.0;   // just split it.
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03:
         if(lractFilt > 0.0) {
            dRedNu = ((1.0 - lractFilt) * dRedNu  +  lractFilt * dRed2);
            dGrnNu = ((1.0 - lractFilt) * dGrnNu  +  lractFilt * dGrn2);
            dBluNu = ((1.0 - lractFilt) * dBluNu  +  lractFilt * dBlu2);
         } // if(lractFilt > 0.0) 
      } // !if(oldVal < 0.8) 
   } else if(doSqrTot == 1) {
      // doSqrTot: new 04-23-02.
      // double oldVal = SQRT3 * sqrt((double)(iBlu*iBlu + iGrn*iGrn + iRed*iRed));
      if(oldVal < 0.8) {
         // the pixel was completely black!
         dRedNu = dGrnNu = dBluNu = newVal / SQRT3;   // just split it.
      } else {
         // why, you ask, do we act as if we were still linear here?
         // because, if newVal is double oldVal, we'll have four
         // times the energy, but each of the internal values will double.
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03:
         if(lractFilt > 0.0) {
            dRedNu = sqrt((1.0 - lractFilt) * (dRedNu*dRedNu)  +  lractFilt * (dRed2*dRed2));
            dGrnNu = sqrt((1.0 - lractFilt) * (dGrnNu*dGrnNu)  +  lractFilt * (dGrn2*dGrn2));
            dBluNu = sqrt((1.0 - lractFilt) * (dBluNu*dBluNu)  +  lractFilt * (dBlu2*dBlu2));
         } // if(lractFilt > 0.0) 
      } // !if(oldVal < 0.8) 
   } else if(doSqrTot == 2) {
      // new 05-10-02: brightness is 3.0 * maximum value of the three (r,g,b).
      if(oldVal < 0.8) {
         // the pixel was completely black!
         // dRedNu = dGrnNu = dBluNu = newVal / SQRT3;   // just split it.
         dRedNu = dGrnNu = dBluNu = newVal / 3.0;   // just split it.  // fix 04-15-02
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03:
         if(lractFilt > 0.0) {
            dRedNu = ((1.0 - lractFilt) * dRedNu  +  lractFilt * dRed2);
            dGrnNu = ((1.0 - lractFilt) * dGrnNu  +  lractFilt * dGrn2);
            dBluNu = ((1.0 - lractFilt) * dBluNu  +  lractFilt * dBlu2);
         } // if(lractFilt > 0.0) 
      }
   } else if(doSqrTot == 3) {
      // new 04-15-03: like 2, but use square energy for combination:
      if(oldVal < 0.8) {
         // the pixel was completely black!
         // dRedNu = dGrnNu = dBluNu = newVal / SQRT3;   // just split it.
         dRedNu = dGrnNu = dBluNu = newVal / 3.0;   // just split it.  // fix 04-15-02
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03: max, but square energy combination:
         if(lractFilt > 0.0) {
            dRedNu = sqrt((1.0 - lractFilt) * (dRedNu*dRedNu)  +  lractFilt * (dRed2*dRed2));
            dGrnNu = sqrt((1.0 - lractFilt) * (dGrnNu*dGrnNu)  +  lractFilt * (dGrn2*dGrn2));
            dBluNu = sqrt((1.0 - lractFilt) * (dBluNu*dBluNu)  +  lractFilt * (dBlu2*dBlu2));
         } // if(lractFilt > 0.0) 
      }
   } else if(doSqrTot == 4) {
      // cubey thing
      if(oldVal < 0.8) {
         // the pixel was completely black!
         dRedNu = dGrnNu = dBluNu = newVal / THREE23;
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03: but square energy combination:
         if(lractFilt > 0.0) {
            dRedNu = sqrt((1.0 - lractFilt) * (dRedNu*dRedNu)  +  lractFilt * (dRed2*dRed2));
            dGrnNu = sqrt((1.0 - lractFilt) * (dGrnNu*dGrnNu)  +  lractFilt * (dGrn2*dGrn2));
            dBluNu = sqrt((1.0 - lractFilt) * (dBluNu*dBluNu)  +  lractFilt * (dBlu2*dBlu2));
         } // if(lractFilt > 0.0) 
      }
   } else if(doSqrTot == 5) {
      // 4th powery thing
      if(oldVal < 0.8) {
         // the pixel was completely black!
         dRedNu = dGrnNu = dBluNu = newVal / THREE34;
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // 04-17-03: but square energy combination:
         if(lractFilt > 0.0) {
            dRedNu = sqrt((1.0 - lractFilt) * (dRedNu*dRedNu)  +  lractFilt * (dRed2*dRed2));
            dGrnNu = sqrt((1.0 - lractFilt) * (dGrnNu*dGrnNu)  +  lractFilt * (dGrn2*dGrn2));
            dBluNu = sqrt((1.0 - lractFilt) * (dBluNu*dBluNu)  +  lractFilt * (dBlu2*dBlu2));
         } // if(lractFilt > 0.0) 
      }
   } else if(doSqrTot == 6) {
      // 09-04-04: combination of 1 and 2.  We'll use SRSS combination.
      if(oldVal < 0.8) {
         // the pixel was completely black!
         dRedNu = dGrnNu = dBluNu = newVal / SQRT3;   // qxl: what?
      } else {
         double dRat = newVal / oldVal;
         dRedNu = dRed * dRat;
         dGrnNu = dGrn * dRat;
         dBluNu = dBlu * dRat;

         // square energy combination:
         if(lractFilt > 0.0) {
            dRedNu = sqrt((1.0 - lractFilt) * (dRedNu*dRedNu)  +  lractFilt * (dRed2*dRed2));
            dGrnNu = sqrt((1.0 - lractFilt) * (dGrnNu*dGrnNu)  +  lractFilt * (dGrn2*dGrn2));
            dBluNu = sqrt((1.0 - lractFilt) * (dBluNu*dBluNu)  +  lractFilt * (dBlu2*dBlu2));
         } // if(lractFilt > 0.0) 
      }
   } else {
      // unrecognized doSqrTot:
      qprintf(fp_dbg, "doSqrTot of %d really pisses me off.\n", doSqrTot);
      JASSERT(false);
   } // !if(doSqrTot == 0)


   // 04-12-04: log extent of overflow here:
   if(logOnly) {
      // try to pass the info back to caller, without fixing overflow:
      if(pRedOut != NULL) *pRedOut = dRedNu;
      if(pGrnOut != NULL) *pGrnOut = dGrnNu;
      if(pBluOut != NULL) *pBluOut = dBluNu;

      return 0;
   } // if(logOnly)


   double dMax, dMin, dCtr;
   double dMaxNu, dMinNu, dCtrNu;
   double dRedOld, dGrnOld, dBluOld;
   double dMaxAllow = 255.49;      // qxl: param!
   switch(doFixOverS) {
      case 0:
         // older, "worse", but makes highlights shiny (if washed out):
         // 01-01-02: possibly use OLD code; it's shinier:
         // #ifdef DEAD_CODE // out 12-26-01; possibly back in 01-01-02
         dRedNu = MIN(dRedNu, dMaxAllow);
         dGrnNu = MIN(dGrnNu, dMaxAllow);
         dBluNu = MIN(dBluNu, dMaxAllow);
         // iRed = MIN(255, (int)(dRedNu + 0.5));
         // iGrn = MIN(255, (int)(dGrnNu + 0.5));
         // iBlu = MIN(255, (int)(dBluNu + 0.5));
         // #endif // DEAD_CODE // out 12-26-01; possibly back in 01-01-02
         break;
      case 1:
         // 12-26-01: if a color is clipped, let _all_ colors be clipped!
         // newer, "better", but can dim brightest parts of picture.
         dMax = MAX(dRedNu, dGrnNu); dMax = MAX(dMax, dBluNu);
         if(dMax > dMaxAllow) {
            // reduce everybody
            double dRat = dMaxAllow / dMax;   // less than 1.0
            dRedNu *= dRat;
            dGrnNu *= dRat;
            dBluNu *= dRat;
         } // if(dMax > dMaxAllow)

         // as of 12-26-01, we _know_ these are in-range:
         // iRed = (int)(dRedNu + 0.5);
         // iGrn = (int)(dGrnNu + 0.5);
         // iBlu = (int)(dBluNu + 0.5);
         break;
      case 2:
         // 04-13-02: ensure max brite is achieved.
         dRedOld = MAX(dRedNu, 0.25);   // make it scaleable
         dGrnOld = MAX(dGrnNu, 0.25);   // make it scaleable
         dBluOld = MAX(dBluNu, 0.25);   // make it scaleable
         dMaxNu  = dMax = MA3(dRedOld, dGrnOld, dBluOld);
         dMinNu  = dMin = MI3(dRedOld, dGrnOld, dBluOld);
         dCtrNu  = dCtr = (dRedOld + dGrnOld + dBluOld) - (dMax + dMin);      // right?
         if(dMax > dMaxAllow) {
            if(doSqrTot == 0) {
               // qxtemp: just clip:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);
               // iRed = MIN(255, (int)(dRedNu + 0.5));
               // iGrn = MIN(255, (int)(dGrnNu + 0.5));
               // iBlu = MIN(255, (int)(dBluNu + 0.5));
            } else if(doSqrTot == 1) {
               double eTot   = dMax*dMax + dCtr*dCtr + dMin*dMin;  // we'll ensure that we maintain eTot
               dMaxNu        = dMaxAllow;
               double eTotNu = dMaxNu*dMaxNu + dCtr*dCtr + dMin*dMin;
               double eDif   = eTot - eTotNu;
               double eTwo   = dCtr*dCtr + dMin*dMin;
               double eTwoNu = eTwo + eDif;
               double eRat   = eTwoNu / eTwo;
               double bRat   = sqrt(eRat);
               dCtrNu       *= bRat;
               dMinNu       *= bRat;
               if(dCtrNu > dMaxAllow) {
                  dCtrNu  = dMaxAllow;
                  dMinNu  = sqrt(eTot - dMaxNu*dMaxNu - dCtrNu*dCtrNu);
               } // if(dCtrNu > dMaxAllow)
               // silly way to line up:
               if(ABS(dRedOld - dMax) < 0.1) dRedNu = dMaxNu;
               if(ABS(dGrnOld - dMax) < 0.1) dGrnNu = dMaxNu;
               if(ABS(dBluOld - dMax) < 0.1) dBluNu = dMaxNu;

               if(ABS(dRedOld - dCtr) < 0.1) dRedNu = dCtrNu;
               if(ABS(dGrnOld - dCtr) < 0.1) dGrnNu = dCtrNu;
               if(ABS(dBluOld - dCtr) < 0.1) dBluNu = dCtrNu;

               if(ABS(dRedOld - dMin) < 0.1) dRedNu = dMinNu;
               if(ABS(dGrnOld - dMin) < 0.1) dGrnNu = dMinNu;
               if(ABS(dBluOld - dMin) < 0.1) dBluNu = dMinNu;

               // clip for good measure:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);
               // iRed = MIN(255, (int)(dRedNu + 0.5));
               // iGrn = MIN(255, (int)(dGrnNu + 0.5));
               // iBlu = MIN(255, (int)(dBluNu + 0.5));
            } else if(doSqrTot == 2) {
               // qxtemp: just clip:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);
               // iRed = MIN(255, (int)(dRedNu + 0.5));
               // iGrn = MIN(255, (int)(dGrnNu + 0.5));
               // iBlu = MIN(255, (int)(dBluNu + 0.5));
            } else {
               // qxtemp: just clip:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);
               // iRed = MIN(255, (int)(dRedNu + 0.5));
               // iGrn = MIN(255, (int)(dGrnNu + 0.5));
               // iBlu = MIN(255, (int)(dBluNu + 0.5));
            }
         } else {
            // nothing to fix; just copy:
            // as of 12-26-01, we _know_ these are in-range:
            // iRed = (int)(dRedNu + 0.5);
            // iGrn = (int)(dGrnNu + 0.5);
            // iBlu = (int)(dBluNu + 0.5);
         } // if(dMax > dMaxAllow)
         break;
      case 3:
         // 04-13-02: yet another way to ensure max brite is achieved.
         dRedOld = MAX(dRedNu, 0.25);   // make it scaleable
         dGrnOld = MAX(dGrnNu, 0.25);   // make it scaleable
         dBluOld = MAX(dBluNu, 0.25);   // make it scaleable
         dMaxNu  = dMax = MA3(dRedOld, dGrnOld, dBluOld);
         dMinNu  = dMin = MI3(dRedOld, dGrnOld, dBluOld);
         dCtrNu  = dCtr = (dRedOld + dGrnOld + dBluOld) - (dMax + dMin);      // right?
         if(dMax > dMaxAllow) {
            if((doSqrTot == 0) || (doSqrTot == 2)) {
               // linear combination.
               // let's calculate an average of this method and the newer filter method.

               // Have we alread calc'd dRed2, etc?  If not, do so now:
               if(lractFilt == 0.0) {
                  // we haven't done this call yet, hoping we wouldn't need to.
                  jColor ajc = *this;
                  double unSat2;
                  ajc.setTotBrit2(newVal, (unSat2 = 0.0), doFixOverS, doSqrTot, &dRed2, &dGrn2, &dBlu2, fractTwo, slopeRed, slopeGrn, slopeBlu, oldVal); // 08-26-03; fractTwo 09-04-04; slopes 03-26-05; unSat2-> 0.0 09-03-07; oldVal 07-29-08 (jdl).
               } // if(lractFilt > 0.0)
               double dMax2 = MA3(dRed2, dGrn2, dBlu2);
               double Fact  = (dMaxAllow - dMax2) / (dMax - dMax2);  // if small, use mostly 2 stuff
               dRedNu = Fact * dRedNu  +  (1.0 - Fact) * dRed2;
               dGrnNu = Fact * dGrnNu  +  (1.0 - Fact) * dGrn2;
               dBluNu = Fact * dBluNu  +  (1.0 - Fact) * dBlu2;

               // clip for good measure:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);

               if(dMax > maxOver) {
                  qprintf(fp_dbg, "setTotBrite(): %6.2f %6.2f %6.2f -> %6.2f %6.2f %6.2f (oldVal %6.2f, newVal %6.2f).\n", dRed, dGrn, dBlu, dRedNu, dGrnNu, dBluNu, oldVal, newVal);
                  maxOver = dMax;
               } // if(dMax > maxOver)
            } else if((doSqrTot == 1) || (doSqrTot == 3) || (doSqrTot == 4) || (doSqrTot == 5) || (doSqrTot == 6)) {
               // square combination.
               // let's calculate an average of this method and the newer filter method.

               // Have we alread calc'd dRed2, etc?  If not, do so now:
               if(lractFilt == 0.0) {
                  // we haven't done this call yet, hoping we wouldn't need to.
                  jColor ajc = *this;
                  double unSat2;
                  ajc.setTotBrit2(newVal, (unSat2 = 0.0), doFixOverS, doSqrTot, &dRed2, &dGrn2, &dBlu2, fractTwo, slopeRed, slopeGrn, slopeBlu, oldVal); // 08-26-03; 09-04-04; 03-26-05; 09-03-07; oldVal 07-29-08 (jdl)
               } // if(lractFilt > 0.0)
               double dMax2 = MA3(dRed2, dGrn2, dBlu2);
               double eMax2 = dMax2*dMax2;
               double eMaxA = dMaxAllow*dMaxAllow;

               double eMax  = dMax*dMax;
               double Fact  = (eMaxA - eMax2) / (eMax - eMax2);  // if small, use mostly 2 stuff
               dRedNu = sqrt(Fact * (dRedNu*dRedNu)  +  (1.0 - Fact) * (dRed2*dRed2));
               dGrnNu = sqrt(Fact * (dGrnNu*dGrnNu)  +  (1.0 - Fact) * (dGrn2*dGrn2));
               dBluNu = sqrt(Fact * (dBluNu*dBluNu)  +  (1.0 - Fact) * (dBlu2*dBlu2));

               // clip for good measure:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);

               if(dMax > maxOver) {
                  qprintf(fp_dbg, "setTotBrite(): %6.2f %6.2f %6.2f -> %6.2f %6.2f %6.2f (oldVal %6.2f, newVal %6.2f).\n", dRed, dGrn, dBlu, dRedNu, dGrnNu, dBluNu, oldVal, newVal);
                  maxOver = dMax;
               } // if(dMax > maxOver)
            } else {
               // qxtemp: just clip:
               dRedNu = MIN(dRedNu, dMaxAllow);
               dGrnNu = MIN(dGrnNu, dMaxAllow);
               dBluNu = MIN(dBluNu, dMaxAllow);
            }
         } else {
            // nothing to fix; just copy:
            // as of 12-26-01, we _know_ these are in-range:
         } // if(dMax > dMaxAllow)
         break;
      case 4:
         // 09-03-07: let unsatCorrect() (new check added today) fix things:
         dMax = MA3(dRedNu, dGrnNu, dBluNu);
         if(dMax > dMaxAllow) {
            // let new feature of unsatCorrect fix things():
            // qprintf(fp_dbg, "\ndoFixOverS 4: before %8.4f %8.4f %8.4f\n", dRedNu, dGrnNu, dBluNu);
            double unSatDum = .000001;    // has to be != 0.0 or else unsatCorrect will exit early...
            unsatCorrect(dRedNu, dGrnNu, dBluNu, unSatDum, doSqrTot, fractTwo, (allowOverflow=0));  // allowOverflow 10-01-08 (jdl) (though, this is the default value, heh heh...)
            // qprintf(fp_dbg, "doFixOverS 4: after  %8.4f %8.4f %8.4f\n\n", dRedNu, dGrnNu, dBluNu);
         } // if(dMax > dMaxAllow)

         break;
      default:
         // just clippit
         dRedNu = MIN(dRedNu, dMaxAllow);
         dGrnNu = MIN(dGrnNu, dMaxAllow);
         dBluNu = MIN(dBluNu, dMaxAllow);
         break;
   } // switch(doFixOverS)

   if(pRedOut != NULL) *pRedOut = dRedNu;
   if(pGrnOut != NULL) *pGrnOut = dGrnNu;
   if(pBluOut != NULL) *pBluOut = dBluNu;

   // 07-29-08 (jdl):
   iRed = RANGE(roundy(dRedNu), 0, 255);
   iGrn = RANGE(roundy(dGrnNu), 0, 255);
   iBlu = RANGE(roundy(dBluNu), 0, 255);
   // 07-29-08 (jdl)...

   this->setColors(iRed, iGrn, iBlu);

   return iRet;
} // end of setTotBrite()...





#define D256 (256.0)
#define FRACT(x)       ((x) - floor(x))




//
// new 04-28-03
//
// We expect a positive (or zero) value in.
//
// return: some number 0.0 .. almost 1.0
//
//
// qxdoubToRand()
//

static double doubToRand(double dIn) {
   double dBig = PI * dIn;
   double dRet = FRACT(dBig);

   return dRet;
} // end of doubToRand()





//
// new 04-15-03; mimics getTotBrite()
//
//
// Notes 08-26-03: shit: in tracking down a silly overrange bug, I realize this:
// with doSqrTot == 2, we can return 767.999..., but with doSqrTot == 1 (or 3),
// the max we can return is 765.000.  Yes???  That's silly!
//
//
//
//
//
//
//
// 04-15-03: doSqrTot of 3 (max val; square energy if combining)
// 04-17-03: doSqrTot of 4, 5.
// 04-28-03: better scramble for certain methods.
// 08-26-03: SHIT: we're inconsistent in the range of numbers we'll return for diff doSqrTot!!!
// 09-04-04: doSqrTot 6 and fractTwo (defaults to 0.0).
// 03-26-05: slope variables (default to 1.0).
// 07-07-07: get rid of randomization.
// 07-28-07: oops: triple 255 gave overflow 768; fix!
//
// qxfnBrite()
//

double fnBrite(double dRedIn, double dGrnIn, double dBluIn, int doSqrTot, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu) {
   double dRet;
   double dmax, drest, dRand, dMore;

   // adjust for slope < 1.0, which exaggerates brightnesses lower than 256:
   double dRed = briteFix(dRedIn, slopeRed);
   double dGrn = briteFix(dGrnIn, slopeGrn);
   double dBlu = briteFix(dBluIn, slopeBlu);

   switch(doSqrTot) {
      case 0:
         // sum of red, grn, blu, and source of scaling brightness 0 .. 765.
         dRand = doubToRand(dRed + dGrn + dBlu);       // 04-28-03
         dRet = (dRed + dGrn + dBlu) + dRand;                 // 04-28-03
         break;
      case 1:
         // srss of red, grn, blu, scale-adjusted.
         // doSqrTot == 1: new 04-23-02:
         dRet = SQRT3 * sqrt(dBlu*dBlu + dGrn*dGrn + dRed*dRed);
         break;
      case 2:
      case 3:
         // largest of red, grn, blu, scale-adjusted.
         // doSqrTot == 2: new 05-10-02:
         // doSqrTot == 3: new 04-15-03: still max; to be combined w/ squares later.
         dmax = MA3(dRed, dGrn, dBlu);
         drest = dRed + dGrn + dBlu - dmax;
         if(dmax > 0.0) {
            dMore = 2.99 * (drest / (2.0 * dmax));          // 07-28-07: don't allow quite 3.0 extra...
         } else {
            // just thought of this 07-28-07: don't divide by zero, idiot!
            dMore = 0.0;
         } // !if(dmax > 0.0)
         dRet = 3.0 * dmax + dMore;                      // 07-07-07
         break;
      case 4:
         // cubey thing
         dRet = THREE23 * pow((double)(dBlu*dBlu*dBlu + dGrn*dGrn*dGrn + dRed*dRed*dRed), (1.0/3.0));
         break;
      case 5:
         // 4th powery thing
         dRet = THREE34 * sqrt(sqrt(dRed*dRed*dRed*dRed + dGrn*dGrn*dGrn*dGrn + dBlu*dBlu*dBlu*dBlu));
         break;
      case 6:
         // 09-04-04: combination of 1 and 2.
         if(fractTwo == 0.0) {
            dRet = fnBrite(dRed, dGrn, dBlu, 1, 0.0, slopeRed, slopeGrn, slopeBlu);
         } else if(fractTwo == 1.0) {
            dRet = fnBrite(dRed, dGrn, dBlu, 2, 0.0, slopeRed, slopeGrn, slopeBlu);
         } else {
            dRet = (fractTwo * fnBrite(dRed, dGrn, dBlu, 2, 0.0, slopeRed, slopeGrn, slopeBlu)) + ((1.0 - fractTwo) * fnBrite(dRed, dGrn, dBlu, 1, 0.0, slopeRed, slopeGrn, slopeBlu));
         }
         break;
      default:
         // whaaaa???
         dRet = 0.0;
         JASSERT(false);
         break;
   } // switch(doSqrTot)

   return dRet;
} // end of fnBrite()





//
// returns Brite value (0 .. 765 kinda thing); expects doSqrTot of 1 (restriction removed 04-15-03)
// newRed, newGrn, newBlu are set.
//
// if dPower is greater than 1.0, things get darker.  If less than 1.0, things get brighter.
//
// 04-15-03: support other doSqrTot's
// 09-04-04: fractTwo (defaults to 0.0).
// 03-26-05: slope variables (default to 1.0).
//
// qxfnWeeble()
//

double fnWeeble(double dRed, double dGrn, double dBlu, double dPower, int doSqrTot, double &newRed, double &newGrn, double &newBlu, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu) {
   // if(doSqrTot != 1) return DADA;
   if(dPower <= 0.0) return DADA;

   newRed = D256 * pow((dRed / D256), dPower);
   newGrn = D256 * pow((dGrn / D256), dPower);
   newBlu = D256 * pow((dBlu / D256), dPower);

   double dRet = fnBrite(newRed, newGrn, newBlu, doSqrTot, fractTwo, slopeRed, slopeGrn, slopeBlu);

   return dRet;
} // end of fnWeeble()...



//
// new 03-26-05
//
// 256 in always means 256 out, no matter what slopeIn
// slopeIn < 1.0 means that darker values are represented as brighter than they really are (to discourage amplification)
//
//
//
//
// qxbriteFix()
//

double briteFix(double briteIn, double slopeIn) {
   if(slopeIn == 1.0) return briteIn;

   double dRet = pow((briteIn / D256), slopeIn) * D256;

   return dRet;
} // end of briteFix()...






//
// new 02-09-07, from setTotBrit2().  Split out to return double values rather than int's via jColor class.
//
// This function does "filter" darkening or brightening on a pixel.
//
// Input:
//    dRed, dGrn, dBlu: starting brightness of pixel (resulting in oldVal, calculated here).
//    newVal: new brightness desired.
//    unSaturate
//    doFixOverS
//    doSqrTot
//    fractTwo
//    slopeRed, slopeGrn, slopeblu
//    oldValIn
//
// Output:
//    return value: 0 if ok, NADA if error.
//    newRed, newGrn, newBlu: new brightnesses for the pixel.
//
//
//
// 08-10-07: accept negative unSaturate.
// 09-01-07: move 0.25 min check till AFTER unsatCorrect().
// 07-29-08 (jdl): oldValIn.
// 08-02-08 (jdl): prebFactIn.
// 08-16-08 (jdl): overbright protection.
//
// qxstb2Inner()
//

int stb2Inner(double dRed, double dGrn, double dBlu, double newVal, double unSaturate, int doFixOverS, int doSqrTot, double &newRed, double &newGrn, double &newBlu, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu, double oldValIn, double prebFactIn) {

   // double calcPrlog(double *rCum, double ratIdeal, double dBriteTarg, double dPowerPrev, int &nTimesOut, double &dBriteOut, const double *dTcont, double contReduce, int sqezDiff) {
   int iRet = 0;        // unless we get pissed off.
   int nTimes  = 0;
   double dPowerLow(0.0), dPowerHigh(0.0);
   double dBriteTarg = MAX(newVal, 0.25);  // zero is NOT an acceptable target!
   double dBrite(0.0), dBriteLow(0.0), dBriteHigh(0.0);
   double maxAllow = 255.49;

   // 08-16-08 (jdl):
   if(newVal > MAX_BRIT2) {
      newRed = newGrn = newBlu = maxAllow;
      return iRet;             // ?? This is a "normal" return; is that bad?
   } // if(newVal > MAX_BRIT2)
   // 08-16-08 (jdl)...

   double oldVal = oldValIn;  // 07-29-08 (jdl)
   if(oldVal < 0.0) oldVal = fnBrite(dRed, dGrn, dBlu, doSqrTot, fractTwo, slopeRed, slopeGrn, slopeBlu);

   // if(unSaturate > 0.0) {
   if(unSaturate != 0.0) {         // 08-10-07 (jdl)
      unsatCorrect(dRed, dGrn, dBlu, unSaturate, doSqrTot, fractTwo);  // fractTwo 09-04-04
   }

   double dRedUse= RANGE(dRed * prebFactIn, 0.25, maxAllow);
   double dGrnUse= RANGE(dGrn * prebFactIn, 0.25, maxAllow);
   double dBluUse= RANGE(dBlu * prebFactIn, 0.25, maxAllow);
   // 08-02-08 (jdl)...

   if(oldVal < 0.8) {
      // the pixel was completely black!
      newRed = newGrn = newBlu = newVal / SQRT3;   // just split it.
   } else {
      // normal; do the calcs:

      double dPower = 1.0; // MAX(dPowerPrev, .00001) / 1.8;      // we're about to double it

      dBrite = fnWeeble(dRedUse, dGrnUse, dBluUse, dPower, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu);  // fractTwo 09-04-04; slopes 03-26-05

      // if we're already squeezing too much, squeeze less:
      nTimes = 0;
      while(dBrite < dBriteTarg) {
         dPower /= 2.0;
         dBrite = fnWeeble(dRedUse, dGrnUse, dBluUse, dPower, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu);
         nTimes++;

         if(nTimes >= 1024) {
            qprintf(fp_dbg, "\nDang!  after %d squeeze tries (power %8.4f), my bright is %8.4f; want %8.4f; input values %8.4f, %8.4f, %8.4f.\n", nTimes, dPower, dBrite, dBriteTarg, dRedUse, dGrnUse, dBluUse);
            // qprintf(fp_std, "\nDang!  after %d squeeze tries (power %8.4f), my bright is %8.4f; want %8.4f; input values %8.4f, %8.4f, %8.4f.\n", nTimes, dPower, dBrite, dBriteTarg, dRedUse, dGrnUse, dBluUse);
         } // if(nTimes >= 1024) {
         JASSERT(nTimes < 1024);
         // qprintf(fp_dbg, "Lighter dPower %12.8f gives dBrite %8.4f\n", dPower, dBrite);  // qxtemp!!
         // mprintf(fp_std, "Lighter dPower %12.8f gives dBrite %8.4f\n", dPower, dBrite);  // qxtemp!!
      } // while(dBrite >= dBriteTarg)

      // keep doubling dPower till it's squeezed it:
      nTimes = 0;
      while(dBrite   >= dBriteTarg) {
         dPowerLow  = dPower;
         dBriteLow    = dBrite;       // will be high
         dPower    *= 2.0;
         dBrite = fnWeeble(dRedUse, dGrnUse, dBluUse, dPower, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu);
         nTimes++;

         if(nTimes >= 1024) {
            qprintf(fp_dbg, "Dang!  after %d squeeze tries (pressure %8.4f), my len is %8.4f; want %8.4f.\n", nTimes, dPower, dBrite, dBriteTarg);
            // if(fp_std) mprintf(fp_std, "Dang!  after %d squeeze tries (pressure %8.4f), my len is %8.4f; want %8.4f.\n", nTimes, dPower, dBrite, dBriteTarg);
         } // if(nTimes >= 1024) {
         JASSERT(nTimes < 1024);
         // mprintf(fp_std, "Stronger dPower %12.8f gives dBrite %8.4f\n", dPower, dBrite);  // qxtemp!!
      } // while(dBrite >= dBriteTarg)
      dPowerHigh = dPower;
      dBriteHigh   = dBrite;          // will be low

      // now iterate, knowing we've got this bounded:
      int iMethod = 0;
      nTimes  = 0;
      if(iMethod == 0) {
         while(1) {  // not really...
            double ratLy = (dBriteTarg - dBriteLow) / (dBriteHigh - dBriteLow); // neg / neg = pos.

            // next guess:
            dPower = dPowerLow + ratLy * (dPowerHigh - dPowerLow);
            dBrite = fnWeeble(dRedUse, dGrnUse, dBluUse, dPower, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu);

            if(dBrite > dBriteTarg) {
               // not enough pressure; this is the new low.
               dPowerLow = dPower;
               dBriteLow   = dBrite;
            } else {
               // too much pressure; this is the new high:
               dPowerHigh = dPower;
               dBriteHigh   = dBrite;
            } // !if(dBrite > dBriteTarg)

            // are we, like, really really close?
            if(ABS(dBrite - dBriteTarg) < 0.1) {
               // hell fire, that's close enough!
               break;
            } // if(ABS(dBrite - dBriteTarg) < 0.1)

            nTimes++;
            if(nTimes > 1000) {
               break;
            } // if(nTimes > 1000)
         } // while(1) {  // not really...
      } else if(iMethod == 1) {
         // not used here...
      } else if(iMethod == 2) {
         // new 02-02-02: really stable binary search:
         while(1) {  // not really...
            double ratLy = (dBriteTarg - dBriteLow) / (dBriteHigh - dBriteLow); // neg / neg = pos.

            // next guess:
            // dPower = dPowerLow + ratLy * (dPowerHigh - dPowerLow);
            // iMethod 2: very stable binary search; never go outside:
            // dPower = (dPowerLow + dPowerHigh) / 2.0;
            dPower = sqrt(dPowerLow * dPowerHigh);         // geometric average.
            dBrite = fnWeeble(dRedUse, dGrnUse, dBluUse, dPower, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu);

            if(dBrite > dBriteTarg) {
               // not enough pressure; this is the new low.
               dPowerLow = dPower;
               dBriteLow   = dBrite;
            } else {
               // too much pressure; this is the new high:
               dPowerHigh = dPower;
               dBriteHigh   = dBrite;
            } // !if(dBrite > dBriteTarg)

            // are we, like, really really close?
            if(ABS(dBrite - dBriteTarg) < 0.1) {
               // hell fire, that's close enough!
               break;
            } // if(ABS(dBrite - dBriteTarg) < 0.1)

            nTimes++;
            if(nTimes > 1000) {
               break;
            } // if(nTimes > 1000)
         } // while(1) {  // not really...
      } else {
         // unrecognized iMethod; do nothing
      } // if(iMethod == ?)
   } // !if(oldVal < 0.8) 

   newRed = MIN(newRed, maxAllow);
   newGrn = MIN(newGrn, maxAllow);
   newBlu = MIN(newBlu, maxAllow);

   return iRet;
} // end of stb2Inner()...



//
// new 04-12-03, from calcPrLog()
//
// Note: this version never has over-saturation problems; everything tends to white at the bright end automatically.
//
// dLen   -> dBrite (the value being targeted)
// dPress -> dPower (the value we seek to achieve dBrite)
//
// note: fractFilt is ignored by us; it's used by setTotBrite(), which calls us. [removed 08-26-03]
//
//
//
//
// 04-13-03: unsatEarly flag (defaults to 0). (out 04-17-03)
// 04-14-03: pRedOut, pGrnOut, pBluOut (default to NULL)
// 04-15-03: support other doSqrTot's.
// 04-17-03: internal re-arrangement; don't barf on positive unSaturate.
// 08-26-03: remove unused fractFilt
// 09-04-04: fractTwo (defaults to 0.0).
// 03-26-05: slope variables (default to 1.0).
// 02-09-07: possibly call new function...
// 08-10-07: accept negative unSaturate.
// 07-29-08 (jdl): oldValIn (defaults to -1.0).
// 08-02-08 (jdl): prebFactIn.
//
// qxsetTotBrit2()
//

int jColor::setTotBrit2(double newVal, double unSaturate, int doFixOverS, int doSqrTot, double *pRedOut, double *pGrnOut, double *pBluOut, double fractTwo, double slopeRed, double slopeGrn, double slopeBlu, double oldValIn, double prebFactIn) {

   // new 02-09-07: just call new function:
   int i;
   double dRed, dGrn, dBlu;
   double newRed, newGrn, newBlu;

   int iRed = this->getRed(); dRed = (double)iRed;
   int iGrn = this->getGrn(); dGrn = (double)iGrn;
   int iBlu = this->getBlu(); dBlu = (double)iBlu;

   i = stb2Inner(dRed, dGrn, dBlu, newVal, unSaturate, doFixOverS, doSqrTot, newRed, newGrn, newBlu, fractTwo, slopeRed, slopeGrn, slopeBlu, oldValIn, prebFactIn);

   if(i != 0) return NADA;

   if(pRedOut != NULL) *pRedOut = newRed;
   if(pGrnOut != NULL) *pGrnOut = newGrn;
   if(pBluOut != NULL) *pBluOut = newBlu;

   iRed = RANGE(roundy(newRed), 0, 255);
   iGrn = RANGE(roundy(newGrn), 0, 255);
   iBlu = RANGE(roundy(newBlu), 0, 255);

   this->setColors(iRed, iGrn, iBlu);

   return 0;
} // end of setTotBrit2()...








//
// new 02-12-03, from setTotBrite().  This is the first time we correct for doSqrTot.  See $nv/it.
//
// called by:
//    jColor::setTotBrite()
//    jgrLil::modifyColors()
//
// Input:
//    dRed, dGrn, dBlu: colors to be modified.
//    unSaturate: if 0.0, we return same as we got in; if 1.0, output colors are equal to each other (grey).
//    doSqrTot: 0 and 2 cause linear; all others cause square.
//    fractTwo: not used.
//    allowOverflow (defaults to 0): if non-zero, don't check for overflow.
//
// Output:
//    dRed, dGrn, dBlu: values are modified (if unSaturate != 0.0).
//
//
// 04-15-03: doSqrTot of 3 (max val; square energy if combining)
// 09-04-04: fractTwo (defaults to 0.0).
// 08-10-07: accept negative unSaturate.
// 09-03-07: reduce saturation to prevent overflow.
// 10-01-08 (jdl): allowOverflow (defaults to 0).
//
// qxunsatCorrect()
//

int unsatCorrect(double &dRed, double &dGrn, double &dBlu, double unSaturate, int doSqrTot, double fractTwo, int allowOverflow) {

   // sanity check:
   if(unSaturate == 0.0) return 0;         // no changes necessary.
   // if(unSaturate  < 0.0) return NADA;   // out 08-10-07 (jdl): we now accept negative unSaturate (adds saturation)

   const double dMaxAllow = 255.49;                 // 09-03-07
   const double eMaxAllow = dMaxAllow * dMaxAllow;  // 09-03-07

   if((doSqrTot == 0) || (doSqrTot == 2)) {
      // linear
      double dAvg = (dRed + dGrn + dBlu) / 3.0;

      // 08-10-07: if unSaturate is negative, make sure we don't try to saturate more than is possible:
      double dMin = MI3(dRed, dGrn, dBlu);
      double dNom = dAvg - dMin;                  // dNom means "denominator"
      if(dNom > 0.0) {
         double satMax = dAvg / dNom;
         double satAct = 1.0 - unSaturate;
         if(satAct > satMax) {
            // we've got a problem; we'd drive somebody into negative; NOT good!
            unSaturate = 1.0 - satMax;  // less negative
         } // if(satAct > satMax)
      } else {
         // shade of grey; we don't have to act restricted, 'cause no change will happen...
      } // !if(dNom > 0.0)
      // 08-10-07...

      if(allowOverflow == 0) {
         // always, before 10-01-08, and still default:
         // 09-03-07: reduce saturation to deal with overflow:
         double dMax = MA3(dRed, dGrn, dBlu);
         dNom = dMax - dAvg;                       // dNom means "denominator"
         if(dNom > 0.0) {
            double satMax = (dMaxAllow - dAvg) / dNom;
            double satAct = 1.0 - unSaturate;
            if(satAct > satMax) {
               unSaturate = 1.0 - satMax;  // less negative or more positive.
            } // if(satAct > satMax)
         } else {
            // shade of grey; we don't have to act restricted, 'cause no change will happen...
         } // !if(dNom > 0.0)
         // 09-03-07...
      } else {
         // new option 10-01-08: allow overflow...
      } // !if(allowOverflow == 0)

      dRed = dRed*(1.0 - unSaturate) + dAvg*unSaturate;
      dGrn = dGrn*(1.0 - unSaturate) + dAvg*unSaturate;
      dBlu = dBlu*(1.0 - unSaturate) + dAvg*unSaturate;

      // 08-10-07: don't let slight negatives get out:
      dRed = MAX(dRed, 0.0);
      dGrn = MAX(dGrn, 0.0);
      dBlu = MAX(dBlu, 0.0);
      // 08-10-07...
   } else if((doSqrTot == 1) || (doSqrTot == 3) || (doSqrTot == 4) || (doSqrTot == 5) || (doSqrTot == 6)) {
      // square: before 02-12-03 we used same logic as linear; see $nv/it for why this is BAD!
      double eRed = dRed * dRed;
      double eGrn = dGrn * dGrn;
      double eBlu = dBlu * dBlu;
      double eAvg = (eRed + eGrn + eBlu) / 3.0;

      // 08-10-07: if unSaturate is negative, make sure we don't try to saturate more than is possible:
      double eMin = MI3(eRed, eGrn, eBlu);
      double eNom = eAvg - eMin;                  // eNom means "denominator"
      if(eNom > 0.0) {
         double satMax = eAvg / eNom;
         double satAct = 1.0 - unSaturate;
         if(satAct > satMax) {
            // we've got a problem; we'd drive somebody into negative; NOT good!
            unSaturate = 1.0 - satMax;  // less negative
         } // if(satAct > satMax)
      } else {
         // shade of grey; we don't have to act restricted, 'cause no change will happen...
      } // !if(eNom > 0.0)
      // 08-10-07...

      if(allowOverflow == 0) {
         // always, before 10-01-08, and still default:
         // 09-03-07: reduce saturation to deal with overflow:
         double eMax = MA3(eRed, eGrn, eBlu);
         eNom = eMax - eAvg;                       // eNom means "denominator"
         if(eNom > 0.0) {
            double satMax = (eMaxAllow - eAvg) / eNom;
            double satAct = 1.0 - unSaturate;
            if(satAct > satMax) {
               unSaturate = 1.0 - satMax;  // less negative or more positive.
            } // if(satAct > satMax)
         } else {
            // shade of grey; we don't have to act restricted, 'cause no change will happen...
         } // !if(eNom > 0.0)
         // 09-03-07...
      } else {
         // new option 10-01-08: allow overflow...
      } // !if(allowOverflow == 0)

      eRed = eRed*(1.0 - unSaturate) + eAvg*unSaturate;
      eGrn = eGrn*(1.0 - unSaturate) + eAvg*unSaturate;
      eBlu = eBlu*(1.0 - unSaturate) + eAvg*unSaturate;

      dRed = sqrt(MAX(eRed, 0.0));     // just for slight rounding errors...
      dGrn = sqrt(MAX(eGrn, 0.0));     // just for slight rounding errors...
      dBlu = sqrt(MAX(eBlu, 0.0));     // just for slight rounding errors...
   } else {
      // unrecognized doSqrTot:
      qprintf(fp_dbg, "doSqrTot of %d really pisses me off.\n", doSqrTot);
      // if(fp_std) mprintf(fp_std, "doSqrTot of %d really pisses me off.\n", doSqrTot);
      return NADA;
   } // !if(doSqrTot == 0)

   return 0;
} // end of unsatCorrect()...





//
// new 08-22-03.
//
//
//
//
//
// 09-04-04: doSqrTot 6 (we don't need fractTwo).
//
// qxisSquared()
//

int isSquared(int doSqrTot) {
   int iRet;

   if((doSqrTot == 0) || (doSqrTot == 2)) {
      iRet = 0;
   } else if((doSqrTot == 1) || (doSqrTot == 3) || (doSqrTot == 4) || (doSqrTot == 5) || (doSqrTot == 6)) {
      iRet = 1;
   } else {
      // should never happen...
      iRet = NADA;
   } // !if((doSqrTot == 0) || (doSqrTot == 2))

   return iRet;
} // end of isSquared()...




//
//
//
//
//
//
// qxsetColorFromOld()
//

void jColor::setColorFromOld(int oldColor) {

   switch(oldColor) {
      case 0:  // black
         setColors(0, 0, 0);
         break;
      case 1:  // dark blue
         setColors(0, 0, 128);
         break;
      case 2:  // dark green
         setColors(0, 128, 0);
         break;
      case 3:  // dark cyan
         setColors(0, 128, 128);
         break;
      case 4:  // dark red
         setColors(128, 0, 0);
         break;
      case 5:  // dark purple
         setColors(128, 0, 128);
         break;
      case 6:  // brown
         setColors(128, 128, 0);
         setColors(192, 128, 0);
         break;
      case 7:  // grey
         setColors(192, 192, 192);
         break;
      case 8:  // dark grey
         setColors(128, 128, 128);
         break;
      case 9:  // bright blue
         setColors(0, 0, 255);
         break;
      case 10:  // bright green
         setColors(0, 255, 0);
         break;
      case 11:  // bright cyan
         setColors(0, 255, 255);
         break;
      case 12:  // bright red
         setColors(255, 0, 0);
         break;
      case 13:  // bright purple
         setColors(255, 0, 255);
         break;
      case 14:  // yellow
         setColors(255, 255, 0);
         break;
      case 15:  // white
         setColors(255, 255, 255);
         break;
      default:
         // whaaa????
         setColors(128, 128, 128);  // dark grey...
         break;
   } // switch(oldColor)

   return;
} // end of setColorFromOld()





// qxdColor()

dColor::dColor() {
   m_redVal = 0.0;
   m_grnVal = 0.0;
   m_bluVal = 0.0;
}






dColor::dColor(double dRed, double dGrn, double dBlu) {
   setRed(dRed);
   setGrn(dGrn);
   setBlu(dBlu);
}




dColor::dColor(const dColor& rs) {  // copy constructor...
   m_redVal = rs.m_redVal;
   m_grnVal = rs.m_grnVal;
   m_bluVal = rs.m_bluVal;
} // end of dColor() copy constructor





// qx~dColor()

dColor::~dColor() {
} // end of ~dColor()





//
//
//
//
//
// qxdColor::operator=()
//

const dColor& dColor::operator=(const dColor& rs) {

   if(&rs != this) {
      m_redVal = rs.m_redVal;
      m_grnVal = rs.m_grnVal;
      m_bluVal = rs.m_bluVal;
   } // if(&rs != this)

   return *this;
} // end of dColor::operator=()...





//
//
//
//
//
// qxdColor::operator+=()
//

const dColor& dColor::operator+=(const dColor& rs) {

   m_redVal += rs.m_redVal;
   m_grnVal += rs.m_grnVal;
   m_bluVal += rs.m_bluVal;

   return *this;
} // end of dColor::operator+=()...





// qxdColor::dup_me()

dColor *dColor::dup_me() {
   dColor *newdColor = new dColor(*this);
   return newdColor;
}



//
// new 12-28-07
//
//
//
//
//
// qxgetCol()
//

double dColor::getCol(int iCol) const {
   double dRet = (double)NADA;

   switch(iCol) {
      case COL_RED:
         dRet = m_redVal;
         break;
      case COL_GRN:
         dRet = m_grnVal;
         break;
      case COL_BLU:
         dRet = m_bluVal;
         break;
      default:
         // bite me!  Leave it at NADA;
         break;
   } // switch(iCol)

   return dRet;
} // end of getCol()...




//
//
//
//
//
//
//
// qxsetCol()
//

void dColor::setCol(int iCol, double newVal) {

   switch(iCol) {
      case COL_RED:
         m_redVal = newVal;
         break;
      case COL_GRN:
         m_grnVal = newVal;
         break;
      case COL_BLU:
         m_bluVal = newVal;
         break;
      default:
         // bite me!  Don't do anything.
         break;
   } // switch(iCol)

   return;
} // end of setCol()...





//
//
//
//
//
//
//
// qxchgCol()
//

void dColor::chgCol(int iCol, double newVal) {

   switch(iCol) {
      case COL_RED:
         m_redVal += newVal;
         break;
      case COL_GRN:
         m_grnVal += newVal;
         break;
      case COL_BLU:
         m_bluVal += newVal;
         break;
      default:
         // bite me!  Don't do anything.
         break;
   } // switch(iCol)

   return;
} // end of chgCol()...







//
// new class 01-08-03; used to make a circle of pixels for pointillism.
//
//
//
//
// qxjLoc()

jLoc::jLoc() :
   m_iH(0),
   m_iV(0),
   m_badness(0.0)
                                      {
} // end of jLoc()...



jLoc::jLoc(const jLoc& rs) {  // copy constructor...
   m_iH       = rs.m_iH;
   m_iV       = rs.m_iV;
   m_badness  = rs.m_badness;
} // end of jLoc() (copy constructor)...



jLoc::~jLoc() {
   // nothing allocated...
} // end if ~jLoc()...






//
//
//
//
//
// qxjLoc::operator=()
//

const jLoc& jLoc::operator=(const jLoc& rs) {

   if(&rs != this) {
      m_iH       = rs.m_iH;
      m_iV       = rs.m_iV;
      m_badness  = rs.m_badness;
   } // if(&rs != this)

   return *this;
} // end of jLoc::operator=()...






//
// static function...
//
// NOTE: caller should delete [] what we return!!
//
//
//
//
// qxmakeCircle()
//

jLoc *jLoc::makeCircle(double rHctr, double rVctr, int nPixel) {
   int iH, iV;
   jLoc *pjl = NULL;

   // nPixel ~= dRad * dRad * PI
   double dRad = sqrt((double)nPixel / PI);

   int iHlo = (int)(rHctr - dRad);           // qxl: negative stuff?
   int iHhi = (int)(rHctr + dRad + 0.99);
   int iVlo = (int)(rVctr - dRad);           // qxl: negative stuff?
   int iVhi = (int)(rVctr + dRad + 0.99);

   int nNeeded = (iHhi - iHlo + 1) * (iVhi - iVlo + 1);

   JASSERT(nNeeded > nPixel);

   pjl = new jLoc[nNeeded];

   int indx = 0;
   for(iH=iHlo; iH<=iHhi; iH++) {
      for(iV=iVlo; iV<=iVhi; iV++) {
         pjl[indx].iH(iH);
         pjl[indx].iV(iV);
         double hOff = (double)iH - rHctr;
         double vOff = (double)iV - rVctr;
         double dBad = hOff * hOff   +   vOff * vOff;  // distance squared; not necessary to take sqrt()
         pjl[indx].badness(dBad);
         indx++;
      } // for(iV=iVlo; iV<=iVhi; iV++)
   } // for(iH=iHlo; iH<=iHhi; iH++)

   JASSERT(indx == nNeeded);

   // void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *, const void *);
   qsort((void *)pjl, (size_t)nNeeded, sizeof(jLoc), jLoc::qcmp_jLoc);

   return pjl;
} // end of makeCircle()...





//
// static function, for system version of qsort()
//
//
//
// qxqcmp_jLoc()
//

int jLoc::qcmp_jLoc(const void *jl0, const void *jl1) {
   const jLoc *pjl0 = (const jLoc *)jl0;
   const jLoc *pjl1 = (const jLoc *)jl1;

   double d0 = pjl0->badness();
   double d1 = pjl1->badness();
   if(d1 > d0) return -1;
   if(d1 < d0) return +1;

   // tie (unlikely!):
   return 0;
} // end of qcmp_jLoc()...









//
// Default constructor...
//
//
//
//
// qxjgrLil()

jgrLil::jgrLil() :
   m_colBackground(255,255,255), 
   m_colDrawPen(0,0,0),
   m_jpgQual(0),
   m_pngQual(0)
                                      {
   m_nHoriz    = 0;
   m_nVert     = 0;
   m_rowBytes  = 0;
   m_totBytes  = 0;
   m_doSqrTot   = 1;        // 04-23-02: to combine colors into tot brit
   m_oorResp    = 1;        // 06-04-02: out-of-range response.; default 0->1 10-03-04
   m_xScale    =  1.0;
   m_xOffset   =  0.0;
   m_yScale    =  1.0;
   m_yOffset   =  0.0;
   m_theData   = NULL;
   m_histBig   = NULL;
   m_histLil   = NULL;
   m_histLi2   = NULL;
   m_histp     = NULL;
   m_histq     = NULL;
   m_sourceFileName= NULL;  // 07-07-03
}



// qxjgrLil()

jgrLil::jgrLil(const jgrLil& rs) {  // copy constructor...
   int i;

   m_nHoriz = rs.m_nHoriz;
   m_nVert  = rs.m_nVert;
   m_rowBytes  = rs.m_rowBytes;
   m_totBytes  = rs.m_totBytes;
   m_doSqrTot  = rs.m_doSqrTot;
   m_oorResp   = rs.m_oorResp;
   m_xScale    = rs.m_xScale;
   m_xOffset   = rs.m_xOffset;
   m_yScale    = rs.m_yScale;
   m_yOffset   = rs.m_yOffset;
   m_colBackground  = rs.m_colBackground;
   m_colDrawPen     = rs.m_colDrawPen;
   m_theData = NULL;            // may be changed!
   m_histBig = NULL;            // may be changed!
   m_histLil = NULL;            // may be changed!
   m_histLi2 = NULL;            // may be changed!
   m_histp   = NULL;
   m_histq   = NULL;
   m_sourceFileName= NULL;      // may be changed!

   if(rs.m_theData != NULL) {
      if(this->OtherOwned() == 0) {
         // always, before 12-11-05:
         // int nBytes = m_nHoriz * m_nVert * colorBytesPerPixel();
         int nBytes = m_totBytes;
         m_theData = new UCHAR[nBytes];
         for(i=0; i<nBytes; i++) {
            m_theData[i] = rs.m_theData[i];
         } // for(i=0; i<nBytes; i++)
      } else {
         // new 12-11-05: we can both share this memory, owned by openCV:
         m_theData = rs.m_theData;
      } // !if(this->OtherOwned() == 0)
   } // if(rs.m_theData != NULL)

   // 07-07-03:
   if(rs.m_sourceFileName != NULL) {
      int nBytes = (int)strlen(rs.m_sourceFileName) + 1;
      m_sourceFileName = new char[nBytes];
#ifdef _WIN32      
      sprintf_s(m_sourceFileName, nBytes, "%s", rs.m_sourceFileName);
#else      
      sprintf(m_sourceFileName, "%s", rs.m_sourceFileName);      
#endif      
   } // if(rs.m_sourceFileName != NULL)

   // 08-30-08 (jdl):
   if(rs.m_histBig != NULL) {
      int nGuys = 256;
      m_histBig = new int[nGuys];
      for(i=0; i<nGuys; i++) {
         m_histBig[i] = rs.m_histBig[i];
      } // for(i=0; i<nGuys; i++)
   } // if(rs.m_histBig != NULL)
   if(rs.m_histLil != NULL) {
      int nGuys = 9;
      m_histLil = new int[nGuys];
      for(i=0; i<nGuys; i++) {
         m_histLil[i] = rs.m_histLil[i];
      } // for(i=0; i<nGuys; i++)
   } // if(rs.m_histLil != NULL)
   if(rs.m_histLi2 != NULL) {
      int nGuys = 9;
      m_histLi2 = new int[nGuys];
      for(i=0; i<nGuys; i++) {
         m_histLi2[i] = rs.m_histLi2[i];
      } // for(i=0; i<nGuys; i++)
   } // if(rs.m_histLi2 != NULL)
   if(rs.m_histp != NULL) {
      int nGuys = 7;
      m_histp = new int[nGuys];
      for(i=0; i<nGuys; i++) {
         m_histp[i] = rs.m_histp[i];
      } // for(i=0; i<nGuys; i++)
   } // if(rs.m_histp != NULL)
   if(rs.m_histq != NULL) {
      int nGuys = 7;
      m_histq = new int[nGuys];
      for(i=0; i<nGuys; i++) {
         m_histq[i] = rs.m_histq[i];
      } // for(i=0; i<nGuys; i++)
   } // if(rs.m_histq != NULL)
   // 08-30-08 (jdl)...


   this->m_calc1BrFunc = rs.m_calc1BrFunc;

} // end of jgrLil() copy constructor





//
// new 09-29-23
//
//
//
//
// qxjgrLil()

jgrLil::jgrLil(const jgrLil& rs, int ixLo, int ixTooHi, int iyLo, int iyTooHi)   // copy constructor...
: jgrLil()
{
   int iRed, iGrn, iBlu;

   int ixl = MAX(ixLo, 0);
   int ixh = MIN(ixTooHi, rs.nHoriz());
   int iyl = MAX(iyLo, 0);
   int iyh = MIN(iyTooHi, rs.nVert());

   int nx  = MAX(0, (ixh - ixl));
   int ny  = MAX(0, (iyh - iyl));

   if((nx > 0) && (ny > 0)) {
      this->setSize(nx, ny);

      for(int iy=0; iy<ny; iy++) {
         int iyrs = iy + iyl;

         for(int ix=0; ix<nx; ix++) {
            int ixrs = ix + ixl;
            int jj = rs.getOnePixel(ixrs, iyrs, iRed, iGrn, iBlu);
            int kk = this->setOnePixel(ix, iy, iRed, iGrn, iBlu);
         } // for(int ix=0; ix<nx; ix++)
      } // for(int iy=0; iy<ny; iy++)
   } // if((nx > 0) && (ny > 0))
} // jgrLil() clip crop copy constructor.





//
// new overload for re-scaling 08-25-03
//
//
//
//
//
// 09-22-03 (jdl): interpret high multFact as representing number of pixels in larger direction.
// 11-15-23 (jdl): try to support multFact > 1.0.
//
// qxjgrLil()

jgrLil::jgrLil(const jgrLil& rs, double multFact)    // size change copy constructor...
   // m_colBackground(255,255,255), 
   // m_colDrawPen(0,0,0)
                                      {
// stuff from default constructor (we'll calc actual horiz & vert below):
   double multBack = 1.0 / MAX(.0000001, multFact);   // ex: 0.33333 -> 2.9999999...
   qprintf(fp_dbg, "multFact %8.4f; multBack %8.4f\n", multFact, multBack);

   m_nHoriz    = 0;
   m_nVert     = 0;
   m_rowBytes  = 0;
   m_totBytes  = 0;
   m_doSqrTot   = 1;        // 04-23-02: to combine colors into tot brit
   m_oorResp    = 1;        // 06-04-02; out-of-range response.; default 0->1 10-03-04
   m_OtherOwned = 0;        // 12-11-05: when openCV data is shared with us.
   m_xScale    =  1.0;
   m_xOffset   =  0.0;
   m_yScale    =  1.0;
   m_yOffset   =  0.0;
   m_theData   = NULL;
   m_histBig   = NULL;
   m_histLil   = NULL;
   m_histLi2   = NULL;
   m_histp     = NULL;
   m_histq     = NULL;
   m_sourceFileName= NULL;  // 07-07-03
// end of stuff from default constructor...


   // int i;

   // from the original copy constructor, not much:
   m_colBackground  = rs.m_colBackground;
   m_colDrawPen     = rs.m_colDrawPen;
   m_doSqrTot       = rs.m_doSqrTot;

   int doSqrTot     = this->doSqrTot();
   int useSqrLogic  = isSquared(doSqrTot);

   // what's our new size?
   if(multFact <= 0.0) return;

   // new 09-22-03: change big multFact:
   if(multFact > 8.0) {
      // final number of pixels in larger axis.
      int iLarger = MAX(rs.nHoriz(), rs.nVert());
      if(iLarger > 0) multFact /= (double)iLarger;
   } // if(multFact > 8.0)

   // here's kind of a silly check, but possibly useful (later, we'll branch for <1.0 vs. >1.0):
   if(multFact == 1.0) {
      *this = rs;
      return;
   } // if(multFact <= 1.0)

   double rHnu = (double)rs.m_nHoriz * multFact;
   double rVnu = (double)rs.m_nVert  * multFact;

   if(rHnu < 1.0) return;
   if(rVnu < 1.0) return;

   // qxtemp: I'm gonna shut it off if it's too dang big!
   double rPix = rHnu * rVnu;  // actually, less than the resultant, 'cause we haven't rounded up yet, but close.

   // fprintf(stdout, "rPix = %12.2f\n", rPix);

   // if(rPix > FIVE_MEGA_PIXEL) return;  // qxl: some better notification, else I'll be hunting for this some day!
   if(rPix > HUND_MEGA_PIXEL) return;  // qxl: some better notification, else I'll be hunting for this some day!

   // fprintf(stdout, "Again, rPix = %12.2f\n", rPix);

   // ok, actual size will round off, how?  Is it really critical?
   int iHnu = (int)(rHnu + 0.9);   // usually up, occasionally down.
   int iVnu = (int)(rVnu + 0.9);   // usually up, occasionally down.

   this->setSize(iHnu, iVnu);  // allocates only data space, given our other settings.  Also clears the graph.

   // now we fill each of our squares from some combination of the other guy's:
   if(multFact < 1.0) {
      // expected: we're reducing the size of the pic.  Let's try to get rid of .333 type aliases, ok?
      // double multBack = 1.0 / multFact;   // ex: 0.33333 -> 2.9999999...
      int    iGuess = (int)(multBack + .00001);
      double dGuess = (double)iGuess;
      double absErr = ABS(multBack - dGuess);
      if(absErr < .00001) {
         // close enough.
         multBack = dGuess;
      } // if(absErr < .00001)

      double mbSqd = multBack * multBack;  // for area calcs below.

      int iH, iV;
      int iRed, iGrn, iBlu;

      // outer loops walk thru smaller new graph, then take content from several squares of the input graph:
      for(iV=0; iV<m_nVert; iV++) {
         double rVlow = (double)iV * multBack;
         double rVhii = (double)(iV + 1) * multBack;  // just slightly too high
         int    iVlow = (int)rVlow;             // round down
         int    iVhii = (int)rVhii;             // round down; I know it seems strange; see example below

         // int    iVhii = (int)(rVhii + .99999);  // round up
         double fVlow = 1.0 - (rVlow - (double)iVlow);  // thus 3.2 -> 0.8; see example below
         double fVhii = rVhii - (double)iVhii;          // thus 6.4 -> 0.4; see example below
         for(iH=0; iH<m_nHoriz; iH++) {
            double rHlow = (double)iH * multBack;
            double rHhii = (double)(iH + 1) * multBack;  // just slightly too high
            int    iHlow = (int)rHlow;             // round down
            int    iHhii = (int)rHhii;             // round down; I know it seems strange; see example below

            // int    iHhii = (int)(rHhii + .99999);  // round up
            double fHlow = 1.0 - (rHlow - (double)iHlow);  // thus 3.2 -> 0.8; see example below
            double fHhii = rHhii - (double)iHhii;          // thus 6.4 -> 0.4; see example below
            //
            // here's the thing.  Let's think about just making the output smaller than the input for a moment.
            // Each integer square projects forward to "almost" the next integer.  Say for example that
            // multFact is 1/3.2; that is, square 0 in the new (real range 0 .. .99999) covers 0 to 3.2 in
            // the old; this will be comprised of 100% of squares 0, 1, and 2, and 20% of square 3.  See?
            // The next square up, 1 in the new (real range 1.0 .. 1.99999) covers 3.2 to 6.4.
            // That is, 80% of 3, 100% of 4 and 5, 40% of 6.
            //
            // this->getOnePixel(    iH, iV, iRed, iGrn, iBlu);
            int iH2, iV2;
            double eRed, eGrn, eBlu;
            eRed = eGrn = eBlu = 0.0;

            // now we're walking thru multiple squares of the input graph to make this one square of the new guy:
            for(iV2=iVlow; iV2<=iVhii; iV2++) {
               double dvFac = 1.0;                // default.
               if(iV2 == iVlow) dvFac *= fVlow;
               if(iV2 == iVhii) dvFac *= fVhii;
               for(iH2=iHlow; iH2<=iHhii; iH2++) {
                  double dhFac = 1.0;                // default.
                  if(iH2 == iHlow) dhFac *= fHlow;
                  if(iH2 == iHhii) dhFac *= fHhii;
                  rs.getOnePixel(iH2, iV2, iRed, iGrn, iBlu);
                  if(useSqrLogic == 0) {
                     // old, linear:
                     eRed += (double)iRed * dvFac * dhFac;
                     eGrn += (double)iGrn * dvFac * dhFac;
                     eBlu += (double)iBlu * dvFac * dhFac;
                  } else {
                     // new, square:
                     eRed += (double)(iRed * iRed) * dvFac * dhFac;
                     eGrn += (double)(iGrn * iGrn) * dvFac * dhFac;
                     eBlu += (double)(iBlu * iBlu) * dvFac * dhFac;
                  } // !if(useSqrLogic == 0)
               } // for(iH2=iHlow; iH2<=iHhii; iH2++)
            } // for(iV2=iVlow; iV2<=iVhii; iV2++)

            // now to get the mean energy, we do this:
            eRed /= mbSqd;
            eGrn /= mbSqd;
            eBlu /= mbSqd;

            // change energy back into brightness values stored in graph:
            if(useSqrLogic == 0) {
               // old, linear; no change necessary.
            } else {
               // new, square:
               eRed = sqrt(eRed);   // slightly weerd, no longer energy...
               eGrn = sqrt(eGrn);   // slightly weerd, no longer energy...
               eBlu = sqrt(eBlu);   // slightly weerd, no longer energy...
            } // !if(useSqrLogic == 0)

            // ok, almost ready to put into new graph!
            iRed = roundy(eRed);
            iGrn = roundy(eGrn);
            iBlu = roundy(eBlu);

            this->setOnePixel(iH, iV, iRed, iGrn, iBlu);

         } // for(iH=0; iH<m_nHoriz; iH++)
      } // for(iV=0; iV<m_nVert; iV++)
   } else {
      // multFact > 1.0 
      double multBack = 1.0 / multFact;   // ex: 0.33333 -> 2.9999999...
      fprintf(stdout, "multFact %8.4f; multBack %8.4f\n", multFact, multBack);
      int iH, iV;
      int iRed, iGrn, iBlu;
      double dRed, dGrn, dBlu;

      // outer loops walk thru larger new graph, then take content from the input graph:
      for(iV=0; iV<m_nVert; iV++) {
         double rVlow = (double)iV * multBack;
         for(iH=0; iH<m_nHoriz; iH++) {
            double rHlow = (double)iH * multBack;
            rs.getOnePixeld(rHlow, rVlow, dRed, dGrn, dBlu);
            iRed = (int)(0.5 + dRed);
            iGrn = (int)(0.5 + dGrn);
            iBlu = (int)(0.5 + dBlu);

            this->setOnePixel(iH, iV, iRed, iGrn, iBlu);
         } // for(iH=0; iH<m_nHoriz; iH++)
      } // for(iV=0; iV<m_nVert; iV++)
   } // !if(multFact < 1.0) 


   // we're done, I suppose...
   return;
} // end of jgrLil() copy constructor with multFact...





// qx~jgrLil()

jgrLil::~jgrLil() {
   this->clear();
} // end of ~jgrLil()





//
// use the existing dimensions of "this"; stuff with the data of rs.  Aspect ratio change is possible.
//
//
//
// 07-21-25: quicker out if no size change.
//
// qxcpyChgSize()
//

int jgrLil::cpyChgSize(const jgrLil &rs) {
   int nxThis = this->nHoriz();
   int nyThis = this->nVert();
   int nxThat = rs.nHoriz();
   int nyThat = rs.nVert();

   if((nxThis < 2) || (nyThis < 2) || (nxThat < 2) || (nyThat < 2))
      return NADA;

   // quick out?
   if((nxThis == nxThat) && (nyThis == nyThat)) {
      *this = rs;
      return 0;
   } // if((nxThis == nxThat) && (nyThis == nyThat))

   double multBacx = (double)nxThat / (double)nxThis;
   double multBacy = (double)nyThat / (double)nyThis;

#ifdef MAYBE_NOT
   double multBack = 1.0 / multFact;   // ex: 0.33333 -> 2.9999999...
   int    iGuess = (int)(multBack + .00001);
   double dGuess = (double)iGuess;
   double absErr = ABS(multBack - dGuess);
   if(absErr < .00001) {
      // close enough.
      multBack = dGuess;
   } // if(absErr < .00001)
#endif // MAYBE_NOT

   // double mbSqd = multBack * multBack;  // for area calcs below.
   double mbSqd = multBacx * multBacy;  // for area calcs below.

   int doSqrTot     = this->doSqrTot();
   int useSqrLogic  = isSquared(doSqrTot);

   int iH, iV;
   int iRed, iGrn, iBlu;

   // outer loops walk thru smaller new graph, then take content from several squares of the input graph:
   for(iV=0; iV<m_nVert; iV++) {
      double rVlow = (double)iV * multBacy;
      double rVhii = (double)(iV + 1) * multBacy;  // just slightly too high
      int    iVlow = (int)rVlow;             // round down
      int    iVhii = (int)rVhii;             // round down; I know it seems strange; see example below

      // int    iVhii = (int)(rVhii + .99999);  // round up
      double fVlow = 1.0 - (rVlow - (double)iVlow);  // thus 3.2 -> 0.8; see example below
      double fVhii = rVhii - (double)iVhii;          // thus 6.4 -> 0.4; see example below
      for(iH=0; iH<m_nHoriz; iH++) {
         double rHlow = (double)iH * multBacx;
         double rHhii = (double)(iH + 1) * multBacx;  // just slightly too high
         int    iHlow = (int)rHlow;             // round down
         int    iHhii = (int)rHhii;             // round down; I know it seems strange; see example below

         // int    iHhii = (int)(rHhii + .99999);  // round up
         double fHlow = 1.0 - (rHlow - (double)iHlow);  // thus 3.2 -> 0.8; see example below
         double fHhii = rHhii - (double)iHhii;          // thus 6.4 -> 0.4; see example below
         //
         // here's the thing.  Let's think about just making the output smaller than the input for a moment.
         // Each integer square projects forward to "almost" the next integer.  Say for example that
         // multFact is 1/3.2; that is, square 0 in the new (real range 0 .. .99999) covers 0 to 3.2 in
         // the old; this will be comprised of 100% of squares 0, 1, and 2, and 20% of square 3.  See?
         // The next square up, 1 in the new (real range 1.0 .. 1.99999) covers 3.2 to 6.4.
         // That is, 80% of 3, 100% of 4 and 5, 40% of 6.
         //
         // this->getOnePixel(    iH, iV, iRed, iGrn, iBlu);
         int iH2, iV2;
         double eRed, eGrn, eBlu;
         eRed = eGrn = eBlu = 0.0;

         // now we're walking thru multiple squares of the input graph to make this one square of the new guy:
         for(iV2=iVlow; iV2<=iVhii; iV2++) {
            double dvFac = 1.0;                // default.
            if(iV2 == iVlow) dvFac *= fVlow;
            if(iV2 == iVhii) dvFac *= fVhii;
            for(iH2=iHlow; iH2<=iHhii; iH2++) {
               double dhFac = 1.0;                // default.
               if(iH2 == iHlow) dhFac *= fHlow;
               if(iH2 == iHhii) dhFac *= fHhii;
               rs.getOnePixel(iH2, iV2, iRed, iGrn, iBlu);
               if(useSqrLogic == 0) {
                  // old, linear:
                  eRed += (double)iRed * dvFac * dhFac;
                  eGrn += (double)iGrn * dvFac * dhFac;
                  eBlu += (double)iBlu * dvFac * dhFac;
               } else {
                  // new, square:
                  eRed += (double)(iRed * iRed) * dvFac * dhFac;
                  eGrn += (double)(iGrn * iGrn) * dvFac * dhFac;
                  eBlu += (double)(iBlu * iBlu) * dvFac * dhFac;
               } // !if(useSqrLogic == 0)
            } // for(iH2=iHlow; iH2<=iHhii; iH2++)
         } // for(iV2=iVlow; iV2<=iVhii; iV2++)

         // now to get the mean energy, we do this:
         eRed /= mbSqd;
         eGrn /= mbSqd;
         eBlu /= mbSqd;

         // change energy back into brightness values stored in graph:
         if(useSqrLogic == 0) {
            // old, linear; no change necessary.
         } else {
            // new, square:
            eRed = sqrt(eRed);   // slightly weerd, no longer energy...
            eGrn = sqrt(eGrn);   // slightly weerd, no longer energy...
            eBlu = sqrt(eBlu);   // slightly weerd, no longer energy...
         } // !if(useSqrLogic == 0)

         // ok, almost ready to put into new graph!
         iRed = roundy(eRed);
         iGrn = roundy(eGrn);
         iBlu = roundy(eBlu);

         this->setOnePixel(iH, iV, iRed, iGrn, iBlu);

      } // for(iH=0; iH<m_nHoriz; iH++)
   } // for(iV=0; iV<m_nVert; iV++)


   return 0;
} // int jgrLil::cpyChgSize(const jgrLil &rs)





// qxclear()

void jgrLil::clear() {
   this->clrSize();

   if(m_sourceFileName!= NULL) {delete [] m_sourceFileName; m_sourceFileName=NULL;}  // 07-07-03
} // end of jgrLil::clear()





//
//
//
//
//
// qxjgrLil::operator=()
//

const jgrLil& jgrLil::operator=(const jgrLil& rs) {
   if(&rs != this) {
      if(m_theData != NULL) {
         if(this->OtherOwned() == 0) {
            // always, before 12-11-05:
            delete [] m_theData; m_theData=NULL;
         } else {
            // new 12-11-05: don't delete it; we don't own it.
            m_theData=NULL;
         } // !if(this->OtherOwned() == 0)
      } // if(m_theData != NULL)
      clrHists();                                                                       // 08-30-08 (jdl)
      if(m_sourceFileName!= NULL) {delete [] m_sourceFileName; m_sourceFileName=NULL;}  // 07-07-03

      int i;

      m_nHoriz = rs.m_nHoriz;
      m_nVert  = rs.m_nVert;
      m_rowBytes  = rs.m_rowBytes;
      m_totBytes  = rs.m_totBytes;
      m_doSqrTot  = rs.m_doSqrTot;
      m_oorResp   = rs.m_oorResp;
      m_OtherOwned = rs.m_OtherOwned;
      m_xScale    = rs.m_xScale;
      m_xOffset   = rs.m_xOffset;
      m_yScale    = rs.m_yScale;
      m_yOffset   = rs.m_yOffset;
      m_colBackground  = rs.m_colBackground;
      m_colDrawPen     = rs.m_colDrawPen;
      if(rs.m_theData != NULL) {
         if(this->OtherOwned() == 0) {
            // always, before 12-11-05:
            // int nBytes = m_nHoriz * m_nVert * colorBytesPerPixel();
            int nBytes = m_totBytes;
            m_theData = new UCHAR[nBytes];
            for(i=0; i<nBytes; i++) {
               m_theData[i] = rs.m_theData[i];
            } // for(i=0; i<nBytes; i++)
         } else {
            // new 12-11-05: we can both share this memory, owned by openCV:
            m_theData = rs.m_theData;
         } // !if(this->OtherOwned() == 0)
      } // if(rs.m_theData != NULL)
      // 07-07-03:
      if(rs.m_sourceFileName != NULL) {
         int nBytes = (int)strlen(rs.m_sourceFileName) + 1;
         m_sourceFileName = new char[nBytes];
#ifdef _WIN32         
         sprintf_s(m_sourceFileName, nBytes, "%s", rs.m_sourceFileName);
#else         
         sprintf(m_sourceFileName, "%s", rs.m_sourceFileName);         
#endif         
      } // if(rs.m_sourceFileName != NULL)

      // 08-30-08 (jdl):
      if(rs.m_histBig != NULL) {
         int nGuys = 256;
         m_histBig = new int[nGuys];
         for(i=0; i<nGuys; i++) {
            m_histBig[i] = rs.m_histBig[i];
         } // for(i=0; i<nGuys; i++)
      } // if(rs.m_histBig != NULL)
      if(rs.m_histLil != NULL) {
         int nGuys = 9;
         m_histLil = new int[nGuys];
         for(i=0; i<nGuys; i++) {
            m_histLil[i] = rs.m_histLil[i];
         } // for(i=0; i<nGuys; i++)
      } // if(rs.m_histLil != NULL)
      if(rs.m_histLi2 != NULL) {
         int nGuys = 9;
         m_histLi2 = new int[nGuys];
         for(i=0; i<nGuys; i++) {
            m_histLi2[i] = rs.m_histLi2[i];
         } // for(i=0; i<nGuys; i++)
      } // if(rs.m_histLi2 != NULL)
      if(rs.m_histp != NULL) {
         int nGuys = 7;
         m_histp = new int[nGuys];
         for(i=0; i<nGuys; i++) {
            m_histp[i] = rs.m_histp[i];
         } // for(i=0; i<nGuys; i++)
      } // if(rs.m_histp != NULL)
      if(rs.m_histq != NULL) {
         int nGuys = 7;
         m_histq = new int[nGuys];
         for(i=0; i<nGuys; i++) {
            m_histq[i] = rs.m_histq[i];
         } // for(i=0; i<nGuys; i++)
      } // if(rs.m_histq != NULL)
      // 08-30-08 (jdl)...


      this->m_calc1BrFunc = rs.m_calc1BrFunc;

   } // if(&rs != this)

   return *this;
} // end of jgrLil::operator=()...





//
// new 01-30-02
//
//
// qxnow: fix totbr
//
//
//
// qxjgrLil::operator+=()
//

const jgrLil& jgrLil::operator+=(const jgrLil& rs) {
   int i;

   if(this->isSameSize(rs)) {
      // the two graphs are close enough for this operation:
      if((rs.m_theData != NULL) && (this->m_theData != NULL)) {
         // (the two should always go together...)

         int nBytes = m_totBytes;
         // m_theData = new UCHAR[nBytes];
         for(i=0; i<nBytes; i++) {
            int iNew = (int)m_theData[i] + (int)rs.m_theData[i];
            iNew = MIN(iNew, 255);
            m_theData[i] = (UCHAR)iNew;
         } // for(i=0; i<nBytes; i++)
      } // if((rs.m_theData != NULL) && (this->m_theData != NULL)) 

   } else {
      // just do nothing.
   } // if(this->isSameSize(rs))

   return *this;
} // end of jgrLil::operator+=()...


//#ifdef NOT_HEER
//#endif // NOT_HEER



// qxjgrLil::dup_me()

jgrLil *jgrLil::dup_me() {
   jgrLil *newjgrLil = new jgrLil(*this);
   return newjgrLil;
}





//
//
//
//
// qxjgrLil::memoryUnderMySway()
//

int jgrLil::memoryUnderMySway() const {
   int myMem = sizeof(jgrLil);

   if(m_theData != NULL) {
      if(this->OtherOwned() == 0) {
         // always, before 12-11-05:
         // int nBytes = m_nHoriz * m_nVert * colorBytesPerPixel();
         int nBytes = m_totBytes;
         myMem += nBytes * sizeof(UCHAR);
      } else {
         // new 12-11-05: we can both share this memory, owned by openCV; do not add anything.
         myMem += 0;
      } // !if(this->OtherOwned() == 0)
   } // if(m_theData != NULL)
   // 07-07-03:
   if(m_sourceFileName != NULL) {
      myMem += (int)strlen(m_sourceFileName) + 1;
   } // if(m_sourceFileName != NULL)

   // 08-30-08 (jdl):
   if(m_histBig != NULL) {
      int nGuys = 256;
      myMem += nGuys * sizeof(int);
   } // if(m_histBig != NULL)
   if(m_histLil != NULL) {
      int nGuys = 9;
      myMem += nGuys * sizeof(int);
   } // if(m_histLil != NULL)
   if(m_histLi2 != NULL) {
      int nGuys = 9;
      myMem += nGuys * sizeof(int);
   } // if(m_histp != NULL)
   if(m_histp != NULL) {
      int nGuys = 7;
      myMem += nGuys * sizeof(int);
   } // if(m_histp != NULL)
   if(m_histq != NULL) {
      int nGuys = 7;
      myMem += nGuys * sizeof(int);
   } // if(m_histq != NULL)
   // 08-30-08 (jdl)...

//#ifdef NOT_HEER
//#endif // NOT_HEER

   return myMem;
} // end of memoryUnderMySway()...









//
// includes tile check.  But shapings and tiling expression do not need
// to be the same.  Called by operator+=().
//
//
//
// qxisSameSize()
//
int jgrLil::isSameSize(const jgrLil& rs) {

   if(this->nHoriz() != rs.nHoriz()) return 0;
   if(this->nVert() != rs.nVert()) return 0;

   return 1;
} // end of isSameSize()...





//
// new 07-07-03
//
//
//
//
//
// qxsourceFileName()
//

void jgrLil::sourceFileName(const char *newName) {
   if(m_sourceFileName != NULL) {delete [] m_sourceFileName; m_sourceFileName = NULL;}

   if(newName != NULL) {
      m_sourceFileName = new char[strlen(newName)+1];
#ifdef _WIN32      
      strcpy_s(m_sourceFileName, strlen(newName)+1, newName);
#else      
      strcpy(m_sourceFileName, newName);      
#endif      
   } // if(newName != NULL)

   return;
} // end of sourceFileName()...




//
// new 07-07-03
//
//
//
//
//
// qxsourceFileName()
//

const char *jgrLil::sourceFileName() const {
   static int iFirst = 1;
   // static char *cSorry = "[sorry...]";
   static char cSorry[16];

   if(iFirst) {
#ifdef _WIN32   
      sprintf_s(cSorry, 16, "%s", "[sorry...]");
#else      
      sprintf(cSorry, "%s", "[sorry...]");      
#endif      
      iFirst = 0;
   } // if(iFirst)

   const char *cRet = cSorry;

   // apparently, these are BOTH bad - why???
   // const static char *theThing0[] = "bozo";
   // static const char *theThing1[] = "boxo";

   if(m_sourceFileName != NULL) {
      cRet = m_sourceFileName;
   } // if(m_sourceFileName != NULL)

   return cRet;
} // end of sourceFileName()...






//
// new 08-30-08 (jdl)
//
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxhistLil()
//

int jgrLil::histLil(int iSlot) const {
   // sanity checks:
   if(iSlot < 0)    return NADA;
   if(iSlot > 8)    return NADA;

   if(m_histLil == NULL) makeHists();

   int iRet = m_histLil[iSlot];

   return iRet;
} // end of histLil()...




//
// new 07-04-13 (jdl)
//
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxhistLi2()
//

int jgrLil::histLi2(int iSlot) const {
   // sanity checks:
   if(iSlot < 0)    return NADA;
   if(iSlot > 8)    return NADA;

   if(m_histLi2 == NULL) makeHists();

   int iRet = m_histLi2[iSlot];

   return iRet;
} // end of histLi2()...




//
// new 08-30-08 (jdl)
//
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxhistBig()
//

int jgrLil::histBig(int iSlot) const {
   // sanity checks:
   if(iSlot < 0)    return NADA;
   if(iSlot >= 256) return NADA;

   if(m_histBig == NULL) makeHists();

   int iRet = m_histBig[iSlot];

   return iRet;
} // end of histBig()...




//
// new (late!) 09-27-23
//
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxhistp()
//

int jgrLil::histp(int iSlot) const {
   // sanity checks:
   if(iSlot < 0)    return NADA;
   if(iSlot >= 7) return NADA;

   if(m_histp == NULL) makeHists();

   int iRet = m_histp[iSlot];

   return iRet;
} // end of histp()...




//
// new (late!) 09-27-23
//
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxhistq()
//

int jgrLil::histq(int iSlot) const {
   // sanity checks:
   if(iSlot < 0)    return NADA;
   if(iSlot >= 7) return NADA;

   if(m_histq == NULL) makeHists();

   int iRet = m_histq[iSlot];

   return iRet;
} // end of histq()...




//
// new 08-30-08 (jdl)
//
// Note: we deliberately do NOT check to see if hists already exist; caller should do that.
//
// Output:
//    m_histBig[]: array of 256: number of pixel-colors with that value (0 thru 255).
//    m_histLil[]: array of 9: number of pixel-colors with that number of bits used (ex: 128 thru 255 have 8 bits used).
//    m_histLi2[]: array of 9: like m_histLil, but reduced for pegged values.
//    m_histp[]:   array of 7: p-controlled count.
//    m_histq[]:   array of 7: q-controlled count.
//
//
//
// 07-04-13 (jdl): m_histLi2.
// 10-08-25: make const (using mutable variables)
//
// qxmakeHists()
//

int jgrLil::makeHists() const {
   int i;

   clrHists();

   if(m_nHoriz <= 0)     return NADA;
   if(m_nVert  <= 0)     return NADA;
   if(m_theData == NULL) return NADA;

   const int *pBits = getnBitsArr256();
   const int *qBits = getqBitsArr256();

   // sample all of them:
   const int nBig = 256;
   const int nLil =   9;     // 0 .. 8
   const int nLpq =   7;     // 0 .. 6
   m_histBig = new int[nBig];
   m_histLil = new int[nLil];
   m_histLi2 = new int[nLil];                  // 07-04-13 (jdl): lower for pegged values.
   m_histp   = new int[nLpq];                  // 07-05-13 (jdl): lower for pegged values.
   m_histq   = new int[nLpq];                  // 07-05-13 (jdl): lower for pegged values.
   for(i=0; i<nBig; i++) {m_histBig[i] = 0;}
   for(i=0; i<nLil; i++) {m_histLil[i] = 0;}
   for(i=0; i<nLil; i++) {m_histLi2[i] = 0;}
   for(i=0; i<nLpq; i++) {m_histp[i]   = 0;}
   for(i=0; i<nLpq; i++) {m_histq[i]   = 0;}
   int ihBytes = m_nHoriz * colorBytesPerPixel();   // may be less than m_rowBytes, which is rounded up.
   UCHAR *pH, *pV, *pHpast, *pVpast;

   for(pV=m_theData, pVpast=pV+m_totBytes; pV<pVpast; pV+=m_rowBytes) {
      for(pH=pV, pHpast=pH+ihBytes; pH<pHpast; pH++) {
         int iThisVal = *pH;
         m_histBig[iThisVal]++;
      } // for(iHb=0; iHb<ihBytes; iHb++)
   } // for(iV=0; iV<m_nVert; iV++)

   // it's quicker to fill the Lil arrays with just 256 passes!
   for(i=0; i<256; i++) {
      int numOf = m_histBig[i];
      int nbitOf = pBits[i];
      m_histLil[nbitOf] += numOf;
      int nbitO2 = qBits[i] + 1;       // a dummy "1-based" value to line up with pBits[].
      int nbitMin = MIN(nbitOf, nbitO2);
      m_histLi2[nbitMin] += numOf;
      if(i < 0x80) {
         // governed by original p-values.
         int pIndex = MAX(0, (pBits[i] - 1));
         JASSERT(pIndex >= 0);
         JASSERT(pIndex <  7);
         m_histp[pIndex] += numOf;
      } else {
         // governed by new q-values.
         int qIndex = qBits[i];
         JASSERT(qIndex >= 0);
         JASSERT(qIndex <  7);
         m_histq[qIndex] += numOf;
      } // !if(i < 0x40)
   } // for(i=0; i<256; i++)

   return 0;
} // end of makeHists()...





//
// new 08-30-08 (jdl)
//
//
//
// 10-08-25: make const (using mutable variables)
//
// qxclrHists()
//

int jgrLil::clrHists() const {

   // 10-08-25: we rely on all or none being NULL.
   if(m_histBig == NULL)
      return 0;

   delete [] m_histBig; m_histBig=NULL;
   delete [] m_histLil; m_histLil=NULL;
   delete [] m_histLi2; m_histLi2=NULL;
   delete [] m_histp;   m_histp  =NULL;
   delete [] m_histq;   m_histq  =NULL;

   return 5;

#ifdef SLOW_WAY
   int nDel = 0;

   if(m_histBig != NULL) {
      delete [] m_histBig;
      m_histBig = NULL;
      nDel++;
   } // if(m_histBig != NULL)

   if(m_histLil != NULL) {
      delete [] m_histLil;
      m_histLil = NULL;
      nDel++;
   } // if(m_histLil != NULL)

   if(m_histLi2 != NULL) {
      delete [] m_histLi2;
      m_histLi2 = NULL;
      nDel++;
   } // if(m_histLi2 != NULL)

   if(m_histp != NULL) {
      delete [] m_histp;
      m_histp = NULL;
      nDel++;
   } // if(m_histp != NULL)

   if(m_histq != NULL) {
      delete [] m_histq;
      m_histq = NULL;
      nDel++;
   } // if(m_histq != NULL)

   return nDel;
#endif // SLOW_WAY
} // end of clrHists()...







//
//
//
//
// 12-24-01: pass by reference, not value
// 06-04-02: oorResp
// 11-16-04: const jColor.
//
// qxsetOnePixel()
//

int jgrLil::setOnePixel(int iHoriz, int iVert, const jColor &aColor) {

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return setOnePixel(0, iVert, aColor);
         if(iHoriz >= m_nHoriz) return setOnePixel(m_nHoriz-1, iVert, aColor);
         if(iVert  < 0)         return setOnePixel(iHoriz, 0, aColor);
         if(iVert  >= m_nVert)  return setOnePixel(iHoriz, m_nVert-1, aColor);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return setOnePixel(-iHoriz, iVert, aColor);
         // if(iHoriz >= m_nHoriz) return setOnePixel(2*m_nHoriz-1-iHoriz, iVert, aColor);
         // if(iVert  < 0)         return setOnePixel(iHoriz, -iVert, aColor);
         // if(iVert  >= m_nVert)  return setOnePixel(iHoriz, 2*m_nVert-1-iVert, aColor);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())


   // dots begin in lower left; go to right, then up 1 row...
   // int iLoc = (iVert * m_nHoriz + iHoriz) * colorBytesPerPixel();
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // order in big array (in .bmp file) is backward:
   m_theData[iLoc]   = (UCHAR)aColor.getBlu();
   m_theData[iLoc+1] = (UCHAR)aColor.getGrn();
   m_theData[iLoc+2] = (UCHAR)aColor.getRed();

   clrHists();

   return 0;
} // end of setOnePixel()






//
// new overload 12-25-01
//
//
//
// 06-04-02: oorResp
//
// qxsetOnePixel()
//

int jgrLil::setOnePixel(int iHoriz, int iVert, int iRed, int iGrn, int iBlu) {

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return setOnePixel(0, iVert, iRed, iGrn, iBlu);
         if(iHoriz >= m_nHoriz) return setOnePixel(m_nHoriz-1, iVert, iRed, iGrn, iBlu);
         if(iVert  < 0)         return setOnePixel(iHoriz, 0, iRed, iGrn, iBlu);
         if(iVert  >= m_nVert)  return setOnePixel(iHoriz, m_nVert-1, iRed, iGrn, iBlu);
         break;
      case 1:
         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())



   // dots begin in lower left; go to right, then up 1 row...
   // int iLoc = (iVert * m_nHoriz + iHoriz) * colorBytesPerPixel();
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // 02-05-02: range thing:
   iRed = RANGE(iRed, 0, 255);
   iGrn = RANGE(iGrn, 0, 255);
   iBlu = RANGE(iBlu, 0, 255);

   // order in big array (in .bmp file) is backward:
   m_theData[iLoc]   = (UCHAR)iBlu;
   m_theData[iLoc+1] = (UCHAR)iGrn;
   m_theData[iLoc+2] = (UCHAR)iRed;

   clrHists();

   return 0;
} // end of setOnePixel()







//
// new 12-13-10 (jdl), from setOnePixel()
//
//
//
//
//
//
// qxchgOnePixel()
//

int jgrLil::chgOnePixel(int iHoriz, int iVert, const jColor &aColor) {

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return chgOnePixel(0, iVert, aColor);
         if(iHoriz >= m_nHoriz) return chgOnePixel(m_nHoriz-1, iVert, aColor);
         if(iVert  < 0)         return chgOnePixel(iHoriz, 0, aColor);
         if(iVert  >= m_nVert)  return chgOnePixel(iHoriz, m_nVert-1, aColor);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return chgOnePixel(-iHoriz, iVert, aColor);
         // if(iHoriz >= m_nHoriz) return chgOnePixel(2*m_nHoriz-1-iHoriz, iVert, aColor);
         // if(iVert  < 0)         return chgOnePixel(iHoriz, -iVert, aColor);
         // if(iVert  >= m_nVert)  return chgOnePixel(iHoriz, 2*m_nVert-1-iVert, aColor);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())

   int iRedNu = aColor.getRed();
   int iGrnNu = aColor.getGrn();
   int iBluNu = aColor.getBlu();
   jColor gColor;

   switch(this->combineRule()) {
      case COMBINE_OVERWRITE:
         // no change...
         break;
      case COMBINE_ADD:
         // why can't they leave me alone??
         this->getOnePixel(iHoriz, iVert, gColor);
         if(this->isSquary() == 0) {
            iRedNu += gColor.getRed();
            iGrnNu += gColor.getGrn();
            iBluNu += gColor.getBlu();
         } else {
            iRedNu = roundy(sqrt((double)(iRedNu * iRedNu   +   gColor.getRed() * gColor.getRed())));
            iGrnNu = roundy(sqrt((double)(iGrnNu * iGrnNu   +   gColor.getGrn() * gColor.getGrn())));
            iBluNu = roundy(sqrt((double)(iBluNu * iBluNu   +   gColor.getBlu() * gColor.getBlu())));
         } // if(this->isSquary() == 0)
         break;
      case COMBINE_SUBTRACT:
         this->getOnePixel(iHoriz, iVert, gColor);
         if(this->isSquary() == 0) {
            iRedNu = roundy((double)(iRedNu * gColor.getRed()) / MAX_BRITE1);
            iGrnNu = roundy((double)(iGrnNu * gColor.getGrn()) / MAX_BRITE1);
            iBluNu = roundy((double)(iBluNu * gColor.getBlu()) / MAX_BRITE1);
         } else {
            // same formula!
            iRedNu = roundy((double)(iRedNu * gColor.getRed()) / MAX_BRITE1);
            iGrnNu = roundy((double)(iGrnNu * gColor.getGrn()) / MAX_BRITE1);
            iBluNu = roundy((double)(iBluNu * gColor.getBlu()) / MAX_BRITE1);
         } // if(this->isSquary() == 0)
         break;
      case COMBINE_EOR:
         this->getOnePixel(iHoriz, iVert, gColor);
         if(this->isSquary() == 0) {
            iRedNu ^= gColor.getRed();
            iGrnNu ^= gColor.getGrn();
            iBluNu ^= gColor.getBlu();
         } else {
            // same formula!
            iRedNu ^= gColor.getRed();
            iGrnNu ^= gColor.getGrn();
            iBluNu ^= gColor.getBlu();
         } // if(this->isSquary() == 0)
         break;
      default:
         break;
   } // switch(this->combineRule())



   // dots begin in lower left; go to right, then up 1 row...
   // int iLoc = (iVert * m_nHoriz + iHoriz) * colorBytesPerPixel();
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // order in big array (in .bmp file) is backward:
   m_theData[iLoc]   = (UCHAR)RANGE(iBluNu, 0, 255);
   m_theData[iLoc+1] = (UCHAR)RANGE(iGrnNu, 0, 255);
   m_theData[iLoc+2] = (UCHAR)RANGE(iRedNu, 0, 255);

   clrHists();

   return 0;
} // end of chgOnePixel()







//
// new 01-30-02
//
// unlike setOnePixel(), we ADD to whatever's there.
//
//
//
// qxaddOnePixel()
//

int jgrLil::addOnePixel(int iHoriz, int iVert, int iRed, int iGrn, int iBlu) {

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return addOnePixel(0, iVert, iRed, iGrn, iBlu);
         if(iHoriz >= m_nHoriz) return addOnePixel(m_nHoriz-1, iVert, iRed, iGrn, iBlu);
         if(iVert  < 0)         return addOnePixel(iHoriz, 0, iRed, iGrn, iBlu);
         if(iVert  >= m_nVert)  return addOnePixel(iHoriz, m_nVert-1, iRed, iGrn, iBlu);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return addOnePixel(-iHoriz, iVert, iRed, iGrn, iBlu);
         // if(iHoriz >= m_nHoriz) return addOnePixel(2*m_nHoriz-1-iHoriz, iVert, iRed, iGrn, iBlu);
         // if(iVert  < 0)         return addOnePixel(iHoriz, -iVert, iRed, iGrn, iBlu);
         // if(iVert  >= m_nVert)  return addOnePixel(iHoriz, 2*m_nVert-1-iVert, iRed, iGrn, iBlu);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())


   // dots begin in lower left; go to right, then up 1 row...
   // int iLoc = (iVert * m_nHoriz + iHoriz) * colorBytesPerPixel();
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // order in big array (in .bmp file) is backward:
   m_theData[iLoc]   = (UCHAR)(MIN(255, iBlu + (int)m_theData[iLoc]));
   m_theData[iLoc+1] = (UCHAR)(MIN(255, iGrn + (int)m_theData[iLoc+1]));
   m_theData[iLoc+2] = (UCHAR)(MIN(255, iRed + (int)m_theData[iLoc+2]));

   clrHists();

   return 0;
} // end of addOnePixel()







//
// new 12-22-01
//
// data is returned in aColor, passed by reference.
//
//
//
// qxgetOnePixel()
//

int jgrLil::getOnePixel(int iHoriz, int iVert, jColor &aColor) const {

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return getOnePixel(0, iVert, aColor);
         if(iHoriz >= m_nHoriz) return getOnePixel(m_nHoriz-1, iVert, aColor);
         if(iVert  < 0)         return getOnePixel(iHoriz, 0, aColor);
         if(iVert  >= m_nVert)  return getOnePixel(iHoriz, m_nVert-1, aColor);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return getOnePixel(-iHoriz, iVert, aColor);
         // if(iHoriz >= m_nHoriz) return getOnePixel(2*m_nHoriz-1-iHoriz, iVert, aColor);
         // if(iVert  < 0)         return getOnePixel(iHoriz, -iVert, aColor);
         // if(iVert  >= m_nVert)  return getOnePixel(iHoriz, 2*m_nVert-1-iVert, aColor);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())


   // dots begin in lower left; go to right, then up 1 row...
   // int iLoc = (iVert * m_nHoriz + iHoriz) * colorBytesPerPixel();
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // order in big array (in .bmp file) is backward:
   aColor.setBlu((int)m_theData[iLoc]);
   aColor.setGrn((int)m_theData[iLoc+1]);
   aColor.setRed((int)m_theData[iLoc+2]);

   return 0;
} // end of getOnePixel()






//
// new overload 12-25-01
//
//
//
//
// qxgetOnePixel()
//

int jgrLil::getOnePixel(int iHoriz, int iVert, int &iRed, int &iGrn, int &iBlu) const {

   iRed = iGrn = iBlu = 0;

   // mprintf(fp_std, "This is jgrLil::getOnePixel() with nHoriz %d, nVert %d, iH %d, iV %d.\n", m_nHoriz, m_nVert, iHoriz, iVert);  // qxtemp!!

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return getOnePixel(0, iVert, iRed, iGrn, iBlu);
         if(iHoriz >= m_nHoriz) return getOnePixel(m_nHoriz-1, iVert, iRed, iGrn, iBlu);
         if(iVert  < 0)         return getOnePixel(iHoriz, 0, iRed, iGrn, iBlu);
         if(iVert  >= m_nVert)  return getOnePixel(iHoriz, m_nVert-1, iRed, iGrn, iBlu);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return getOnePixel(-iHoriz, iVert, iRed, iGrn, iBlu);
         // if(iHoriz >= m_nHoriz) return getOnePixel(2*m_nHoriz-1-iHoriz, iVert, iRed, iGrn, iBlu);
         // if(iVert  < 0)         return getOnePixel(iHoriz, -iVert, iRed, iGrn, iBlu);
         // if(iVert  >= m_nVert)  return getOnePixel(iHoriz, 2*m_nVert-1-iVert, iRed, iGrn, iBlu);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())


   // dots begin in lower left; go to right, then up 1 row...
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel());

   // order in big array (in .bmp file) is backward:
   iBlu = (int)m_theData[iLoc++];
   iGrn = (int)m_theData[iLoc++];
   iRed = (int)m_theData[iLoc++];

   return 0;
} // end of 2nd getOnePixel()...





//
// new overload 05-05-03: use doubles and averages.
//
// Notes 08-07-03.  Oh shit, I think I'm fucked up!!!   By half a pixel, bucking FIGHT me!
// The routine that calls this adds 0.5 to the integer pixel, so that row 0 gets a value of 0.5,
// midway in the square if you look at it in large scale.  To stay consistent with that logic,
// this routine should subtract 0.5 from the pixel passed, alas!  Bucking FIGHT me!
//
// Expressed another way: if dHoriz is 1074.5, we should draw exclusively from integer pixel
// 1074, NOT draw half from 1074 and half from 1075.
//
// The dilemma is: if we fix this, past work will be FUCKED.  Oh well, c'est la vie.  We change S/N.
// Anyway, 1/2 pixel error probably isn't the end of the world...
//
//
//
// 08-07-03 (jdl): subtract 0.5 from double pixels passed.  Note: this makes the program INCONSISTENT w/ past 
//                 functionality!!!
// 06-17-10 (jdl): rename; add 'd' to end.
//
// qxgetOnePixeld()
//

int jgrLil::getOnePixeld(double dHoriz, double dVert, double &dRed, double &dGrn, double &dBlu) const {
   int ixLo, ixHi, iyLo, iyHi;
   double factLowx, factHigx, factLowy, factHigy;

   // 08-07-03: correct for adding of 0.5 to pixel number on calling side!
   dHoriz -= 0.5;   // 08-07-03
   dVert  -= 0.5;   // 08-07-03
   // end of 08-07-03: correct for adding of 0.5 to pixel number on calling side!

   int doSqrTot = this->doSqrTot();  // 0: linear; 1: square (favored); 2: max; etc.

   dRed = dGrn = dBlu = 0.0;

   ixLo = (int)floor(dHoriz);
   ixHi = ixLo + 1;
   factHigx = dHoriz - (double)ixLo;
   factLowx = 1.0 - factHigx;

   iyLo = (int)floor(dVert);
   iyHi = iyLo + 1;
   factHigy = dVert - (double)iyLo;
   factLowy = 1.0 - factHigy;

   int iRed, iGrn, iBlu;
   switch(doSqrTot) {
      case 1:
      case 3:
      case 4:
      case 5:
         // combine squares:
         this->getOnePixel(ixLo, iyLo, iRed, iGrn, iBlu);
         dRed += factLowx * factLowy * (double)(iRed * iRed);
         dGrn += factLowx * factLowy * (double)(iGrn * iGrn);
         dBlu += factLowx * factLowy * (double)(iBlu * iBlu);

         this->getOnePixel(ixLo, iyHi, iRed, iGrn, iBlu);
         dRed += factLowx * factHigy * (double)(iRed * iRed);
         dGrn += factLowx * factHigy * (double)(iGrn * iGrn);
         dBlu += factLowx * factHigy * (double)(iBlu * iBlu);

         this->getOnePixel(ixHi, iyLo, iRed, iGrn, iBlu);
         dRed += factHigx * factLowy * (double)(iRed * iRed);
         dGrn += factHigx * factLowy * (double)(iGrn * iGrn);
         dBlu += factHigx * factLowy * (double)(iBlu * iBlu);

         this->getOnePixel(ixHi, iyHi, iRed, iGrn, iBlu);
         dRed += factHigx * factHigy * (double)(iRed * iRed);
         dGrn += factHigx * factHigy * (double)(iGrn * iGrn);
         dBlu += factHigx * factHigy * (double)(iBlu * iBlu);

         dRed = sqrt(dRed);
         dGrn = sqrt(dGrn);
         dBlu = sqrt(dBlu);

         break;
      case 0:
      case 2:
      default:
         // combine linearly:
         this->getOnePixel(ixLo, iyLo, iRed, iGrn, iBlu);
         dRed += factLowx * factLowy * (double)iRed;
         dGrn += factLowx * factLowy * (double)iGrn;
         dBlu += factLowx * factLowy * (double)iBlu;

         this->getOnePixel(ixLo, iyHi, iRed, iGrn, iBlu);
         dRed += factLowx * factHigy * (double)iRed;
         dGrn += factLowx * factHigy * (double)iGrn;
         dBlu += factLowx * factHigy * (double)iBlu;

         this->getOnePixel(ixHi, iyLo, iRed, iGrn, iBlu);
         dRed += factHigx * factLowy * (double)iRed;
         dGrn += factHigx * factLowy * (double)iGrn;
         dBlu += factHigx * factLowy * (double)iBlu;

         this->getOnePixel(ixHi, iyHi, iRed, iGrn, iBlu);
         dRed += factHigx * factHigy * (double)iRed;
         dGrn += factHigx * factHigy * (double)iGrn;
         dBlu += factHigx * factHigy * (double)iBlu;

         break;
   } // switch(doSqrTot)

   return 0;
} // end of getOnePixeld()  // was 3rd getOnePixel()...






//
// new 08-15-09, from getOnePixel()
//
//
//
//
// qxgetOneColor()
//

int jgrLil::getOneColor(int iHoriz, int iVert, int iColor) const {
   int iRet = NADA;

   // mprintf(fp_std, "This is jgrLil::getOneColor() with nHoriz %d, nVert %d, iH %d, iV %d.\n", m_nHoriz, m_nVert, iHoriz, iVert);  // qxtemp!!

   // 06-04-02: make sure the graph has some size:
   if(m_nHoriz <= 0)      return NADA;
   if(m_nVert  <= 0)      return NADA;

   switch(this->oorResp()) {
      case NADA:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
      case 0:
         // 12-24-01: don't abort; fill in at the edge:
         if(iHoriz < 0)         return getOneColor(0, iVert, iColor);
         if(iHoriz >= m_nHoriz) return getOneColor(m_nHoriz-1, iVert, iColor);
         if(iVert  < 0)         return getOneColor(iHoriz, 0, iColor);
         if(iVert  >= m_nVert)  return getOneColor(iHoriz, m_nVert-1, iColor);
         break;
      case 1:
         // 06-04-02: imaginary mirrored tiling of image to infinite space:
         // if(iHoriz < 0)         return getOneColor(-iHoriz, iVert, iColor);
         // if(iHoriz >= m_nHoriz) return getOneColor(2*m_nHoriz-1-iHoriz, iVert, iColor);
         // if(iVert  < 0)         return getOneColor(iHoriz, -iVert, iColor);
         // if(iVert  >= m_nVert)  return getOneColor(iHoriz, 2*m_nVert-1-iVert, iColor);

         // new function call 04-23-03, avoids recursion:
         iHoriz = fnMirrorOvf(m_nHoriz, iHoriz);
         iVert  = fnMirrorOvf(m_nVert,  iVert);
         break;
      default:
         if(iHoriz < 0)         return NADA;
         if(iHoriz >= m_nHoriz) return NADA;
         if(iVert  < 0)         return NADA;
         if(iVert  >= m_nVert)  return NADA;
         break;
   } // switch(this->oorResp())


   // dots begin in lower left; go to right, then up 1 row...
   // color order in big array is backward (blu, grn, red instead of red, grn, blu):
   int iLoc = (iVert * m_rowBytes) + (iHoriz * colorBytesPerPixel()) + (2 - iColor); 

   iRet = (int)m_theData[iLoc];

   return iRet;
} // end of getOneColor()...






//
// new 01-29-02
//
// All values should be >= 0, and within the size of the jgrLil.
//
//
//
// 01-30-02: iAdd
//
// qxdrawLine()
//

int jgrLil::drawLine(double x0, double y0, double x1, double y1, int iRed, int iGrn, int iBlu, int iAdd) {
   int iX, iY;

   int ixMin = (int)(MIN(x0, x1) + 0.5);
   int ixMax = (int)(MAX(x0, x1) + 0.5);
   int iyMin = (int)(MIN(y0, y1) + 0.5);
   int iyMax = (int)(MAX(y0, y1) + 0.5);

   if(ABS(x1 - x0) > ABS(y1 - y0)) {
      // tends toward horizontal; work from that perspective
      for(iX=ixMin; iX<=ixMax; iX++) {
         iY = (int)(reMap(x0, x1, (double)iX, y0, y1) + 0.5);
         if(iAdd) {
            this->addOnePixel(iX, iY, iRed, iGrn, iBlu);
         } else {
            this->setOnePixel(iX, iY, iRed, iGrn, iBlu);
         } // !if(iAdd)
      } // for(iX=ixMin; iX<=ixMax; iX++)
   } else {
      // tends toward vertical; work from that perspective
      for(iY=iyMin; iY<=iyMax; iY++) {
         iX = (int)(reMap(y0, y1, (double)iY, x0, x1) + 0.5);
         if(iAdd) {
            this->addOnePixel(iX, iY, iRed, iGrn, iBlu);
         } else {
            this->setOnePixel(iX, iY, iRed, iGrn, iBlu);
         } // !if(iAdd)
      } // for(iY=iyMin; iY<=iyMax; iY++)
   } // !if(ABS(x1 - x0) > ABS(y1 - y0))

   return 0;
} // end of drawLine()...





//
//
//
// 12-11-23: clear any pad bytes to 0.
//
// qxclrGraph()
//

int jgrLil::clrGraph(void) {
   int iH, iV;

   int nPad = m_rowBytes - m_nHoriz * colorBytesPerPixel(); // 0, 1, 2, or 3, to flesh out a multiple of 4 bytes.

   for(iV=0; iV<m_nVert; iV++) {
      for(iH=0; iH<m_nHoriz; iH++) {
         setOnePixel(iH, iV, m_colBackground);
      } // for(iH=0; iH<m_nHoriz; iH++)

      if(nPad > 0) {
         UCHAR *pBegin = m_theData + (iV * m_rowBytes) + (m_nHoriz * colorBytesPerPixel());  // beginning of pad space, this line
         for(int i=0; i<nPad; i++)
            *(pBegin + i) = 0;

      } // if(nPad > 0)
   } // for(iV=0; iV<m_nVert; iV++)

   return 0;
} // end of clrGraph()





//
// new overload 10-04-08
//
//
//
//
//
// 12-11-23: clear any pad bytes to 0.
//
// qxclrGraph()
//

int jgrLil::clrGraph(int iRed, int iGrn, int iBlu) {
   int iH, iV;

   int nPad = m_rowBytes - m_nHoriz * colorBytesPerPixel(); // 0, 1, 2, or 3, to flesh out a multiple of 4 bytes.

   for(iV=0; iV<m_nVert; iV++) {
      for(iH=0; iH<m_nHoriz; iH++) {
         setOnePixel(iH, iV, iRed, iGrn, iBlu);
      } // for(iH=0; iH<m_nHoriz; iH++)

      if(nPad > 0) {
         UCHAR *pBegin = m_theData + (iV * m_rowBytes) + (m_nHoriz * colorBytesPerPixel());  // beginning of pad space, this line
         for(int i=0; i<nPad; i++)
            *(pBegin + i) = 0;

      } // if(nPad > 0)
   } // for(iV=0; iV<m_nVert; iV++)

   return 0;
} // end of clrGraph()





//
//
//
//
// 12-22-01: also set tiles.
// 12-27-01: more graceful for zero tileSize
// 10-26-02: don't do tile stuff here (we still clear any old tile stuff out).
// qxl: 03-09-04: take out call to fillTotbr()
// 12-11-05: pOther (defaults to NULL)
//
// qxsetSize()
//

int jgrLil::setSize(int nHoriz, int nVert, UCHAR *pOther) {


   qprintf(fp_dbg, "This is jgrLil::setSize() with time %d.\n", do_proc_time()); // 03-09-04; qxtemp?

   this->clrSize();

   if((nHoriz <= 0) || (nVert <= 0)) return NADA;


   int rowBytesLil = nHoriz * colorBytesPerPixel();
   int rowBytes = 4 * ((rowBytesLil+3) / 4);  // round up
   int nBytes = rowBytes * nVert;

   if(pOther == NULL) {
      // always, before 12-11-05:
      m_theData = new UCHAR[nBytes];
      m_OtherOwned = 0;
   } else {
      // new 12-11-05:
      m_theData = pOther;
      m_OtherOwned = 1;
   } // !if(pOther == NULL)


   qprintf(fp_dbg, "rowBytes = %d; nBytes = %d; m_theData at %p\n", rowBytes, nBytes, m_theData);

   if(m_theData == NULL) return NADA;

   // we succeeded:
   m_nHoriz = nHoriz;
   m_nVert  = nVert;
   m_rowBytes  = rowBytes;
   m_totBytes  = nBytes;

   // let's clear it, too!
   if(this->OtherOwned() == 0) {
      // always, before 12-11-05:
      clrGraph();
   } else {
      // new 12-11-05: dont' clear the data passed to us!
   } // !if(this->OtherOwned() == 0)


   int do_call = 0;  // 03-18-04
   if(do_call == 0) {
      if(fp_dbg) qprintf(fp_dbg, "jgrLil::setSize() not calling fillTotbr() at time %d.\n", do_proc_time()); // 03-09-04; qxtemp?
   } else {
      if(fp_dbg) qprintf(fp_dbg, "jgrLil::setSize() about to call fillTotbr() at time %d.\n", do_proc_time()); // 03-09-04; qxtemp?
//jmk8/22/05

      // 04-23-02: do the new totbr:
      qprintf(fp_dbg, "setSize() calling fillTotbr()...\n");

      qprintf(fp_dbg, "jgrLil::setSize() back from fillTotbr() at time %d.\n", do_proc_time()); // 03-09-04; qxtemp?
   } // !if(do_call == 0)

   return 0;
} // end of setSize()...





//
// new 12-24-01, from setSize().
//
//
//
// qxclrSize()
//

int jgrLil::clrSize() {
   // kill anything already there:
   if(m_theData != NULL) {
      if(this->OtherOwned() == 0) {
         // always, before 12-11-05:
         delete [] m_theData; m_theData=NULL;
      } else {
         // new 12-11-05: don't delete it; we don't own it.
         m_theData=NULL;
      } // !if(this->OtherOwned() == 0)
   } // if(m_theData != NULL)

   clrHists();                                                                       // 08-30-08 (jdl)

   // set everybody back to zeroish sizes:
   m_nHoriz = 0;
   m_nVert  = 0;
   m_rowBytes  = 0;
   m_totBytes  = 0;

   return 0;
} // end of clrSize()...






//
// new 12-21-01, for $pb project.
//
// See words in words.h; we don't want to play swap games when we
// think it's Intell order all the way.  OK?
//
//
//
// 10-26-02: don't do tiling from here.
// 09-27-03: recover dataLength when it does not match; see $nv/it.
// 11-11-23: barf if file does not begin with "BM".
//
// qxreadBmp()
//

int jgrLil::readBmp(const char *fileName) {
   int i;
   char char1, char2;
   FILE *fp_in = NULL;

   this->clrSize();  // 12-27-01 - just in case we fail.

   if(fileName == NULL) return NADA;

#ifdef _WIN32
   if(fopen_s(&fp_in, fileName, "rb") != 0) return NADA;
#else   
   if((fp_in = fopen(fileName, "rb")) == NULL) return NADA;   
#endif   


   // expect to find 'B' 'M' at the top of the file:
   if((i = (int)fread((void *)(&char1), sizeof(char), 1, fp_in)) != 1) {
      qprintf(stderr, "Unable to read first character from file.\n");
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }
   if((i = (int)fread((void *)(&char2), sizeof(char), 1, fp_in)) != 1) {
      qprintf(stderr, "Unable to read second character from file.\n");
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }

   // 11-11-23:
   if((char1 != 'B') || (char2 != 'M')) {
      fclose(fp_in); fp_in=NULL;
      return NADAfunc("jgrLil::readBmp(): incorrect initial bytes.");
   } // if((char1 != 'B') || (char2 != 'M'))

   int fullLength;
   int hdrLength;
   int pixWidth;
   int pixHeight;
   int dataLength;
   int magic0;
   int magic1;
   int dum0, dum1, dum2, dum3;

   if((i = (int)fread((void *)(&fullLength), sizeof(int), 1, fp_in)) != 1) {
      qprintf(stderr, "Unable to read fullLength from file.\n");
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }
   if((i = (int)fread((void *)(&dum0), sizeof(int), 1, fp_in)) != 1) {
      qprintf(stderr, "Unable to read dum0 from file.\n");
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }
   if((i = (int)fread((void *)(&hdrLength), sizeof(int), 1, fp_in)) != 1) {
      qprintf(stderr, "Unable to read hdrLength from file.\n");
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }

   if(hdrLength > 1000) {
      qprintf(stderr, "hdrLength of %d is too long.\n", hdrLength);
      qprintf(fp_dbg, "hdrLength of %d is too long.\n", hdrLength);
      fclose(fp_in); fp_in=NULL;
      return NADA;
   } // if(hdrLength > 1000)

   int lenRemain = hdrLength - 14;  // 14 bytes already read.
   if(lenRemain < 32) {
      qprintf(stderr, "lenRemain of %d is too short.\n", lenRemain);
      fclose(fp_in); fp_in=NULL;
      return NADA;
   } // if(lenRemain < 32)

   int nInt = lenRemain / sizeof(int);
   qprintf(fp_dbg, "lenRemain %d; nInt %d.\n", lenRemain, nInt); // qxtemp!

   // check pos lenRemain; check even multiples of 4 bytes.
   lenRemain -= nInt * sizeof(int);  // we hope it's now zero!

   SMARTA(pHdrRemain, int, nInt, smaRemain);


   if((i = (int)fread((void *)(pHdrRemain), sizeof(int), nInt, fp_in)) != nInt) {
      qprintf(stderr, "Unable to read %d bytes into pHdrRemain.\n", nInt*4);
      // delete [] pHdrRemain; pHdrRemain=NULL;
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }

   if(lenRemain > 0) {
      // read 1 to 3 bytes that wouldn't fit in another int:
      char cdum[4];
      if((i = (int)fread((void *)(cdum), sizeof(char), lenRemain, fp_in)) != lenRemain) {
         qprintf(stderr, "Unable to read %d bytes into cdum.\n", lenRemain);
         // delete [] pHdrRemain; pHdrRemain=NULL;
         fclose(fp_in); fp_in=NULL;
         return NADA;
      }
   } // if(lenRemain > 0)


   dum1        = pHdrRemain[0];
   pixWidth    = pHdrRemain[1];
   pixHeight   = pHdrRemain[2];
   dum2        = pHdrRemain[3];
   dum3        = pHdrRemain[4];
   dataLength  = pHdrRemain[5];
   magic0      = pHdrRemain[6];
   magic1      = pHdrRemain[7];




   qprintf(fp_dbg, "jgrLil::readBmp() we begin with chars \"%c%c\".\n", char1, char2);
   qprintf(fp_dbg, "dum0       = %d (%08x).\n", dum0, dum0);
   qprintf(fp_dbg, "fullLength = %d (%08x).\n", fullLength, fullLength);
   qprintf(fp_dbg, "hdrLength  = %d (%08x).\n", hdrLength, hdrLength);
   qprintf(fp_dbg, "dum1       = %d (%08x).\n", dum1, dum1);
   qprintf(fp_dbg, "pixWidth   = %d (%08x).\n", pixWidth, pixWidth);
   qprintf(fp_dbg, "pixHeight  = %d (%08x).\n", pixHeight, pixHeight);
   qprintf(fp_dbg, "dum2       = %d (%08x).\n", dum2, dum2);
   qprintf(fp_dbg, "dum3       = %d (%08x).\n", dum3, dum3);
   qprintf(fp_dbg, "dataLength = %d (%08x).\n", dataLength, dataLength);
   qprintf(fp_dbg, "magic0     = %d (%08x).\n", magic0, magic0);
   qprintf(fp_dbg, "magic1     = %d (%08x).\n", magic1, magic1);

   // 09-27-03: try to recover dataLength; paint.exe seems to save zero!
   if(fullLength != (dataLength + hdrLength)) {
      if(fp_dbg) qprintf(fp_dbg, "Bad fullLength vs. dataLength + hdrLength.\n");
      dataLength = fullLength - hdrLength;
      if(fp_dbg) qprintf(fp_dbg, "dataLength = %d (%08x).\n", dataLength, dataLength);
   } // if(fullLength != (dataLength + hdrLength))

   int nPix = pixWidth * pixHeight;
   double dBytesPerPix = (double)dataLength / (double)nPix;
   if(fp_dbg) qprintf(fp_dbg, "Bytes per pixel: %8.5f\n", dBytesPerPix);

   int nErr = 0;

   if(nErr > 0) {
      fclose(fp_in); fp_in=NULL;
      return NADA;
   } // if(nErr > 0)

   this->setSize(pixWidth, pixHeight);  // 10-26-02: no longer allocates tiles...

   if(this->totBytes() != dataLength) {
      if(fp_dbg) qprintf(fp_dbg, "Bad: totBytes() %d, dataLength %d.\n", this->totBytes(), dataLength);
      nErr++;
   } // if(this->totBytes() != dataLength)

   if(nErr > 0) {
      fclose(fp_in); fp_in=NULL;
      return NADA;
   } // if(nErr > 0)

   // I think it's ok to actually suck up the data:
   if((i = (int)fread((void *)(m_theData), sizeof(UCHAR), dataLength, fp_in)) != dataLength) {
      if(stderr) qprintf(stderr, "Unable to read %d bytes into m_theData; got %d.\n", dataLength, i);
      fclose(fp_in); fp_in=NULL;
      return NADA;
   }

   qprintf(fp_dbg, "readBmp() calling fillTotbr()...\n");


   fclose(fp_in); fp_in=NULL;

   qprintf(fp_dbg, "Leaving readBmp()...\n");


   return 0;
} // end of readBmp()...







//
//
//
// qxwriteBmp()
//

int jgrLil::writeBmp(const char *fileName) const {
   int iLenLil = m_totBytes;
   int iLenHed = 54;
   int iLenBig = iLenLil + iLenHed;  // see $nv/it: the header

   if(fileName == NULL)     return NADA;
   if(m_theData == NULL)    return NADA;
   if(iLenLil  <= 0)        return NADA;
   if(!isBmpName(fileName)) return NADA;

   arcMeDotty(fileName);             // in case this name exists.

   FILE *fp_out;
#ifdef _WIN32   
   if(fopen_s(&fp_out, fileName, "wb") != 0) return NADA;
#else   
   if((fp_out = fopen(fileName, "wb")) == NULL) return NADA;   
#endif   

   // the following is sleuthed; see $nv/it around 09-08-00:
   jgrLil::writeFixQtyLf(fp_out, 'B', 1);
   jgrLil::writeFixQtyLf(fp_out, 'M', 1);
   jgrLil::writeFixQtyLf(fp_out, iLenBig, 4);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, iLenHed, 4);
   jgrLil::writeFixQtyLf(fp_out, 0x28, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, m_nHoriz, 4);
   jgrLil::writeFixQtyLf(fp_out, m_nVert, 4);
   jgrLil::writeFixQtyLf(fp_out, 0x01, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x18, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, iLenLil, 4);
   jgrLil::writeFixQtyLf(fp_out, 0xC4, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x0E, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0xC4, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x0E, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);
   jgrLil::writeFixQtyLf(fp_out, 0x00, 1);


   fwrite(m_theData, iLenLil, 1, fp_out);



   fclose(fp_out);

   return 0;
} // end of writeBmp()






//
// new 02-05-04: will attempt to translate .jpg, etc.
//
//
//
//
// 07-20-25: png.
//
// qxreadFile()
//


int jgrLil::readFile(const char *fileName) {
   int i;
   int iRet;

   // sanity check(s):
   if(fileName == NULL) return NADA;

   if(isBmpName(fileName)) {
      iRet = this->readBmp(fileName);


   } else if(isJpgName(fileName)) {
#ifdef USE_FI
      // find some name not yet used:
      char cTmp[64];
      for(i=0; ; i++) {
#ifdef _WIN32      
         sprintf_s(cTmp, 64, "blah%03d.bmp", i);
#else         
         sprintf(cTmp, "blah%03d.bmp", i);         
#endif         
         if(file_t(cTmp) < 0) break;                // get out when we've found a guy who does not exist.
      } // for(i=0; ; i++)

      jpgToBmp(fileName, cTmp);
      iRet = this->readBmp(cTmp);
      remove(cTmp);
#else
      iRet = NADA;
#endif // !USE_FI


   } else if(isPngName(fileName)) {
#ifdef USE_FI
      // find some name not yet used:
      char cTmp[64];
      for(i=0; ; i++) {
#ifdef _WIN32      
         sprintf_s(cTmp, 64, "blah%03d.bmp", i);
#else         
         sprintf(cTmp, "blah%03d.bmp", i);         
#endif         
         if(file_t(cTmp) < 0) break;                // get out when we've found a guy who does not exist.
      } // for(i=0; ; i++)

      pngToBmp(fileName, cTmp);
      iRet = this->readBmp(cTmp);
      remove(cTmp);
#else
      iRet = NADA;
#endif // !USE_FI
   } else {
      // unrecognized name, poo!
      iRet = NADA;
   }

   return iRet;
} // end of readFile()...




//
// 02-05-04: will attempt to translate .jpg, etc.
//
//
//
// 12-22-06 (jdl): jpgQual().
// 07-20-25: png.
//
// qxwriteFile()
//

int jgrLil::writeFile(const char *fileName) const {
   int i;
   int iRet;

   // sanity check(s):
   if(fileName         == NULL) return NADA;
   if(strlen(fileName) == 0)    return NADA;

   if(isBmpName(fileName)) {
      iRet = this->writeBmp(fileName);


   } else if(isJpgName(fileName)) {
#ifdef USE_FI
      // find some name not yet used:
      char cTmp[64];
      for(i=0; ; i++) {
#ifdef _WIN32      
         sprintf_s(cTmp, 64, "blah%03d.bmp", i);
#else         
         sprintf(cTmp, "blah%03d.bmp", i);         
#endif         
         if(file_t(cTmp) < 0) break;                // get out when we've found a guy who does not exist.
      } // for(i=0; ; i++)

      iRet = this->writeBmp(cTmp);
      int force;                                               // 05-16-07 (jdl)
      bmpToJpg(cTmp, fileName, (force=0), this->jpgQual());    //  jpgQual() 12-22-06 (jdl)
      remove(cTmp);
#else
      iRet = NADA;
#endif // !USE_FI


   } else if(isPngName(fileName)) {
#ifdef USE_FI
      // find some name not yet used:
      char cTmp[64];
      for(i=0; ; i++) {
#ifdef _WIN32      
         sprintf_s(cTmp, 64, "blah%03d.bmp", i);
#else         
         sprintf(cTmp, "blah%03d.bmp", i);         
#endif         
         if(file_t(cTmp) < 0) break;                // get out when we've found a guy who does not exist.
      } // for(i=0; ; i++)

      iRet = this->writeBmp(cTmp);
      int force;
      bmpToPng(cTmp, fileName, (force=0), this->pngQual()); 
      remove(cTmp);
#else
      iRet = NADA;
#endif // !USE_FI
   } else {
      // unrecognized name, poo!
      iRet = NADA;
   }

   return iRet;
} // end of writeFile()...







//
// new 09-10-00, from MevBase version...
//
//
// we return the number of bytes...
// NOTE: the data is in REVERSE order, least significant first.
//
//
//
//
// qxjgrLil::formatFixQty()
// qxformatFixQty()
//

int jgrLil::formatFixQty(UCHAR *ubuf_out, int iQty, int nBytes) {
   int i;
   int iStill = iQty;
   int iThis;
   // UCHAR ubuf[16];
   UCHAR *ucp = ubuf_out;

   // sanity check:
   // if(iQty < 0)         return NADA;
   if(ubuf_out == NULL) return NADA;
   if(nBytes <  1)      return NADA;

   // just the number of bytes we ask for:
   for(i=0, iStill=iQty, ucp=ubuf_out; i<nBytes; i++, ucp++) {
      iThis  = iStill & 0xFF;
      iStill = iStill >> 8;
      // if(ucp != ubuf_out) iThis += 0x80;  // carry bit...
      *ucp   = (UCHAR)iThis;
   } 

   // return (ucp - ubuf_out);  // number of valid bytes...
   return nBytes;
} // end of formatFixQty()









//
// new 09-10-00, from MevBase version.
//
//
//
// return value: number of bytes written; NADA if bad...
//
// qxjgrLil::writeFixQty()
// qxwriteFixQty()
//

int jgrLil::writeFixQty(FILE *fp_out, int iQty, int nBytes) {
   int n;
   int ict = 0;
   // int iStill = iQty;
   // int iThis;
   // UCHAR ubuf[16];
   UCHAR *ucp;

   // sanity check:
   // if(iQty < 0)       return NADA;
   if(fp_out == NULL) return NADA;
   if(nBytes <  1)    return NADA;

   // #define SMARTA(name, closs, nElem, nam2)   closs *name = new closs[nElem]; SmartParr<closs> nam2(name);
   SMARTA(ubuf, UCHAR, (nBytes+16), smaUbuf);
   // UCHAR *ubuf = new UCHAR[nBytes + 16];

   if((n=jgrLil::formatFixQty(ubuf, iQty, nBytes)) != nBytes) return NADA;

   // walk back down; write out:
   ucp = ubuf + n;
   while(ucp > ubuf) {
      ucp--;
      putc((int)(*ucp), fp_out);
      ict++;
   } // while(ucp > ubuf)

   // delete [] ubuf; ubuf=NULL;

   return ict;
} // end of writeFixQty()







//
// new 09-10-00, from writeFixQty(); this version writes lil first.
//
//
//
// return value: number of bytes written; NADA if bad...
//
// qxjgrLil::writeFixQtyLf()
// qxwriteFixQtyLf()
//

int jgrLil::writeFixQtyLf(FILE *fp_out, int iQty, int nBytes) {
   int i;
   int n;
   int ict = 0;
   // int iStill = iQty;
   // int iThis;
   // UCHAR ubuf[16];
   UCHAR *ucp;

   // sanity check:
   // if(iQty < 0)       return NADA;
   if(fp_out == NULL) return NADA;
   if(nBytes <  1)    return NADA;

   SMARTA(ubuf, UCHAR, (nBytes+16), smaUbuf);
   // UCHAR *ubuf = new UCHAR[nBytes + 16];

   if((n=jgrLil::formatFixQty(ubuf, iQty, nBytes)) != nBytes) return NADA;

   // walk from bottom to top:
   for(i=0; i<nBytes; i++) {
      ucp = ubuf + i;
      putc((int)(*ucp), fp_out);
      ict++;
   } // for(i=0; i<nBytes; i++)


   // // walk back down; write out:
   // delete [] ubuf; ubuf=NULL;

   return ict;
} // end of writeFixQtyLf()






//
// we clip to double representations of the int screen dot ranges.
//
// input 
//    x0, etc: pixels in graph coords, but double.
//
// output:
//    return value: 
//       NADA: line is completely eaten; no draw is possible
//       0:    line-draw possible; no modification.
//       1:    line-draw possible; there IS modification.
//
//    x0, etc: clipped values in graph (but double) coordinates.
//       Note: these values are modified ONLY if return == 1.
//
// internal:
//    slidex: 0.0 at end 0, 1.0 at end 1; spills out
//    slidey: 0.0 at end 0, 1.0 at end 1; spills out
//
//
// qxdoClip()
//

int jgrLil::doClip(double &x0, double &y0, double &x1, double &y1) {

   double x0_hold = x0;
   double y0_hold = y0;
   double delta_x = x1 - x0;
   double delta_y = y1 - y0;
   double tolly   = .00001;   // pixels, in effect.
   double xmax_sl = (double)m_nHoriz - tolly;
   double xmin_sl = tolly;
   double ymax_sl = (double)m_nVert  - tolly;
   double ymin_sl = tolly;
   double xmax    = MAX(x0, x1);
   double xmin    = MIN(x0, x1);
   double ymax    = MAX(y0, y1);
   double ymin    = MIN(y0, y1);
   double slide0, slidex_min, slidey_min;
   double slide1, slidex_max, slidey_max;


   // are we completely out of range?
   if(xmax < xmin_sl) return NADA;
   if(xmin > xmax_sl) return NADA;
   if(ymax < ymin_sl) return NADA;
   if(ymin > ymax_sl) return NADA;

   // are we completely IN range?
   if((xmin >= xmin_sl) && (xmax <= xmax_sl) && (ymin >= ymin_sl) && (ymax <= ymax_sl)) return 0;


   // we have some work to do...

   // project the clip points onto the line:
   if(ABS(delta_x) > tolly) {
      slidex_min = (xmin_sl - x0) / delta_x;
      slidex_max = (xmax_sl - x0) / delta_x;
      if(slidex_min > slidex_max) oswap(slidex_min, slidex_max);
   } else {
      // x is (virtually) constant; must be inside; no restriction:
      slidex_min = 0.0;
      slidex_max = 1.0;
   }
   if(ABS(delta_y) > tolly) {
      slidey_min = (ymin_sl - y0) / delta_y;
      slidey_max = (ymax_sl - y0) / delta_y;
      if(slidey_min > slidey_max) oswap(slidey_min, slidey_max);
   } else {
      // y is (virtually) constant; must be inside; no restriction:
      slidey_min = 0.0;
      slidey_max = 1.0;
   }

   // intersect the requirements:
   slide0 = MAX(0.0, slidex_min);
   slide0 = MAX(slide0, slidey_min);
   slide1 = MIN(1.0, slidex_max);
   slide1 = MIN(slide1, slidey_max);

   // can't do it?
   if(slide0 >= slide1) return NADA;

   // do it!
   x0 = x0_hold + slide0 * delta_x;
   y0 = y0_hold + slide0 * delta_y;
   x1 = x0_hold + slide1 * delta_x;
   y1 = y0_hold + slide1 * delta_y;

   return 1;                // some change...
} // end of doClip()...




//
// Input:
//    ucWhere
//
// Output:
//    return value: offset from base, corrected (reduced) for filler bytes on each row, if any.
//
//
//
//
//
// qxgetByteOffset()
//

int jgrLil::getByteOffset(const UCHAR *ucWhere) const {

   // how far are we from the start?
   int alreadyAlong = (int)(ucWhere - m_theData);

   int rowBytesLil = m_nHoriz * colorBytesPerPixel();

   int iV   = alreadyAlong / m_rowBytes;
   int iHx3 = alreadyAlong - iV * m_rowBytes;

   int iRet = iV * rowBytesLil + iHx3;

   return iRet;
} // int jgrLil::getByteOffset(UCHAR *ucWhere) const





// qxgetBufFromOffset()

const UCHAR *jgrLil::getBufFromOffset(int offset) const {
   int rowBytesLil = m_nHoriz * colorBytesPerPixel();
   int iV = offset / rowBytesLil;
   int iHx3 = offset - iV * rowBytesLil;

   const UCHAR *pRet = m_theData  +   iV * m_rowBytes   + iHx3;

   return pRet;
} // UCHAR *jgrLil::getBufFromOffset(int offset)




//
// new 12-28-01
//
// Pass in NULL to begin.
// We skip over pad bytes
// We return NULL when we're done.
//
//
//
// 06-03-13 (jdl): bBackward (default false)
// 09-28-23 (jdl): pass out row, col, color 
//
// qxgetNextBufLoc()
//

const UCHAR *jgrLil::getNextBufLoc(const UCHAR *ucPrev, bool bBackward, int *pvOut, int *phOut, int *pcOut) const {

   // sanity check:
   if(m_theData == NULL) return NULL;

   int iHx3, iV;

   if(!bBackward) {
      // always, before 06-03-13: go forward through the file:

      // start of process:
      if(ucPrev == NULL) {
         if(pvOut != NULL) *pvOut = m_nVert  - 1;
         if(phOut != NULL) *phOut = 0;
         if(pcOut != NULL) *pcOut = 0;
         return m_theData;
      } // if(ucPrev == NULL)

      // how far are we from the start?
      int alreadyAlong = (int)(ucPrev - m_theData);

      // are we already done?
      if(alreadyAlong >= (m_totBytes - 1)) return NULL;

      iV   = alreadyAlong / m_rowBytes;
      iHx3 = alreadyAlong - iV * m_rowBytes;

      int rowBytesLil = m_nHoriz * colorBytesPerPixel();

      // here's our increment:
      iHx3++;

      if(iHx3 >= rowBytesLil) {
         iV++;
         iHx3 = 0;
      } // if(iHx3 >= rowBytesLil)

      if(pvOut != NULL) *pvOut = m_nVert - iV - 1;    // convert .bmp bot to top, into Bill's top to bot.
      if(phOut != NULL) *phOut = iHx3 / 3;
      if(pcOut != NULL) *pcOut = iHx3 % 3;

      // did an increment push us over the edge?
      if(iV >= m_nVert) return NULL;   // all done!
   } else {
      // new option 06-03-13: go backward through the file:

      // start of process:
      if(ucPrev == NULL) {
         if(pvOut != NULL) *pvOut = 0;
         if(phOut != NULL) *phOut = m_nHoriz - 1;
         if(pcOut != NULL) *pcOut = 2;
         return m_theData + m_totBytes - 1;
      } // if(ucPrev == NULL)

      // how far are we from the start?
      int alreadyAlong = (int)(ucPrev - m_theData);

      // are we already done?
      if(alreadyAlong == 0) return NULL;

      iV   = alreadyAlong / m_rowBytes;
      iHx3 = alreadyAlong - iV * m_rowBytes;

      int rowBytesLil = m_nHoriz * colorBytesPerPixel();

      // here's our decrement:
      iHx3--;

      if(iHx3 < 0) {
         iV--;
         iHx3 = rowBytesLil - 1;
      } // if(iHx3 < 0)

      if(pvOut != NULL) *pvOut = m_nVert - iV - 1;    // convert .bmp bot to top, into Bill's top to bot.
      if(phOut != NULL) *phOut = iHx3 / 3;
      if(pcOut != NULL) *pcOut = iHx3 % 3;

      // did a decrement push us over the edge?
      if(iV < 0) return NULL;   // all done!

   } // !if(!bBackward)

   return m_theData + iHx3 + iV*m_rowBytes;
} // end of getNextBufLoc()...






//
// new 09-29-23
//
//
//
//
//
//
// qxcalcAvgRGB()

int jgrLil::calcAvgRGB(double &dRedLin, double &dGrnLin, double &dBluLin, double &dRedSqr, double &dGrnSqr, double &dBluSqr) const {
   int iRed, iGrn, iBlu;

   long long rAccum   = 0;
   long long rsqAccum = 0;
   long long gAccum   = 0;
   long long gsqAccum = 0;
   long long bAccum   = 0;
   long long bsqAccum = 0;

   int nh   = this->nHoriz();
   int nv   = this->nVert();
   int nPix = nh * nv;
   int mPix = MAX(nPix, 1);  // for dividing.

   for(int iv=0; iv<nv; iv++) {
      for(int ih=0; ih<nh; ih++) {
         int jj = this->getOnePixel(ih, iv, iRed, iGrn, iBlu);
         rAccum   += iRed;
         rsqAccum += iRed*iRed;
         gAccum   += iGrn;
         gsqAccum += iGrn*iGrn;
         bAccum   += iBlu;
         bsqAccum += iBlu*iBlu;
      } // for(int ih=0; ih<nh; ih++)
   } // for(int iv=0; iv<nv; iv++)

   dRedLin = (double)rAccum        / (double)mPix;
   dRedSqr = sqrt((double)rsqAccum / (double)mPix);
   dGrnLin = (double)gAccum        / (double)mPix;
   dGrnSqr = sqrt((double)gsqAccum / (double)mPix);
   dBluLin = (double)bAccum        / (double)mPix;
   dBluSqr = sqrt((double)bsqAccum / (double)mPix);

   return 0;
} // int jgrLil::calcAvgRGB(double &dRedLin, double &dGrnLin, double &dBluLin, double &dRedSqr, double &dGrnSqr, double &dBluSqr) const






//
// new, long overdue, 08-07-10 (jdl)
//
//
// Call moveTo() before calling us for the first time.
//
// We allow locations outside the actual graph; we just don't try to write to them (isInXYrange()).
//
//
//
//
//
//
// 12-13-10 (jdl): chgOnePixel(), not setOnePixel()
//
// qxlineTo()
//

int jgrLil::lineTo(int iHtarg, int iVtarg) {
   int idh = iHtarg - m_iHFrom;
   int idv = iVtarg - m_iVFrom;
   int iLarge = MAX(ABS(idh), ABS(idv));

   // special case; avoid dividing by zero:
   if(iLarge == 0) {
      if(this->isInXYrange(iHtarg, iVtarg))
         chgOnePixel(iHtarg, iVtarg, m_colDrawPen);
      return 0;
   } // if(iLarge == 0)

   int ifLH=0, ifSH=0, ifLV=0, ifSV=0;
   double factor;
   if(ABS(idh) >= ABS(idv)) {
      // walk horizontally with small vertical changes:
      factor = (double)ABS(idv) / (double)ABS(idh);
      ifLH = SGN(idh);
      ifSV = SGN(idv);
   } else {
      // walk vertically with small horizontal changes:
      factor = (double)ABS(idh) / (double)ABS(idv);
      ifLV = SGN(idv);
      ifSH = SGN(idh);
   } // !if(ABS(idh) >= ABS(idv))

   // include both endpoints:
   // 01-26-20: first endpoint only if we just moved:
   for(int il=0; il<=iLarge; il++) {
      if(m_bJustMoved) {
         m_bJustMoved = false;
      } else if(il == 0) {
         continue;
      } // if(m_bJustMoved)

      int is = (int)(0.5 + (double)il*factor);   // is ("small") always less than (or possibly equal to) il ("large")
      int iH = m_iHFrom + ifLH*il + ifSH*is;
      int iV = m_iVFrom + ifLV*il + ifSV*is;
      if(this->isInXYrange(iH, iV))
         chgOnePixel(iH, iV, m_colDrawPen);
   } // for(int il=0; il<=iLarge; il++)

   // new current location:
   // this->moveTo(iHtarg, iVtarg);    // out: don't set m_bJustMoved
   m_iHFrom=iHtarg; m_iVFrom=iVtarg;

   return 0;
} // end of lineTo()...






//
// new 08-14-10 (jdl)
//
//
// sets coefficients so that:
//    graphx = inputx * m_xScale + m_xOffset
//
//
//
//
// qxsetScaleX()
//

int  jgrLil::setScaleX(double graphXlow, double inputXlow, double graphXhigh, double inputXhigh) {

   // crude check:
   if(inputXhigh == inputXlow) {
      inputXlow  -= 1.0;
      inputXhigh += 1.0;
   } // if(inputXhigh == inputXlow)

   m_xScale = (graphXhigh - graphXlow) / (inputXhigh - inputXlow);
   m_xOffset = graphXlow - inputXlow * m_xScale;

   return 0;
} // end of setScaleX()...







//
// new 08-14-10 (jdl)
//
// sets coefficients so that:
//    graphy = inputy * m_yScale + m_yOffset
//
//
//
//
// qxsetScaleY()
//

int  jgrLil::setScaleY(double graphYlow, double inputYlow, double graphYhigh, double inputYhigh) {

   // crude check:
   if(inputYhigh == inputYlow) {
      inputYlow  -= 1.0;
      inputYhigh += 1.0;
   } // if(inputYhigh == inputYlow)

   m_yScale = (graphYhigh - graphYlow) / (inputYhigh - inputYlow);
   m_yOffset = graphYlow - inputYlow * m_yScale;

   return 0;
} // end of setScaleY()...



//
// returns xValNew
//
//
//
//
// 01-29-02: don't be so finicky about who's low and who's high.
//
// qxreMap()
//

double reMap(double xBotOld, double xTopOld, double xValOld, double xBotNew, double xTopNew) {
   double xValNew;

   // sanity check:
   // if(xTopOld <= xBotOld) return DADA;
   if(xTopOld == xBotOld) return (xBotNew + xTopNew)/2.0;

   double dRat = (xValOld - xBotOld) / (xTopOld - xBotOld);
   xValNew = xBotNew + dRat*(xTopNew - xBotNew);

   return xValNew;
} // end of reMap()...







//
// new 02-06-02
//
// we add 0.5 and truncate, basically.  But if the result is nearly
// something plus 0.5, we randomize.
//
//
//
// qxroundMe()
//

int roundMe(double rawVal) {
   int iRet;
   int iVal;
   double tolDif = 0.01;   // if about this close to .5, randomize.

   int iSgn = SGN(rawVal);
   if(iSgn == 0) return 0;

   rawVal *= (double)iSgn;              // now positive

   double dRand = (randDoub() - 0.5) * tolDif;
   rawVal += dRand;                     // ambiguity removed, probably.

   // now do the normal stuff:
   iVal = (int)(rawVal + 0.5);
   iRet = iVal * iSgn;

   return iRet;
} // end of roundMe()...






//
// new 04-23-03
//
// Example: if nInRange = 16, then values 0 thru 15 are valid; we map:
//
// iVal    return
// ----    ------
//   33 ->  1
//   32 ->  0
//   31 ->  0
//   30 ->  1
//   17 -> 14
//   16 -> 15
//   15 -> 15
//   14 -> 14
//    1 ->  1
//    0 ->  0
//   -1 ->  0
//   -2 ->  1
//  -15 -> 14
//  -16 -> 15
//  -17 -> 15
//  -18 -> 14
//  -31 ->  1
//  -32 ->  0
//  -33 ->  0
//  -34 ->  1
//
//
// qxfnMirrorOvf()
//

int fnMirrorOvf(int nInRange, int iVal) {
   if(nInRange <= 0) return NADA;

   int iPeriod = 2 * nInRange;
   // while(iVal < 0)        iVal += iPeriod;
   // while(iVal >= iPeriod) iVal -= iPeriod;

   if(iVal < 0) {
      int iPos = -iVal;
      int nChg = (iPos + iPeriod - 1) / iPeriod;  // always at least 1
      nChg *= iPeriod;
      iVal += nChg;
   } // if(iVal < 0)

   if(iVal >= iPeriod) {
      int nChg = iVal / iPeriod;  // always at least 1
      nChg *= iPeriod;
      iVal -= nChg;
   } // if(iVal >= iPeriod)

   // fold top half down:
   int iOut = iVal;
   if(iVal >= nInRange) iOut = iPeriod - iVal -1;

   return iOut;
} // end of fnMirrorOvf()...




