// stufpic.cpp
//
// Written by John A deLaubenfels, https://www.adaptune.com/steg.htm .
// Permission granted to use freely in any non-fraudulent way.
//




#ifdef NEED_PCH
#include "pch.h"
#endif

#include "stufpic.h"
#include "bufin.h"



#pragma warning( disable : 4996 )


//
// 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;
         }
#ifdef NOT_USED
         int j = i;
         int shiftCount = 0;
         while(j != 0) {
            j >>= 1;
            shiftCount++;
         } // while(j != 0)

         // JASSERT(shiftCount == nBits[i]);
#endif // NOT_USED
      } // 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()...



//#ifdef NOT_NEEDED
////
////
////
//// qxgetRdenom()
//
//int getRdenom() {
//
//   std::vector<unsigned long long> vcSeed;
//   MT64::getAutoSeed(vcSeed);
//
//   MT64::rescrambleVector(vcSeed, 3);                             // 3: arbitrary.
//
//   MT64 anMT;
//   anMT.init_by_array64(&(vcSeed[0]), vcSeed.size());
//
//
//   int nDenom = 32768 + anMT.rand_n_quick(32768);                 // fairly arbitrary...
//
//   return nDenom;
//} // int getRdenom()
//#endif // NOT_NEEDED



//
// Input:
//    jgGuy:      the image to receive stuffing.
//    bytesEmbed: the number of bytes that will be embedded, already compressed.
//    bytesBack:  number of bytes we want to stuff backward.
//    nReserve:   bytes to reserve for backward-stuffing, etc.
//    factSafety: something like 1.01 to have some padding.
//    maxBits:    if > 0, maximum number of bits to embed an a pixel-color.
//    bScatter:   if true, calc for my grey-preserving algorithm.
//    vcBackRatios: permissible ratios for back stuffing, sorted large to small.
//    twoGuys:    main.denom should be set.  back.numer and back.denom should be tentatively set; we may modify.
//    pBitsLib:   if non-NULL, fill in.
//
// Output:
//    return value: safety, another name for detection difficulty.
//    twoGuys: main.numer at least set; back.denom also possible.
//    pBitsLib: if non-NULL, filled in.
//
// Note: this function is needed because bScatter behaves non-linearly, especially at high embed ratios.
// We take doubles because actual numbers must be ratioed up to play "what if" games thinking about
// shrinking while having at hand only the huge input wrapper file.
//
//
// 10-08-25: make jgGuy const (using mutable variables).
// 10-09-25: bScatter.
//
// qxcalcSafety()

double calcSafety(const jgrLil &jgGuy, double bytesEmbed, double bytesBack, double nReserve, double factSafety, int maxBits, bool bScatter, const std::vector<onePair> &vcBackRatios, twoPairs &twoGuys, bitsLib *pBitsLib=NULL) {
   const double smallPos = 1.0E-12;
   // starting values:
   int iIndx = 0;
   if(vcBackRatios.size() == 0) {
      // leave twoGuys.back undisturbed.
   } else {
      twoGuys.back.numer =  vcBackRatios[iIndx].numer;
      twoGuys.back.denom =  vcBackRatios[iIndx].denom;
   } // !if(vcBackRatios.size() == 0)


   double totDataBytes = (double)jgGuy.bytesTot();

   // back stuffing doesn't use bScatter, so we make a different call:
   double factOutBack = (double)bytesCanTakeg3(jgGuy, twoGuys.back.numer, twoGuys.back.denom, maxBits) / totDataBytes;
   if(factOutBack < smallPos)
      return -1.0;

   double dataUseBack = bytesBack / factOutBack;

   double nCanTake(-1.0);
   if(bScatter) {
      nCanTake = bytesCanTakeScattered(jgGuy, 1, 1, maxBits);   // don't need pBitsLib for this call.
   } else {
      nCanTake = bytesCanTakeg3(jgGuy, 1, 1, maxBits);
   } // !if(bScatter)

   double factOutMmax = nCanTake / totDataBytes;
   if(factOutMmax < smallPos)
      return -1.0;

   double dataUseMmin = bytesEmbed/factOutMmax;

   double dataBytesNeed = factSafety * (nReserve + dataUseMmin + dataUseBack);   // at least this much needed.
   if(dataBytesNeed > totDataBytes)
      return -1.0;

   double dataBytesMain = totDataBytes/factSafety - (nReserve + dataUseBack);

   double dRat = dataUseMmin / dataBytesMain;

   if(dRat > 1.0)
      return -1.0;

   // dRat = MAX(dRat, dRatMin);

   // first guess:
   twoGuys.main.numer = (int)(0.999999 + (double)twoGuys.main.denom * dRat);      // round off up.

   qprintf(fp_dbg, "\ncalcSafety(): first guess %d/%d; backward %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoGuys.back.numer, twoGuys.back.denom);
   // qprintf(fp_std, "\ncalcSafety(): first guess %d/%d; backward %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoGuys.back.numer, twoGuys.back.denom);  // qxtemp

   double dRaBack = (double)twoGuys.back.numer / (double)twoGuys.back.denom;

   // decide what to use for back.denom:
   while((dRat < dRaBack) && (iIndx < ((int)vcBackRatios.size() - 1))) {

      iIndx++;
      double dRaTest       = (double)vcBackRatios[iIndx].numer / (double)vcBackRatios[iIndx].denom;
      double factOutTest   = bytesCanTakeg3(jgGuy, vcBackRatios[iIndx].numer, vcBackRatios[iIndx].denom, maxBits) / totDataBytes;

      // passed sanity check:
      twoGuys.back.numer =  vcBackRatios[iIndx].numer;
      twoGuys.back.denom =  vcBackRatios[iIndx].denom;
      dRaBack       = dRaTest;      // (double)twoGuys.back.numer / (double)twoGuys.back.denom;
      factOutBack   = factOutTest;  // bytesCanTakeg3(jgGuy, twoGuys.back.numer, twoGuys.back.denom) / totDataBytes;
      dataUseBack   = bytesBack / factOutBack;
      dataBytesMain = totDataBytes/factSafety - (nReserve + dataUseBack);
      // dRat       = MAX(dRatMin, (dataUseMmin / dataBytesMain));
      dRat          = dataUseMmin / dataBytesMain;

      twoGuys.main.numer = (int)(0.999999 + (double)twoGuys.main.denom * dRat);      // round off up.

      qprintf(fp_dbg, "calcSafety(): another guess %d/%d; backward %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoGuys.back.numer, twoGuys.back.denom);
   } // while((dRat < dRaBack) && (iIndx < ((int)vcBackRatios.size() - 1)))

   double factOutTarg = bytesEmbed / ((totDataBytes / factSafety) - (nReserve + dataUseBack));


   // very low embed ratios with small data can, by chance, spill over, so add about one here:
   // double dFudge = (double)twoGuys.main.denom * factFudge;    // useful for silly ratios like 5 / 21234.

   // qprintf(fp_dbg, "inside:  main %d/%d, back %d/%d: factOutBack %10.8f, factOutTarg %10.8f, dFudge %6.4f\n", twoGuys.main.numer, twoGuys.main.denom, twoGuys.back.numer, twoGuys.back.denom, factOutBack, factOutTarg, dFudge);  // qxtemp

   // iterate to correct for non-linearity:
   for(int i=0; i<16; i++) {
      double nCanTakLil(-1.0);
      if(bScatter) {
         nCanTakLil = bytesCanTakeScattered(jgGuy, twoGuys.main.numer, twoGuys.main.denom, maxBits, NULL, NULL, pBitsLib);
      } else {
         nCanTakLil = bytesCanTakeg3(jgGuy, twoGuys.main.numer, twoGuys.main.denom, maxBits);
      } // !bScatter

      double factOutMain = nCanTakLil / totDataBytes;
      if(factOutMain < smallPos) {
         qprintf(fp_dbg, "factOutMain %12.9f\n", factOutMain);
         qprintf(fp_std, "factOutMain %12.9f\n", factOutMain);  // qxtemp
         return -1.0;
      } // if(factOutMain < smallPos)

      double dRaFix = factOutTarg / factOutMain;
      // double dRaFxTame = sqrt(dRaFix);
      double dRaFxTame = dRaFix;

      double dRatTarg = dRaFxTame * (double)twoGuys.main.numer / (double)twoGuys.main.denom;

      // xptl: find ideal denominator for desired ratio:
      int iDenom = findDenom(dRatTarg, 32768, 65536);
      int iNumer = MAX(1, (int)(0.5 + (double)iDenom * dRatTarg));
      double dRatGot = (double)iNumer / (double)iDenom;

      qprintf(fp_dbg, "dRaFix %12.10f; prev %d/%d  ->  %d/%d; safety %12.9f.\n", dRaFix, twoGuys.main.numer, twoGuys.main.denom, iNumer, iDenom, LOG_2(1.0/dRatGot));

      if((twoGuys.main.numer == iNumer) && (twoGuys.main.denom == iDenom))
         break;

      twoGuys.main.numer = iNumer;
      twoGuys.main.denom = iDenom;

   } // for(int i=0; i<8; i++)

   if(bScatter && (pBitsLib != NULL)) {
      if((twoGuys.main.numer != pBitsLib->numer) || (twoGuys.main.denom != pBitsLib->denom)) {
         double nCanTakLix = bytesCanTakeScattered(jgGuy, twoGuys.main.numer, twoGuys.main.denom, maxBits, NULL, NULL, pBitsLib);
      } // if((twoGuys.main.numer != pBitsLib->numer) || (twoGuys.main.denom != pBitsLib->denom))
   } // if(pBitsLib != NULL)

   double safety =  LOG_2((double)twoGuys.main.denom / (double)twoGuys.main.numer);

   return safety;
} // double calcSafety()...




// qxfillFacts()

int fillFacts(int &nReserve, double &factSafety) {

   static int    nResStat   = 128;
   static double factStat   = 1.01;
   static bool   bInit      = false;

   if(!bInit) {
      Bufin aBuf("facties.txt");
      if(aBuf.is_active()) {
         int nlines = aBuf.nlines();
         if(nlines > 0) nResStat   = atoi(aBuf[0]);
         if(nlines > 1) factStat   = atof(aBuf[1]);
      } // if(aBuf.is_active())

      qprintf(fp_dbg, "fillFacts(): nReserve %d, factSafety %6.4f\n", nResStat, factStat);

      bInit = true;
   } // if(!bInit)

   nReserve   = nResStat;
   factSafety = factStat;

   return 0;
} // int fillFacts()...




// qxgetReserve()

int getReserve() {
   int nReserve(0);
   double factSafety(0.0);

   fillFacts(nReserve, factSafety);

   return nReserve;
} // int getReserve()




// qxgetFacSaf()

double getFacSaf() {
   int nReserve(0);
   double factSafety(0.0);

   fillFacts(nReserve, factSafety);

   return factSafety;
} // double getFacSaf()





//
//
// Input:
//    jgGuy
//    bytesEmbed
//    bytesBack
//    bScatter
//    fileBytesOut
//    widPix
//    hgtPix
//    safety
//    vcBackRatios: permissible ratios for back stuffing.
//    twoGuys
//    anMT
//
// Output:
//    return value: 0 if all went well; -1 otherwise.
//    fileBytesOut: updated.
//    widPix:       updated.
//    hgtPix:       updated.
//    safety:       updated.
//    twoGuys:      filled with suggested numer & denom values.
//
//
// Note: zero or one of the four guys to be updated should be valid.  For the 3 ints, valid is > 0;
// for the double, valid is >= 0.0.
//
//
//
// 10-08-25 (jdl): bScatter.
// 10-08-25: make jgGuy const (using mutable variables).
//
// qxupdateShrink()

int updateShrink(const jgrLil &jgGuy, int bytesEmbed, int bytesBack, bool bScatter, int &fileBytesOut, int &widPix, int &hgtPix, double &safety, int maxBits, const std::vector<onePair> &vcBackRatios, twoPairs &twoGuys, MT64 &anMT) {
   if(twoGuys.main.denom < 32768)
      twoGuys.main.denom = 32768;    // was getRdenom(); will be modified soon, probably.

   if(vcBackRatios.size() == 0)
      return NADA;             // qxl: make up something?

   twoGuys.back.numer =  1;
   twoGuys.back.denom = 16;  // starting guess.

   int totDataBytes = jgGuy.bytesTot();

   double factOutBack = bytesCanTakeg3(jgGuy, twoGuys.back.numer, twoGuys.back.denom, maxBits) / (double)totDataBytes;
   double dataUseBack = (double)bytesBack / factOutBack;

   int nCanTake = 0;
   if(bScatter) {
      nCanTake = (int)bytesCanTakeScattered(jgGuy, 1, 1, maxBits);
   } else {
      nCanTake = bytesCanTakeg3(jgGuy, 1, 1, maxBits);
   } // if(bScatter)

   double factOutMmax = (double)nCanTake / (double)totDataBytes;

   int    nReserve   = getReserve();
   double factSafety = getFacSaf();

   double dataBytesNeed = factSafety * ((double)nReserve + (double)bytesEmbed/factOutMmax + dataUseBack);

   if(dataBytesNeed > (double)totDataBytes)
      return NADA;



   int    nPxlNow    = jgGuy.nHoriz() * jgGuy.nVert();
   // double minPxlRat  = (double)bytesEmbed / (double)(nCanTake - nReserve);
   // double minPxlRat  = factSafety * (double)(bytesEmbed + nReserve) / (double)nCanTake;
   double minPxlRat  = dataBytesNeed / (double)totDataBytes;
   double minMult    = sqrt(minPxlRat);

   int widMin = 1 + (int)((double)jgGuy.nHoriz() * minMult);       // min width  in pixels
   int hgtMin = 1 + (int)((double)jgGuy.nVert()  * minMult);       // min height in pixels

   int nDenom = twoGuys.main.denom;      // was 16384;  // fairly arbitrary
   int nNumer = 0;

   if((fileBytesOut <= 0) && (widPix <= 0) && (hgtPix <= 0) && (safety < 0.0)) {
      // initial call: we should come up with something reasonable.

      int fbHalf(0), widHalf(0), hgtHalf(0);
      double safetyHalf = 1.0 + (anMT.realcc() * .001);   // to get random denominators.
      twoPairs twoHalf(twoGuys);
      updateShrink(jgGuy, bytesEmbed, bytesBack, bScatter, fbHalf, widHalf, hgtHalf, safetyHalf, maxBits, vcBackRatios, twoHalf, anMT);

      int fb3mb(3000000), wid3mb(0), hgt3mb(0);
      double safety3mb = -1.0;
      twoPairs two3mb(twoGuys);
      updateShrink(jgGuy, bytesEmbed, bytesBack, bScatter, fb3mb, wid3mb, hgt3mb, safety3mb, maxBits, vcBackRatios, two3mb, anMT);

      // take the smaller output of 3 mb and safety 1.0:
      if(fb3mb < fbHalf) {
         fileBytesOut = fb3mb;
         widPix       = wid3mb;
         hgtPix       = hgt3mb;
         safety       = safety3mb;
         twoGuys      = two3mb;
      } else {
         // uhhh, if the pic is very small, make it larger:
         if(fbHalf < 90000) {
            updateShrink(jgGuy, bytesEmbed, bytesBack, bScatter, (fileBytesOut=90000), widPix, hgtPix, safety, maxBits, vcBackRatios, twoGuys, anMT);
         } else {
            fileBytesOut = fbHalf;
            widPix       = widHalf;
            hgtPix       = hgtHalf;
            safety       = safetyHalf;
            twoGuys      = twoHalf;
         } // !if(fbHalf < 90000)
      } // !if(fb6mb < fbHalf)
   } else if(fileBytesOut > 0) {
      // base on desired output file size:
      int nPxlTarg = (fileBytesOut - 54) / 3;     // 54 bytes header; 3 bytes/pixel.
      double pxlRat = MIN(1.0, ((double)nPxlTarg / (double)nPxlNow));
      pxlRat = MAX(pxlRat, minPxlRat);

      double multFact = sqrt(pxlRat);
      double rHnu = (double)jgGuy.nHoriz() * multFact;
      double rVnu = (double)jgGuy.nVert()  * multFact;

      // ok, actual size will round off, how?  Is it really critical?
      // 02-16-24: YES, it's critical, if we end up with too few pixels!
      widPix = MAX(widMin, (int)(rHnu + 0.5));   // round either way
      hgtPix = MAX(hgtMin, (int)(rVnu + 0.5));   // round either way

      int rowBytesLil = widPix * 3;
      int rowBytes = 4 * ((rowBytesLil+3) / 4);  // round up
      int nBytes = rowBytes * hgtPix;
      fileBytesOut = nBytes + 54;                // 54 byte header.

      int nPxlNu = widPix * hgtPix;
      double pxlRact =(double)nPxlNu / (double)nPxlNow;

      double nEmbBig = (double)bytesEmbed / pxlRact;
      double nBakBig = (double)bytesBack  / pxlRact;
      double nResBig = (double)nReserve   / pxlRact;

      safety = calcSafety(jgGuy, nEmbBig, nBakBig, nResBig, factSafety, maxBits, bScatter, vcBackRatios, twoGuys);

   } else if(widPix > 0) {
      // specified width in pixels:
      widPix = MIN(widPix, jgGuy.nHoriz());
      double multFact = (double)widPix / (double)jgGuy.nHoriz();
      multFact = MAX(multFact, minMult);

      double rHnu = (double)jgGuy.nHoriz() * multFact;
      double rVnu = (double)jgGuy.nVert()  * multFact;
      widPix = MAX(widMin, (int)(rHnu + 0.5));   // round either way
      hgtPix = MAX(hgtMin, (int)(rVnu + 0.5));   // round either way

#ifdef OUTTT
      double dataBytesAct = (double)(widPix * hgtPix * 3);

      if(dataBytesAct < dataBytesNeed) {
         // oops!  shrank too much!
         hgtPix++;
      } // if(dataBytesAct < dataBytesNeed)
#endif // OUTTT

      int rowBytesLil = widPix * 3;
      int rowBytes = 4 * ((rowBytesLil+3) / 4);  // round up
      int nBytes = rowBytes * hgtPix;
      fileBytesOut = nBytes + 54;                // 54 byte header.

      int nPxlNu = widPix * hgtPix;
      double pxlRact =(double)nPxlNu / (double)nPxlNow;

      double nEmbBig = (double)bytesEmbed / pxlRact;
      double nBakBig = (double)16         / pxlRact;
      double nResBig = (double)nReserve   / pxlRact;

      // qprintf(fp_std, "\n\n\nCalling calcSafety() with widPix %d, hgtPix %d.\n", widPix, hgtPix); // qxtemp!

      safety = calcSafety(jgGuy, nEmbBig, nBakBig, nResBig, factSafety, maxBits, bScatter, vcBackRatios, twoGuys);

      // qprintf(fp_std, "Called calcSafety() with widPix %d, hgtPix %d: %8.4f.\n", widPix, hgtPix, safety); // qxtemp!

   } else if(hgtPix > 0) {
      // specified height in pixels:
      hgtPix = MIN(hgtPix, jgGuy.nVert());
      double multFact = (double)hgtPix / (double)jgGuy.nVert();
      multFact = MAX(multFact, minMult);

      double rHnu = (double)jgGuy.nHoriz() * multFact;
      double rVnu = (double)jgGuy.nVert()  * multFact;
      widPix = MAX(widMin, (int)(rHnu + 0.5));   // round either way
      hgtPix = MAX(hgtMin, (int)(rVnu + 0.5));   // round either way

#ifdef OUTTT
      double dataBytesAct = (double)(widPix * hgtPix * 3);

      if(dataBytesAct < dataBytesNeed) {
         // oops!  shrank too much!
         widPix++;
      } // if(dataBytesAct < dataBytesNeed)
#endif // OUTTT

      int rowBytesLil = widPix * 3;
      int rowBytes = 4 * ((rowBytesLil+3) / 4);  // round up
      int nBytes = rowBytes * hgtPix;
      fileBytesOut = nBytes + 54;                // 54 byte header.

      int nPxlNu = widPix * hgtPix;
      double pxlRact =(double)nPxlNu / (double)nPxlNow;

      double nEmbBig = (double)bytesEmbed / pxlRact;
      double nBakBig = (double)16         / pxlRact;
      double nResBig = (double)nReserve   / pxlRact;

      safety = calcSafety(jgGuy, nEmbBig, nBakBig, nResBig, factSafety, maxBits, bScatter, vcBackRatios, twoGuys);

   } else if(safety >= 0.0) {
      // specified detection difficulty:
      safety = MIN(32.0, safety);           // keep doubles in range
      double safGoal = safety;              // holds steady while safety jumps around.
      double dRatTarg = pow(0.5, safGoal);  // 0 -> 1.0;  1 -> 0.5;  2 -> 0.25, etc.

      // xptl: find ideal denominator for desired ratio:
      int iDenom = findDenom(dRatTarg, 32768, 65536);
      int iNumer = (int)(0.5 + (double)iDenom * dRatTarg);
      double dRatGot = (double)iNumer / (double)iDenom;

      qprintf(fp_dbg, "With %d/%d, we get %11.9f, missing %11.9f by %11.9f\n", iNumer, iDenom, dRatGot, dRatTarg, ABS(dRatGot - dRatTarg));

      nDenom = twoGuys.main.denom = iDenom;
      nNumer = twoGuys.main.numer = iNumer;

//    double dNumerHi = MAX(2.0, ((double)nDenom * dRatTarg));
//    double dNumerLo = MAX(2.0, (dNumerHi - ((double)nDenom * factFudge)));

      double nCanTake(-1.0);
      if(bScatter) {
         nCanTake = bytesCanTakeScattered(jgGuy, nNumer, nDenom, maxBits);
      } else {
         nCanTake = bytesCanTakeg3(jgGuy, nNumer, nDenom, maxBits);
      } // !bScatter

      double factOutMain = (nCanTake / (double)totDataBytes);

      double dataUseMain = bytesEmbed / factOutMain;

      // pick a likely back ratio:
      int iIndx = 0;
      double dRaBack = (double)vcBackRatios[iIndx].numer / (double)vcBackRatios[iIndx].denom;

      while((dRatTarg < dRaBack) && (iIndx < ((int)vcBackRatios.size() - 1))) {
         iIndx++;
         dRaBack = (double)vcBackRatios[iIndx].numer / (double)vcBackRatios[iIndx].denom;
      } // while((dRat < dRaBack) && (iIndx < ((int)vcBackRatios.size() - 1)))

      twoGuys.back.numer = vcBackRatios[iIndx].numer;
      twoGuys.back.denom = vcBackRatios[iIndx].denom;

      // double bytesBack = 16.0;   // we don't have to ratio this up for this calc!

      // back stuffing doesn't use bScatter, so we make a different call:
      double factOutBack = (double)bytesCanTakeg3(jgGuy, vcBackRatios[iIndx].numer, vcBackRatios[iIndx].denom, maxBits) / (double)totDataBytes;
      double dataUseBack = (double)bytesBack / factOutBack;


      // the grand total:
      double dataBytesNeed = factSafety * (nReserve + dataUseMain + dataUseBack);   // at least this much needed.

      double factPix = dataBytesNeed / (double)totDataBytes;

      // qprintf(fp_dbg, "outside: main %d/%d, back %d/%d: factOutBack %10.8f, factOutMain %10.8f\n", nNumer, nDenom, vcBackRatios[iIndx].numer, vcBackRatios[iIndx].denom, factOutBack, factOutMain);  // qxtemp




      if(factPix > 1.0) {
         // BITE me!  Can't use this desired safety; revert to full-size:
         updateShrink(jgGuy, bytesEmbed, bytesBack, bScatter, fileBytesOut, (widPix=jgGuy.nHoriz()), hgtPix, (safety=-1.0), maxBits, vcBackRatios, twoGuys, anMT);

      } else {
         // what we hoped for: we can keep desired ratio.
         double multFact  = sqrt(factPix);

         double rHnu = (double)jgGuy.nHoriz() * multFact;
         double rVnu = (double)jgGuy.nVert()  * multFact;
         widPix = (int)(rHnu + 0.999);
         hgtPix = (int)(rVnu + 0.999);

         int rowBytesLil = widPix * 3;
         int rowBytes = 4 * ((rowBytesLil+3) / 4);  // round up
         int nBytes = rowBytes * hgtPix;
         fileBytesOut = nBytes + 54;                // 54 byte header.

         // we switch numer & denom; no neg:
         safety =  LOG_2((double)twoGuys.main.denom / (double)twoGuys.main.numer);

         qprintf(fp_dbg, "safGoal to safety: %14.12f, %14.12f, diff %14.12f\n", safGoal, safety, (safety - safGoal));
      } // !if(factPix > 1.0)

   } else {
      // int i = 1 / 0;
   }

   int iRet = ((safety < -0.5) ? (NADA) : 0);

   return iRet;
} // int updateShrink(const jgrLil &jgGuy, int bytesEmbed, int &fileBytesOut, int &widPix, int &hgtPix, double &safety)




//
//
//
//
//
//
// 11-15-23: pass in src and dest.
// 05-06-25: bShuffle.
// 05-08-25: bConserv.
// 05-09-25: pOneOverride (default NULL).
// 05-17-25: bTrim (echos bShuffle).
// 10-08-25: bScatter.
//
//
// qxdoEmbed()

int doEmbed(const jgrLil &jgSrc, jgrLil &jgDest, std::vector<unsigned char> &vcEmbed, const std::vector<unsigned long long> &passwordSeedVector, int widPix, int hgtPix, int maxBits, twoPairs &twoGuys, MT64 &anMTin, bool bScatter, bool bShuffle, bool bConserv, bool bTrim, onePair *pOneOverride) {
   int bytesEmbed = (int)vcEmbed.size();

   qprintf(fp_dbg, "\ndoEmbed() with size %dx%d, embedding %d bytes.\n", widPix, hgtPix, bytesEmbed);
   // qprintf(fp_std, "\ndoEmbed() with size %dx%d, embedding %d bytes.\n", widPix, hgtPix, bytesEmbed);  // qxtemp!

   jgDest.clear();
   jgDest.setSize(widPix, hgtPix);
   jgDest.cpyChgSize(jgSrc);

   unsigned int smallSeed1 = anMTin.genrand64_int32();    // used for stuffing and extracting the main forward info.
   unsigned int smallSeed2 = anMTin.genrand64_int32();    // used for stuffing and extracting the little backward info.

//#ifdef REPLACED_D
//   std::vector<unsigned long long> aLongLongSeed;
//   MT64::getAutoSeed(aLongLongSeed);
//   smallSeed1 = (unsigned int)(aLongLongSeed[3] >> 32);
//   smallSeed2 = (unsigned int)(aLongLongSeed[2] >> 17);
//#endif // REPLACED_D

   unsigned char *pCbuf = ADDR_ZEROTH(vcEmbed);
   int iSiz = (int)vcEmbed.size() - 12;      // don't include the extra 12 bytes.

   unsigned char *pCmi1 = pCbuf + 4;         // where we write the size (1st)
   unsigned char *pCmi2 = pCbuf + 8;         // where we write the size (2nd)
   unsigned char *pCfwd = pCbuf +12;         // start of actual file data

   int *ip1 = (int *)(pCmi1);
   *ip1     = iSiz;

   int *ip2 = (int *)(pCmi2);
   *ip2     = iSiz;                          // copy for auto-detection of size

   unsigned int *ulp = (unsigned int *)(pCbuf);
   *ulp     = smallSeed1;

   int maxAl = jgDest.bytesTot();
   int maxG3 = bytesCanTakeg(jgDest, 1);
   char cMax[256];

   sprintf(cMax, "%d", ((bScatter) ? (maxAl) : (maxG3)));  // maxG3 won't survive bScatter.

   MT64 anMTloc(true);    // true: do skip initialization
   // anMT.init_by_array64(&(aLongLongSeed[0]), aLongLongSeed.size());


   // qprintf(fp_std, "About to call calcSafety().\n");  // qxtemp!


   // has all been planned before calling us, but that was before actual shrinking.
   int    nReserve   = getReserve();
   double factSafety = getFacSaf();

   twoPairs twoCopy = twoGuys;

   std::vector<onePair> vcBackEmpty;
   bitsLib aBitLib;
   bitsLib *pBLuse(NULL);
   if(bScatter)
      pBLuse = &aBitLib;

   double safety = calcSafety(jgDest, bytesEmbed, 16, nReserve, factSafety, maxBits, bScatter, vcBackEmpty, twoCopy, pBLuse);

   qprintf(fp_dbg, "doEmbed(): input %d/%d  -->  %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoCopy.main.numer, twoCopy.main.denom);
   // qprintf(fp_std, "doEmbed(): input %d/%d  -->  %d/%d.\n", twoGuys.main.numer, twoGuys.main.denom, twoCopy.main.numer, twoCopy.main.denom);  // qxtemp


   int nDenom = twoCopy.main.denom;
   int nNumer = twoCopy.main.numer;

   if(pOneOverride != NULL) {
      // caller has his own idea about how heavily to stuff the thing.
      qprintf(fp_dbg, "Hoping to change %d / %d   to   %d / %d.\n", nNumer, nDenom, pOneOverride->numer, pOneOverride->denom);

      double rat0 = (double)nNumer / (double)nDenom;
      double rat1 = (double)pOneOverride->numer / (double)pOneOverride->denom;

      if(rat1 < rat0) {
         qprintf(fp_dbg, "Sorry: can't impose pOneOverride values: %6.4f < %6.4f.\n", rat1, rat0);
      } else {
         qprintf(fp_dbg, "Imposing pOneOverride values: %6.4f >= %6.4f.\n", rat1, rat0);
         nNumer = twoCopy.main.numer = pOneOverride->numer;
         nDenom = twoCopy.main.denom = pOneOverride->denom;

         if(bScatter && (pBLuse != NULL)) {
            double nCanTakLix = bytesCanTakeScattered(jgDest, nNumer, nDenom, maxBits, NULL, NULL, pBLuse);
         } // if(pBLuse != NULL)

      } // !if(rat1 < rat0)
   } // if(pOneOverride != NULL)

   // 05-09-25: report back what we're actually using:
   twoGuys = twoCopy;

   // step in1: scramble the data with our own seed:
   anMTloc.init_genrand32(smallSeed1);
   anMTloc.scramUnscram(pCmi1, iSiz+8, bConserv);   // data and both copies of the data length.

   char hfileInPasss[256];
   sprintf(hfileInPasss, "%d/%d", nNumer, nDenom);

   // step in2: scramble the data with password:
   initByThree(anMTloc, passwordSeedVector, hfileInPasss, cMax);
   anMTloc.scramUnscram(pCbuf, iSiz+12, bConserv);   // data, both copies of the length, and the seed for the previous scramble.

   bool bUseSeed2 = true;
// bool bScatter  = true;        // out 10.08.25
// bool bTrim     = bShuffle;    // except in version 1.08

   bool bBackward, bFillEntireFile;

   // step in3: embed the data with password:
   if(!bUseSeed2) {
      initByTwo(anMTloc, passwordSeedVector, hfileInPasss);
   } else {
      char cTemppy[256];
      sprintf(cTemppy, "%u", smallSeed2);
      initByThree(anMTloc, passwordSeedVector, hfileInPasss, cTemppy);
   } // !if(!bUseSeed2)

   int i1 = applyBytesg3(jgDest, pCbuf, iSiz+12, nNumer, nDenom, maxBits, anMTloc, (bBackward=false), (bFillEntireFile=true), bScatter, pBLuse, bShuffle, bConserv, bTrim);

   if(i1 < 0) {
      qprintf(fp_dbg, "applyBytesg3() returns %d.\n", i1);
      return NADA;
   } // if(i1 < 0)

   int mbUse = (((maxBits >= 8) || (maxBits < 0)) ? (0) : (maxBits));
   int iRev  = (1 * bScatter    +   2 * bUseSeed2   +   4 * mbUse    +    32 * bShuffle   +   64 * bConserv   +   128 * bTrim);

   // backward-stuff the numerator and denominator:
   unsigned int uiLil[4];
   uiLil[0] = smallSeed2;
   uiLil[1] = (unsigned int)nNumer;
   uiLil[2] = (unsigned int)nDenom;
   uiLil[3] = (unsigned int)iRev;


   // first tiny pass: with program-chosen seed:
   anMTloc.init_genrand32(smallSeed2);

   // step in4a:
   anMTloc.scramUnscram((UCHAR *)(&uiLil[1]), 12);

   // second tiny pass: with user-chosen seed:
   if(bScatter) {
      // get current non-scatter capacity; our non-scatter backward stuff won't alter it:
      jgDest.clrHists();
      // qprintf(stdout, "old cMax = \"%s\".\n", cMax);     // qxtemp

      maxG3 = bytesCanTakeg(jgDest, 1);
      sprintf(cMax, "%d", maxG3);
   } // if(bScatter)
   // qprintf(stdout, "cMax = \"%s\".\n", cMax);     // qxtemp

   qprintf(fp_dbg, "Backwardack cMax = \"%s\".\n", cMax);        // qxtemp?

   initByTwo(anMTloc, passwordSeedVector, cMax);

   // step in4b:
   anMTloc.scramUnscram((UCHAR *)(&uiLil[0]), 16);

   initByTwo(anMTloc, passwordSeedVector, cMax);
   int numer2 = twoGuys.back.numer;    //  1;
   int denom2 = twoGuys.back.denom;    // 16;
   int maxbBack = 8;

   // step in4c:
   int i2 = applyBytesg3(jgDest, (UCHAR *)(&uiLil[0]), 16, numer2, denom2, maxbBack, anMTloc, (bBackward=true), (bFillEntireFile=false), false);   // no bScatter on backward stuff, so we don't disturb maxG3.

   int iSpare = i2 - i1 - 1;
   double fracNofil = (double)iSpare / (double)maxAl;
   double fracFill  = 1.0 - fracNofil;
   double pctFill   = 100.0 * fracFill;

   qprintf(fp_dbg, "Stuffing: i1 = %d; i2=%d; maxAl %d; %d data bytes backward; %d to spare; %5.1f%% full.\n", i1, i2, maxAl, (maxAl - i2), iSpare, pctFill);
   // qprintf(fp_std, "Stuffing: i1 = %d; i2=%d; maxAl %d; %d data bytes backward; %d to spare; %5.1f%% full.\n", i1, i2, maxAl, (maxAl - i2), iSpare, pctFill);  // qxtemp!

   if(i2 < 0)
      return NADA;

   if(i1 >= i2) {
      // BITE me!  The stuffs crashed into each other!
      // qprintf(fp_std, "Smash/crash; FAIL!\n");
      qprintf(fp_dbg, "Smash/crash; FAIL!\n");
      return NADA;
   } // if(i1 >= i2)


   return 0;
} // int doEmbed()...




//
//
//
//
// qxdoExtrInner()

int doExtrInner(int nNumerBack, int nDenomBack, jgrLil &jgGuy, const std::vector<unsigned long long> &passwordSeedVector, JarqAnch &jrqOut) {
   bool bBackward(false);
   int  iRetBad = NADA;

   qprintf(fp_dbg, "\ndoExtrInner() with backward %d/%d.\n", nNumerBack, nDenomBack);

   unsigned int uiLil[4];
   // uiLil[0] = smallSeed2;
   // uiLil[1] = (unsigned int)hfileInNumes;
   // uiLil[2] = (unsigned int)hfileInDenos;
   // uiLil[3] = version: bScatter, bUseSeed2, ...

   MT64 anMT(true);      // true: do skip initialization

   bool bScatter  = false;
   bool bShuffle  = false;
   bool bTrim     = false;
   bool bConserv  = false;
   bool bUseSeed2 = false;
   int  maxBits   = 0;

   char cMxt[256];
   int maxGnu = bytesCanTakeg(jgGuy, 1);         // if we use every available bit under guard.
   sprintf(cMxt, "%d", maxGnu);                  // can be used as partial decryption seed.

   int numer2 = nNumerBack;     //  1;
   int denom2 = nDenomBack;     // 16;

   UCHAR *puc = (UCHAR *)(&uiLil[0]);

   // qprintf(fp_dbg, "cMxt = \"%s\".\n", cMxt);
   
   // step out1a:
   initByTwo(anMT, passwordSeedVector, cMxt);
 
#ifdef TEST_CODE
   unsigned long long ullRand = anMT.genrand64_int64();
   
   qprintf(fp_dbg, "First out of anMT: %016llX.\n", ullRand);

   initByTwo(anMT, passwordSeedVector, cMxt);
#endif // TEST_CODE

   int i2 = extractBytesg3(jgGuy, puc, 16, numer2, denom2, 8, anMT, (bBackward=true), false);

#ifdef TEST_CODE
   int nc = anMT.nCallsSinceInit();
   qprintf(fp_dbg, "nCalls %d, next guy %016llX.\n", nc, anMT.genrand64_int64());
   for(int i=0; i<16; i++)
      qprintf(fp_dbg, "%02X ", puc[i]);
   qprintf(fp_dbg, "\n");
#endif // TEST_CODE


   // step out1b:
   initByTwo(anMT, passwordSeedVector, cMxt);

   anMT.scramUnscram((UCHAR *)(&uiLil[0]), 16);

#ifdef TEST_CODE
   nc = anMT.nCallsSinceInit();
   qprintf(fp_dbg, "2nd nCalls %d, next guy %016llX.\n", nc, anMT.genrand64_int64());
   for(int i=0; i<16; i++)
      qprintf(fp_dbg, "%02X ", puc[i]);
   qprintf(fp_dbg, "\n");
#endif // TEST_CODE

   unsigned int smallSeed2 = uiLil[0];


   // step out1c:
   anMT.init_genrand32(smallSeed2);

   anMT.scramUnscram((UCHAR *)(&uiLil[1]), 12);

#ifdef TEST_CODE
   nc = anMT.nCallsSinceInit();
   qprintf(fp_dbg, "3rd nCalls %d, next guy %016llX.\n", nc, anMT.genrand64_int64());
   for(int i=0; i<16; i++)
      qprintf(fp_dbg, "%02X ", puc[i]);
   qprintf(fp_dbg, "\n");
#endif // TEST_CODE


   // for now, these rules apply:
   for(int i=1; i<=3; i++) {
      if((uiLil[i] & 0xFFFF0000) != 0) {
         qprintf(fp_dbg, "uiLil[%d] has value %08X; does not pass low-word test.\n", i, uiLil[i]);
         return iRetBad;
      } // if((uiLil[i] & 0xFFFF0000) != 0)
   } // for(int i=1; i<=3; i++)

   int nNumer  =    (int)uiLil[1];
   int nDenom  =    (int)uiLil[2];

   if(nNumer > nDenom) {
      qprintf(fp_dbg, "Numer > denom: %d > %d.\n", nNumer, nDenom);
      return iRetBad;
   } // if(nNumer > nDenom)

   char cRat[256];
   sprintf(cRat, "%d/%d", nNumer, nDenom);

   bScatter  = !!(uiLil[3]        & 1);
   bUseSeed2 = !!((uiLil[3] >> 1) & 1);
   maxBits   =   ((uiLil[3] >> 2) & 7);
   bShuffle  = !!((uiLil[3] >> 5) & 1);
   bConserv  = !!((uiLil[3] >> 6) & 1);
   bTrim     = !!((uiLil[3] >> 7) & 1);
   int iBlank =  ((uiLil[3] >> 8) & 255);        // if this isn't blank, sender has later version.

   qprintf(fp_dbg, "bScatter = %d, bShuffle = %d, bTrim %d, bConserv = %d, bUseSeed2 = %d, maxBits = %d, iBlank %d.\n", bScatter, bShuffle, bTrim, bConserv, bUseSeed2, maxBits, iBlank);

   if(iBlank != 0) {
      qprintf(fp_dbg, "Non-zero iBlank means this was written by a later version.\n");
      iRetBad = NAD3;    // we still want to see if by some means we can extract.
   } // if(iBlank != 0)

   int maxAl = jgGuy.bytesTot();
   int maxG3 = bytesCanTakeg(jgGuy, 1);
   char cMax[256];

   sprintf(cMax, "%d", ((bScatter) ? (maxAl) : (maxG3)));  // maxG3 won't survive bScatter.

   UCHAR *pcOut   = NULL;  // used to be allocated by extract fn.
   int    lenNeed = 0;

   bitsLib aBitLib;
   bitsLib *pBLuse(NULL);

   if(bScatter) {
      pBLuse = &aBitLib;

      double dDum = bitsCanTakeScattered(jgGuy, nNumer, nDenom, maxBits, NULL, NULL, pBLuse);
   } // if(bScatter)


   // first pass tests plausibility; if survives, 2nd pass grabs the data.
   for(int iPass=0; iPass<2; iPass++) {
      pcOut = NULL; // make sure this is fresh

      // step out2: extract the data with password.
      if(!bUseSeed2) {
         initByTwo(anMT, passwordSeedVector, cRat);
      } else {
         char cTemppy[256];
         sprintf(cTemppy, "%u", smallSeed2);
         initByThree(anMT, passwordSeedVector, cRat, cTemppy);
      } // !if(!bUseSeed2)

      int iOutLen = 0;
      if(iPass == 0) {
         // test for plausibility:
         pcOut = new UCHAR[16];
         iOutLen = extractBytesg3(jgGuy, pcOut, 16, nNumer, nDenom, maxBits, anMT, (bBackward=false), bScatter, pBLuse, bShuffle, bConserv, bTrim);
      } else {
         // the real thing:
         pcOut = new UCHAR[lenNeed];
         
#ifdef _WIN32         
         unsigned long long ull0 = getProccyTime();
#endif // _WIN32         

         iOutLen = extractBytesg3(jgGuy, pcOut,  lenNeed, nNumer, nDenom, maxBits, anMT, (bBackward=false), bScatter, pBLuse, bShuffle, bConserv, bTrim);

#ifdef _WIN32
         unsigned long long ull1 = getProccyTime();
         unsigned long long ullDif = ull1 - ull0;

         double secTot = (double)(ullDif) / 1.0E7;      // yep, ten MILLION slices per second from this function.
         double minTot = secTot / 60.0;
         double hrrTot = minTot / 60.0;

         if(iOutLen > 1024)
            qprintf(fp_dbg, "doExtract() time: %llu (%8.3f seconds, %8.3f minutes, %8.3f hours).\n", ullDif, secTot, minTot, hrrTot);
#endif // _WIN32



      } // !if(iPass == 0)

      if(pcOut == NULL) {
         qprintf(fp_dbg, "pcOut is NULL.\n");
         return iRetBad;
      } // if(pcOut == NULL)


      // make sure pcOut gets deleted:
      SmartParr<UCHAR> smaOut(pcOut);


      if(iOutLen < 16) {
         qprintf(fp_dbg, "iOutLen %d is less than 16.\n", iOutLen);
         return iRetBad;
      } // if(iOutLen < 16)


      // step out3: unscramble the data with password:
      initByThree(anMT, passwordSeedVector, cRat, cMax);
      anMT.scramUnscram(pcOut, iOutLen, bConserv);

      int *ip1 = (int *)(pcOut+4);  // len, once unscrambled
      int *ip2 = (int *)(pcOut+8);  // copy of len, once unscrambled

      unsigned int *ulp = (unsigned int *)(pcOut);  // seed
      unsigned int seedOut = *ulp;

      // qprintf(stdout, "seedOut on extract: %d.\n", seedOut);  // qxtemp.

      // step out4: unscramble the data with our own seed.
      anMT.init_genrand32(seedOut);
      anMT.scramUnscram(pcOut+4, iOutLen-4, bConserv);


      int oSiz1 = *ip1;
      int oSiz2 = *ip2;
      if(oSiz1 != oSiz2) {
         qprintf(fp_dbg, "Sorry!  oSiz1 = %d, but oSiz2 = %d; iOutLen %d irrelevant.\n", oSiz1, oSiz2, iOutLen);
         // qprintf(fp_std, "Sorry!  oSiz1 = %d, but oSiz2 = %d; iOutLen %d irrelevant.\n", oSiz1, oSiz2, iOutLen);
         return iRetBad;
      } // if(oSiz1 != oSiz2)

      if(iPass == 0) {
         qprintf(fp_dbg, "iPass 0 checks pass.\n");

         // can't do the checks of pass 2...
         lenNeed = oSiz1 + 32;    // just a bit of padding (12 bytes definitely needed)
      } else {
         // we hope we got the real thing.
         if(oSiz1 > (iOutLen - 12)) {
            // SHIT!  Not enough bytes extracted.
            qprintf(fp_dbg, "Sorry!  iOutLen = %d, but oSiz1 = %d.\n", iOutLen, oSiz1);
            return iRetBad;
         } else {

            // 07-24-23: figure out if this is a jarq:
            sucqObj  mySuq(NULL, pcOut+12, pcOut+12+oSiz1);
            int irevSaved = 0;
            int i = jrqOut.unArcJarq(irevSaved, mySuq);
            // qprintf(stdout, "Have fleshed out jaAnch w/ return value %d.\n", i);
            if(i != 0) {
               // contents corrupted, not recognized as anchor.
               jrqOut.clear();
               qprintf(fp_dbg, "Contents corrupted.\n");
               return NAD2;                                  // signals caller not to try other calls to us.
            } else {
               // qxhere
            } // if(i == 0)
         } // if(oSiz1 != oSiz2), etc.
      } // !if(iPass == 0)

      zeroAr(pcOut, iOutLen);
   } // for(int iPass=0; iPass<2; iPass++)

   return 0;
} // int doExtrInner()...




// qxdoExtract()
//

int doExtract(jgrLil &jgGuy, const std::vector<unsigned long long> &passwordSeedVector, const std::vector<onePair> &vcBackRatios, JarqAnch &jrqOut) {
   int i(0), iInner(NADA), nDenomBack(NADA), nNumerBack(1);

   for(i=0; i<(int)vcBackRatios.size(); i++) {
      if((iInner = doExtrInner(vcBackRatios[i].numer, vcBackRatios[i].denom, jgGuy, passwordSeedVector, jrqOut)) == 0)
         return iInner;

      // is there no hope?
      if(iInner == NAD2)     // we sucked specified bytes but couldn't make a Jarq.
         return iInner;

      if(iInner == NAD3)     // apparently written by a later version.
         return iInner;
   } // for(i=0; i<(int)vcBackRatios.size(); i++)

   // give up
   return NADA;
} // int doExtract(jgrLil &jgGuy, const std::vector<unsigned long long> &passwordSeedVector, JarqAnch &jrqOut)




//
//
// Input:
//    jgGuy
//    nNumer
//    nDenom
//    maxBits
//    pBitsPerIval (default NULL).  If non-NULL, array of 256 to be filled.
//    pErrsPerIval (default NULL).  If non-NULL, array of 256 to be filled.
//    pBitsLib     (default NULL).  If non-NULL, fill with info we can use later.
//
// Output:
//    return value: expected number of bits jgGuy can take.
//    pBitsPerIval: array filled if non-NULL.
//    pErrsPerIval: array filled if non-NULL.
//    pBitsLib:     filled if non-NULL.
//
//
//
// 10-08-25: make ajg const (using mutable variables)
//
// qxbitsCanTakeScattered()

double bitsCanTakeScattered(const jgrLil &jgGuy, int nNumer, int nDenom, int maxBits, double *pBitsPerIval, double *pErrsPerIval, bitsLib *pBitsLib) {
   static bool bDidDbg = false;
   double bitsPerLoc[256];
   double errsPerLoc[256];
   int mostestBits = 0;

   double *pBitsUse = ((pBitsPerIval == NULL) ? (bitsPerLoc) : (pBitsPerIval));
   double *pErrsUse = ((pErrsPerIval == NULL) ? (errsPerLoc) : (pErrsPerIval));

   double dTot = 0.0;
   bool bDbg = false;
   if(bDbg)
      qprintf(fp_dbg, "\nbitsCanTakeScattered() for %d %d.\n", nNumer, nDenom);

   const int *pBits = getnBitsArr256();

   if(pBitsLib != NULL) {
      pBitsLib->clrPairs();                 // we may not overwrite everybody.
      pBitsLib->numer   = nNumer;
      pBitsLib->denom   = nDenom;
      pBitsLib->maxBits = maxBits;
   } // if(pBitsLib != NULL)

   for(int iVal=0; iVal<256; iVal++) {
      int nPix = jgGuy.histBig(iVal);      // number of pixel-colors having this brightness

#ifdef OUT_T
      if((nPix == 0) && (!bDbg) && (pBitsPerIval == NULL))
         continue;
#endif // OUT_T

      int nCyc  = (1 << pBits[iVal]) / 2;     // achieves iVal 0 -> nCyc 0.

      int cycPer256 = 256 / MAX(1, nCyc);

      double dCum = 0.0, dErrCum = 0.0;

      for(int iScatOff=0; iScatOff<nCyc; iScatOff++) {
         int nRemain0 = NADA, nRemain1 = NADA, nHurdl = NADA;
         double dShift0(0.0), dShift1(0.0);

         int nb0 =  calcScatter(iVal, iScatOff, nNumer, nDenom, (nHurdl=0),          maxBits, false, &nRemain0, &dShift0);
         int nb1 =  calcScatter(iVal, iScatOff, nNumer, nDenom, (nHurdl=(nDenom-1)), maxBits, false, &nRemain1, &dShift1);

         mostestBits = MAX(mostestBits, nb0);

         int nRemainUse = 0;            // if we can't bump, this dummy value signals no hope for bumping.

         double dBits = 0.0, dErrThis = 0.0;
         if(nb0 == nb1) {
            JASSERT(dShift0 == dShift1);
            dBits    = (double)nb0;
            dErrThis = dShift0;
         } else {
            int nbMin   = MIN(nb0, nb1);
            JASSERT(nbMin == nb1);

            int nRemain = nRemain0;      // nb0 is higher, so has the bump
            nRemainUse  = nRemain0;

            // we're trying to guess how likely the hurdle will be low enough to clear.
            // if nDenom = 8, and nRemain = 0, no chance.  1, 1/8 etc.  Wow, this is a nice clean expression.
            dBits    = (double)nbMin + (double)nRemain / (double)nDenom;
            dErrThis = dShift1  +   (dShift0 - dShift1) * ((double)nRemain / (double)nDenom);
         } // !if(nb0 == nb1)

         if(pBitsLib != NULL) {
            bitsPair abp(nb1, nRemainUse);
            for(int iBump=0; iBump<cycPer256; iBump++) {
               int indx = iScatOff + iBump * nCyc;
               (*pBitsLib).vcVcLib[iVal][indx] = abp;
            } // for(int iBump=0; iBump<cycPer256; iBump++)
         } // if(pBitsLib != NULL)

         dCum    += dBits;
         dErrCum += dErrThis;
      } // for(int iScatOff=0; iScatOff<nCyc; iScatOff++)

      double dAvg    = dCum    / (double)MAX(1, nCyc);
      double dErrAvg = dErrCum / (double)MAX(1, nCyc);

      pBitsUse[iVal] = dAvg;
      pErrsUse[iVal] = dErrAvg;

      if(bDbg)
         qprintf(fp_dbg, "   %3d  %8.6f %8.6f\n", iVal, dAvg, dErrAvg);

      dTot += (double)nPix * dAvg;
   } // for(int iVal=0; iVal<256; iVal++) {

   // double dRet = dTot * (double)nNumer / (double)nDenom;
   double dRet = dTot;  // already has numer & denom factored in.

   if(!bDidDbg && (nNumer == 1) && (nDenom == 1)) {
      qprintf(fp_dbg, "\nFor 1/1:\n");

      for(int i=0; i<256; i++) {
         qprintf(fp_dbg, "%5d (%02X)  %9.6f   %9.6f\n", i, i, pBitsUse[i], pErrsUse[i]);
      } // for(int i=0; i<256; i++)

      qprintf(fp_dbg, "pErrsUse[160] = %9.6f\n", pErrsUse[160]);
      bDidDbg = true;
   } // if(!bDidDbg && (nNumer == 1) && (nDenom == 1))

   if(pBitsLib != NULL) {
      pBitsLib->mostestBits = mostestBits;
   } // if(pBitsLib != NULL)

   qprintf(fp_dbg, "mostestBits with ratio %d/%d: %d.  We return %8.2f bits.\n", nNumer, nDenom, mostestBits, dRet);  // qxtemp?

   return dRet;
} // double bitsCanTakeScattered()...


//
// new overload 12-04-23: take double dNumer.
//
//
//
//
// 10-08-25: make ajg const (using mutable variables)
//
// qxbitsCanTakeScattered()

double bitsCanTakeScattered(const jgrLil &jgGuy, double dNumer, int nDenom, int maxBits) {
   int nNumerLo = (int)dNumer;
   int nNumerHi = nNumerLo + 1;

   double dFractHi = dNumer - (double)nNumerLo;
   double dFractLo = 1.0 - dFractHi;

   double bctLo = bitsCanTakeScattered(jgGuy, nNumerLo, nDenom, maxBits);
   double bctHi = bitsCanTakeScattered(jgGuy, nNumerHi, nDenom, maxBits);

   double dRet = bctLo * dFractLo    +    bctHi * dFractHi;

   return dRet;
} // double bitsCanTakeScattered(jgrLil &jgGuy, double dNumer, int nDenom)




//
// new 09-25-23
//
// Input:
//    iVal:   current value of the pixel-color.
//    iScatoff: scatter offset.
//    nNumer: numerator of embed ratio.
//    nDenom: denominator of embed ratio.
//    nHurdl: the hurdle the remainder must cross to add an extra bit.
//    maxBits: don't return more than this (if > 0).
//    bDbg:   if true, writes to fp_dbg.
//    pRemainOut (default NULL): if non-NULL, return remainder value used.
//    pValShiftAvg (default NULL): if non-NULL, return average shift in value for embed.
//
// Output:
//    return value: number of bits this pixel-color can hold.
//    pRemainOut (default NULL): if non-NULL, remainder value used
//    pValShiftAvg (default NULL): if non-NULL, average shift in value.
//
// Local:
//    lmask:  an array of all bits set, with gradually more low bits clear.
//    lmrev:  reverse (one's complement) of lmask.
//    pBits:  array of 256: iVal -> max safe embedded bits, not considering top crashing.
//    qBits:  array of 256: iVal -> max safe embbed bits without crashing the top.
//
//
//
// qxcalcScatter()
//

int calcScatter(int iVal, int iScatoff, int nNumer, int nDenom, int nHurdl, int maxBits, bool bDbg, int *pRemainOut, double *pValShiftAvg) {
   int i= 0;

   static bool bInit = false;
   static const int *pBits = NULL;
   static const int *qBits = NULL;
   static int lmask[9], lmrev[9];

   if(!bInit) {
      pBits = getnBitsArr256();
      qBits = getqBitsArr256();

      for(i=0; i<=8; i++) {
         lmask[i] = 0xFFFFFFFF << i;
         lmrev[i] = ~lmask[i];
      } // for(i=0; i<=8; i++)

      bInit = true;
   } // if(!bInit)

   if(pValShiftAvg != NULL)
      *pValShiftAvg = 0.0;


   int nBits = NADA;
   int nGuard = 1;
   std::string ssTmp;
   if(bDbg) {
      qprintf(fp_dbg, "\niVal %d, iScatoff %d, nNumer %d, nDenom %d, nHurdl %d.\n", iVal, iScatoff, nNumer, nDenom, nHurdl);
   } // if(bDbg)

   int nb0   = 8;

   int nbMax = MAX(0, (pBits[iVal] - nGuard));

   if(maxBits > 0)
      nbMax = MIN(nbMax, maxBits);

   std::vector<int> vcUnincrWork;               // nBits that work though we wanted to increment away from them.

   // problems if we don't check this:
   if(iVal == 0)
      return 0;

   bool bSuccess = false;

   for(int nbTry=nbMax; nbTry>=0; nbTry--) {
      int jScatoff = iScatoff;
      int jValMin = ((iVal + jScatoff) & lmask[nbTry])   - jScatoff;
      int jValMax = jValMin + lmrev[nbTry];

      if(bDbg) {
         qprintf(fp_dbg, "\nnbTry %d, jScatoff %d, jValMin %d, jValMax %d.\n", nbTry, jScatoff, jValMin, jValMax);
      } // if(bDbg)

      int nOverflow = 0;
      if(jValMax >= 256) {
         if(bDbg) {
            qprintf(fp_dbg,  "Oh FLOG me: iVal %d --> jValMax %d.\n", iVal, jValMax);
         } // if(bDbg)

         nOverflow++;
      } // if(jValMax >= 256)

      if(jValMin < 0) {
         if(bDbg) {
            qprintf(fp_dbg,  "Oh FLOG me: iVal %d --> jValMin %d.\n", iVal, jValMin);
         } // if(bDbg)

         nOverflow++;
      } // if(jValMin < 0)

      if(nOverflow > 0)
         continue;

      int nBitsHtst = qBits[jValMax];
      if(nBitsHtst < nbTry) {
         if(bDbg) {
            qprintf(fp_dbg, "nBitsHtst %d too small; skipping.\n", nBitsHtst);
         } // if(bDbg)
         continue;
      } // if(nBitsHtst < nbTry)

      // as nbTry falls, jValMin tends to rise, so jBitsHi tends to rise:
      int jBitsHi = pBits[jValMin] - nGuard;  // number of bits we can stuff this time, before ratioing down.
      jBitsHi     = MAX(0, jBitsHi);          // else -1 can scruu us up.

      int juNumer = jBitsHi * nNumer;
      int juInt   = juNumer / nDenom;
      int juRem   = juNumer % nDenom;

      // int nBits   = nuInt;                 // may be increased in a moment...

      int jBits  = juInt;
      int joBits = jBits;                     // before incrementing jBits

      if(juRem > nHurdl) {
         jBits++;
      } else {
      } // if(juRem > nHurdl)

      if(bDbg) {
         qprintf(fp_dbg, "We end up with jBits %d (joBits %d) vs. target nbTry %d.\n", jBits, joBits, nbTry);
      } // if(bDbg)

      if(jBits >= nbTry) {
         bSuccess = true;
         nBits = nbTry;
         if(pRemainOut != NULL)
            *pRemainOut = juRem;

         if(pValShiftAvg != NULL)
            *pValShiftAvg = ((double)jValMax + (double)jValMin) / 2.0    -  (double)iVal;

         break;
      } else if(joBits == nbTry) {
         vcUnincrWork.push_back(joBits);
      } // if(jBits == nbTry)

   } // for(int nbTry=nbMax; nbTry>=0; nbTry--)

   if(bDbg) {
      qprintf(fp_dbg, "calcScatter() returning %d.\n", nBits);
   } // if(bDbg)

   if(nBits < 0) {
      qprintf(fp_dbg, "calcScatter() returning %d.\n", nBits);
      // qprintf(stdout, "calcScatter() returning %d.\n", nBits);
   } // if(nBits < 0)

   return nBits;
} // int calcScatter()...






//
// new 10-22-23: easier to recognize than a hexadecimal fingerprint.
//
//
//
//
//
// qxmakeSillyWord()

std::string makeSillyWord(MT64 &anMT, int nSyllables) {
   static bool bInit = false;
   static std::vector<std::string> vcSsVowels, vcSsBegin, vcSsMid, vcSsEnd;

   if(!bInit) {
      int i = 0;

      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("a");
      vcSsVowels.push_back("ai");
      vcSsVowels.push_back("au");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("e");
      vcSsVowels.push_back("ea");
      vcSsVowels.push_back("ee");
      vcSsVowels.push_back("ei");
      vcSsVowels.push_back("eu");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("i");
      vcSsVowels.push_back("ie");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("o");
      vcSsVowels.push_back("oa");
      vcSsVowels.push_back("oi");
      vcSsVowels.push_back("oo");
      vcSsVowels.push_back("ou");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("u");
      vcSsVowels.push_back("ua");
      vcSsVowels.push_back("uo");


      vcSsBegin.push_back("B");
      vcSsBegin.push_back("Bl");
      vcSsBegin.push_back("Br");
      vcSsBegin.push_back("C");
      vcSsBegin.push_back("Ch");
      vcSsBegin.push_back("Cl");
      vcSsBegin.push_back("Cr");
      vcSsBegin.push_back("D");
      vcSsBegin.push_back("Dr");
      vcSsBegin.push_back("F");
      vcSsBegin.push_back("Fl");
      vcSsBegin.push_back("Fr");
      vcSsBegin.push_back("G");
//    vcSsBegin.push_back("gh");
      vcSsBegin.push_back("Gl");
      vcSsBegin.push_back("Gr");
      vcSsBegin.push_back("H");
      vcSsBegin.push_back("J");
      vcSsBegin.push_back("K");
      vcSsBegin.push_back("Kl");
      vcSsBegin.push_back("Kr");
      vcSsBegin.push_back("L");
      vcSsBegin.push_back("M");
      vcSsBegin.push_back("N");
      vcSsBegin.push_back("P");
      vcSsBegin.push_back("Ph");
      vcSsBegin.push_back("Pl");
      vcSsBegin.push_back("Pr");
      vcSsBegin.push_back("R");
      vcSsBegin.push_back("S");
      vcSsBegin.push_back("Sc");
      vcSsBegin.push_back("Sh");
      vcSsBegin.push_back("Sk");
      vcSsBegin.push_back("Sl");
      vcSsBegin.push_back("Sm");
      vcSsBegin.push_back("Sn");
      vcSsBegin.push_back("Sp");
      vcSsBegin.push_back("Spl");
      vcSsBegin.push_back("Spr");
      vcSsBegin.push_back("St");
      vcSsBegin.push_back("Str");
      vcSsBegin.push_back("T");
      vcSsBegin.push_back("Th");
      vcSsBegin.push_back("Tr");
      vcSsBegin.push_back("V");
      vcSsBegin.push_back("W");
      vcSsBegin.push_back("Y");
      vcSsBegin.push_back("Z");


      vcSsMid.push_back("b");
      vcSsMid.push_back("bl");
      vcSsMid.push_back("br");
      vcSsMid.push_back("c");
      vcSsMid.push_back("ch");
      vcSsMid.push_back("cl");
      vcSsMid.push_back("cr");
      vcSsMid.push_back("d");
      vcSsMid.push_back("dr");
      vcSsMid.push_back("f");
      vcSsMid.push_back("fl");
      vcSsMid.push_back("fr");
      vcSsMid.push_back("g");
//    vcSsMid.push_back("gh");
      vcSsMid.push_back("gl");
      vcSsMid.push_back("gr");
      vcSsMid.push_back("h");
      vcSsMid.push_back("j");
      vcSsMid.push_back("k");
      vcSsMid.push_back("kh");
      vcSsMid.push_back("kl");
      vcSsMid.push_back("kr");
      vcSsMid.push_back("l");
      vcSsMid.push_back("lp");
      vcSsMid.push_back("m");
      vcSsMid.push_back("mm");
      vcSsMid.push_back("n");
      vcSsMid.push_back("nn");
      vcSsMid.push_back("p");
      vcSsMid.push_back("ph");
      vcSsMid.push_back("pl");
      vcSsMid.push_back("pr");
      vcSsMid.push_back("r");
      vcSsMid.push_back("s");
      vcSsMid.push_back("sc");
      vcSsMid.push_back("sh");
      vcSsMid.push_back("sk");
      vcSsMid.push_back("sl");
      vcSsMid.push_back("sm");
      vcSsMid.push_back("sn");
      vcSsMid.push_back("sp");
      vcSsMid.push_back("spl");
      vcSsMid.push_back("spr");
      vcSsMid.push_back("st");
      vcSsMid.push_back("str");
      vcSsMid.push_back("t");
      vcSsMid.push_back("th");
      vcSsMid.push_back("tr");
      vcSsMid.push_back("v");
      vcSsMid.push_back("w");
//    vcSsMid.push_back("y");
      vcSsMid.push_back("z");




      vcSsEnd.push_back("b");
      vcSsEnd.push_back("c");
      vcSsEnd.push_back("ch");
      vcSsEnd.push_back("d");
      vcSsEnd.push_back("f");
      vcSsEnd.push_back("g");
      vcSsEnd.push_back("h");
      vcSsEnd.push_back("k");
      vcSsEnd.push_back("l");
      vcSsEnd.push_back("lf");
      vcSsEnd.push_back("lm");
      vcSsEnd.push_back("m");
      vcSsEnd.push_back("n");
      vcSsEnd.push_back("nd");
      vcSsEnd.push_back("ng");
      vcSsEnd.push_back("p");
      vcSsEnd.push_back("r");
      vcSsEnd.push_back("rd");
      vcSsEnd.push_back("rf");
      vcSsEnd.push_back("rg");
      vcSsEnd.push_back("rm");
      vcSsEnd.push_back("rt");
      vcSsEnd.push_back("s");
      vcSsEnd.push_back("sh");
      vcSsEnd.push_back("st");
      vcSsEnd.push_back("t");
      vcSsEnd.push_back("th");
//    vcSsEnd.push_back("y");
      vcSsEnd.push_back("z");




      bInit = true;
   } // if(!bInit)

   int nVowels = (int)vcSsVowels.size();
   int nBegin  = (int)vcSsBegin.size();
   int nMid    = (int)vcSsMid.size();
   int nEnd    = (int)vcSsEnd.size();

   std::string ssRet;

   int nOuter = MIN(1, nSyllables);
   int nInner = MAX(0, (nSyllables-1));

   for(int i=0; i<nOuter; i++) {
      int iRand = anMT.rand_n_quick(nBegin);
      ssRet += vcSsBegin[iRand];

      iRand = anMT.rand_n_quick(nVowels);
      ssRet += vcSsVowels[iRand];

      for(int j=0; j<nInner; j++) {
         iRand = anMT.rand_n_quick(nMid);
         ssRet += vcSsMid[iRand];

         iRand = anMT.rand_n_quick(nVowels);
         ssRet += vcSsVowels[iRand];
      } // for(int j=0; j<nInner; j++)

      iRand = anMT.rand_n_quick(nEnd);
      ssRet += vcSsEnd[iRand];
   } // for(int i=0; i<nOuter; i++)

   return ssRet;
} // std::string makeSillyWord()...







//
// new 09-22-11 (jdl).  Helper function: non-member, non-friend.
//
//
//
// Input:
//    anMT: guy to be initialized according to two inputs:
//    passwordSeedVector: one source.
//    secondPassword: another source.
//
//  Output:
//    anMT: initialized according to inputs.
//
//
//
//
// qxinitByTwo()
//

static void initByTwo(MT64 &anMT, const std::vector<unsigned long long> &passwordSeedVector, const char *secondPassword) {
   anMT.init_by_array64(&(passwordSeedVector[0]), passwordSeedVector.size());

   UCHAR *puc = new UCHAR[256 + strlen(secondPassword)];
   SmartParr<UCHAR> smaUc(puc);

   int j, k;
   for(j=0; j<128; j++) {
      puc[j] = (UCHAR)anMT.rand_n_quick(256);
   } // for(j=0; j<128; j++)

   for(k=0; k<strlen(secondPassword); k++) {
      puc[j+k] = (UCHAR)secondPassword[k];
   } // for(k=0; k<strlen(secondPassword); k++)

   std::vector<unsigned long long> vecOut;
   MT64::bytesToVector(puc, (j+k), vecOut);

   anMT.init_by_array64(&(vecOut[0]), vecOut.size());

   return;
} // end of static void initByTwo()...





//
// new 06-04-13 (jdl), from initbyTwo().  Helper function: non-member, non-friend.
//
//
//
// Input:
//    anMT: guy to be initialized according to two inputs:
//    passwordSeedVector: one source.
//    secondPassword: another source.
//    thirdPassword: another source.
//
//  Output:
//    anMT: initialized according to inputs.
//
//
//
//
// qxinitByThree()
//

static void initByThree(MT64 &anMT, const std::vector<unsigned long long> &passwordSeedVector, const char *secondPassword, const char *thirdPassword) {
   anMT.init_by_array64(&(passwordSeedVector[0]), passwordSeedVector.size());

   UCHAR *puc = new UCHAR[256 + strlen(secondPassword) + strlen(thirdPassword)];
   SmartParr<UCHAR> smaUc(puc);

   int j, k, L;

   for(j=0; j<128; j++) {
      puc[j] = (UCHAR)anMT.rand_n_quick(256);
   } // for(j=0; j<128; j++)

   for(k=0; k<strlen(secondPassword); k++) {
      puc[j+k] = (UCHAR)secondPassword[k];
   } // for(k=0; k<strlen(secondPassword); k++)

   for(L=0; L<strlen(thirdPassword); L++) {
      puc[j+k+L] = (UCHAR)thirdPassword[L];
   } // for(L=0; L<strlen(thirdPassword); L++)

   std::vector<unsigned long long> vecOut;
   MT64::bytesToVector(puc, (j+k+L), vecOut);

   anMT.init_by_array64(&(vecOut[0]), vecOut.size());

   return;
} // end of static void initByThree()...








//
// new 09-22-11, from applyBytesg2(); this version uses a stuff ratio: 
//    . If I could stuff 7 bits, maybe I stuff 3 or 4.
//    . If I could stuff 3 bits, maybe I stuff 1 or 2.
//
// Note: "stuff" is a transitive verb.  I could have used "apply" ("application"?).
//
//
// this routine "hides" a sequence of bytes in the image.  If we have
// extra room, we scramble the remaining portion w/ random data, so the
// image doesn't suddenly change character.
//
// This is the trickiest of the four.  The number of steg bits we encode into a given pic byte depends upon
// the magnitude of the pic byte.
//
// This method may spread a pic byte's data across more than one steg byte.  So we use bit-shifting tricks.
//
//
// Input:
//    this: the bitmap to be stuffed with steganography bytes
//    cBytes: array of bytes to be stuffed (applied).
//    nBytes: number of bytes in cBytes to be stuffed.
//    nNumer: numerator of the stuff ratio.  Should form a proper fraction with the denominator.
//    nDenom: denominator of the stuff ratio.
//    anMT: instance of class MT64 to get pseudo-random numbers from.
//    bBackward: if true, start from end of file and work backward.
//    bFillEntireFile: if true, keep stuffing the file after all data is applied.
//    bScatter: if true, try to make + and - about balance out.
//    pBitsLib (default NULL): if non-NULL, shortcut to avoid calls to calcScatter().
//    bShuffle (default false): if true and bScatter, less completely random iScatOff.
//    bConserv (default false): if true and !bScatter, fewer calls to MT64 random functions.
//
// Output:
//    return value: NADA if something went wrong; otherwise, sample number "needed" (rough guess).
//
//
//
//
//
// Special case: cBytes == NULL and nBytes == 0: just randomly stuff the file.
//
//
//
//
// 06-03-13 (jdl): make cBytes const.  bBackward; bFillEntireFile.
// 07-04-13 (jdl): qBits (pegged calcs).
// 09-23-23 (jdl): bScatter.
// 10-31-23 (jdl): for Steg2, non-member function.
// 06-02-24 (jdl): pBitsLib (default NULL).
// 05-06-25 (jdl): bShuffle (default false).
// 05-08-25 (jdl): bConserv (default false).
// 05-17-25 (jdl): bTrim.
//
// qxapplyBytesg3()
//

int applyBytesg3(jgrLil &ajg, const UCHAR *cBytes, int nBytes, int nNumer, int nDenom, int maxBits, MT64 &anMT, bool bBackward, bool bFillEntireFile, bool bScatter, const bitsLib *pBitsLib, bool bShuffle, bool bConserv, bool bTrim) {
   int i;
   int nBumped=0, nUnbumped=0;
   int iRet = NADA;
   int bitsNeeded = nBytes * BITS_PER_BYTE;
   int nBitsLost = 0;
   int nLostScat = 0;
   int nGainScat = 0;
   bool bDbg = false;

   if(pBitsLib != NULL) {
      JASSERT(pBitsLib->numer   == nNumer);
      JASSERT(pBitsLib->denom   == nDenom);
      JASSERT(pBitsLib->maxBits == maxBits);
   } // if(pBitsLib != NULL)

   qprintf(fp_dbg, "\nThis is applyBytesg3() with bBackward %d, nBytes %d, nNumer %d; nDenom %d, bScatter %d, bShuffle %d, bTrim %d, bConserv %d.\n", bBackward, nBytes, nNumer, nDenom, bScatter, bShuffle, bTrim, bConserv);

   std::vector<int> vcTotBits;   // by iVal
   std::vector<int> vcNpixies;   // by iVal
   for(int i=0; i<256; i++) {
      vcTotBits.push_back(0);
      vcNpixies.push_back(0);
   } // for(int i=0; i<256; i++)

   MT64 anM2(true);              // true: do skip initialization.
   anM2.init_by_otherMT64(anMT);

   BitSrc aBt2;

#ifdef NOT_USED
   MT64 anM3(true);
   anM3.init_by_otherMT64(anM2);
#endif // NOT_USED

#ifdef TEST_CODE
   FILE *fp_bts = NULL;
   if(bScatter) {
      const char *fileBits = "bitsies.txt";
      arcMeDotty(fileBits);
      fp_bts = fopen(fileBits, "w");
   } // if(bScatter)
#endif // TEST_CODE

   // qprintf(stdout, "about to call bytesCanTakeg3().\n");  // qxtemp
   double bitsPerIval[256];
   double errsPerIval[256];

   if(bScatter) {
      double dBitsScram = bitsCanTakeScattered(ajg, nNumer, nDenom, maxBits, bitsPerIval, errsPerIval);
      double dBytesScram = dBitsScram / (double)BITS_PER_BYTE;
   } // if(bScatter)


   // qprintf(fp_dbg, "applyBytesg3(): bScatter %d, fraction %d/%d: want to stuff %d; we think we can hold %d, or %8.2f scattered.\n", bScatter, nNumer, nDenom, nBytes, iBytee, dBytesScram);
   // qprintf(stdout, "applyBytesg3(): bScatter %d, fraction %d/%d: want to stuff %d; we think we can hold %d, or %8.2f scattered.\n", bScatter, nNumer, nDenom, nBytes, iBytee, dBytesScram);

   // sanity checks:
   if(cBytes == NULL) {
      // ok if nBytes == 0; we'll just do a random stuff of the file.
      if(nBytes != 0) 
         return NADA;
   } // !if(cBytes == NULL)

   if(nDenom <= 0) 
      return NADA;
   if(nNumer <= 0) 
      return NADA;
   if(nNumer == nDenom) {
      // special case: stuff everybody.  not a proper fraction, but we'll allow it.
   } else {
      int nInt = nNumer / nDenom;
      int nRem = nNumer % nDenom;   // remainder; should be nNumer.

      if(nInt != 0)           
         return NADA;                   // I said a PROPER fraction!
      if(nRem != nNumer) 
         return NADA;                   // I said a PROPER fraction!
   } // !if(nNumer == nDenom)

   if(bScatter && bShuffle && (pBitsLib == NULL))
      return NADA;                         // qxl: support?

// #ifdef OLD_STUFF
   Shuffled *pSF = NULL;
   std::vector<int> vcBitOrder;
   if(bScatter && bShuffle) {
      Shuffled::makeVec(nNumer, nDenom, vcBitOrder, bTrim, pBitsLib->mostestBits);
      pSF = new Shuffled(0, &anMT, true, false, &vcBitOrder);    // true: bOneForAll, 256 in length; false: !bRestrict.
   } // if(bShuffle)

   SmartPtr<Shuffled> aspSF(pSF);
// #endif// OLD_STUFF

#ifdef DIDNT_LIKE
   std::vector<SmartPtr<Shuffled>> vcShuffs;
   int whichColor = 0;
   std::vector<int> vcBitOrder;
   if(bShuffle) {
      Shuffled::makeVec(nNumer, nDenom, vcBitOrder, bTrim, pBitsLib->mostestBits);
      vcShuffs.resize(3);
      for(int i=0; i<3; i++)
         vcShuffs[i].newData(new Shuffled(0, &anMT, true, false, &vcBitOrder, true));    // true: bOneForAll, 256 in length; false: !bRestrict.
   } // if(bShuffle)
#endif // DIDNT_LIKE

   BitSrc aBtS;

   // we can do it!
   // set up masks:
   int lmask[9], lmrev[9];
   for(i=0; i<=8; i++) {
      lmask[i] = 0xFFFFFFFF << i;
      lmrev[i] = ~lmask[i];
   } // for(i=0; i<=8; i++)


   int nValBits = 0;       // number of valid bits in the shift register.
   int iShift   = 0;       // actual shift register
   int iByte    = 0;       // index of input array
   int nRand    = 0;       // number of times we pulled random numbers; if > 1, we probably stuffed all steg bytes.
   int bitsStuft=0;        // total number of bits we stuff into the file.
   const int *pBits = getnBitsArr256();
   const int *qBits = getqBitsArr256();
   int ivOut, ihOut, icOut;               // vert, horz, color.

   UCHAR *ucPic = NULL;    // signal to start
   while((ucPic = ajg.getNextBufLoc(ucPic, bBackward, &ivOut, &ihOut, &icOut)) != NULL) {

      // keep iShift filled w/ bits:
      if(nValBits < BITS_PER_BYTE) {
         int iFeed;
         if(iByte < nBytes) {
            iFeed = (int)cBytes[iByte++];
         } else {
            // random junk, never accessed; does not have to be consistent across versions.
//          iFeed = anM2.rand_n_quick(256);   // 09-28-23
            iFeed = aBt2.getNbits(8, &anM2);  // 05-10-25
            nRand++;
         } // !if(iByte < nBytes)
         iShift   |= iFeed << nValBits;
         nValBits += BITS_PER_BYTE;
      } // if(nValBits < BITS_PER_BYTE)


      int iVal    = (int)(*ucPic);

      int iScatoff = 0;                           // scatter offset
      int nHurdl   = anMT.rand_n_quick(nDenom);   // hurdle the remainder must surpass to add a bit.

      // how many bits go into this?  pseudo-random:
      int nBits    = NADA;


      if(!bScatter) {
         int n = 1;   // 1-based dummy value: this is this minimum to fix pBits[].
         int nBitsHi = pBits[iVal] - n;  // number of bits we can stuff this time, before ratioing down.

         nBitsHi = MAX(0, nBitsHi);

         int nuNumer = nBitsHi * nNumer;
         int nuInt   = nuNumer / nDenom;
         int nuRem   = nuNumer % nDenom;

         nBits       = nuInt;                 // may be increased in a moment...

         if(nuRem > nHurdl) {
            nBits++;
            nBumped++;
         } else {
            nUnbumped++;
         } // if(nuRem > nHurdl)

         // 07-04-13 (jdl): possibly reduce for pegged concerns:
         int nBitsH2 = qBits[iVal];  // already "0-based"; no need to subtract anything.
         if(nBitsH2 < nBits) {
            // mprintf(fp_dbg, "0x%02X: Reducing %2d bits to %2d bits.\n", iVal, nBits, nBitsH2);
            nBitsLost += (nBits - nBitsH2);
            nBits      = nBitsH2;
         } // if(nBitsH2 < nBits)

      } else {
         // new: bScatter.
         if(!bShuffle) {
            // always, before 05-06-25:
            if(!bConserv) {
               iScatoff = anMT.rand_n_quick(256);
            } else {
               iScatoff = aBtS.getNbits(8, &anMT);
            } // !if(!bConserv)

            if(pBitsLib == NULL) {
               nBits = calcScatter(iVal, iScatoff, nNumer, nDenom, nHurdl, maxBits, bDbg);
               if(nBits < 0) {
                  qprintf(fp_dbg, "Bad Doggie!\n");
                  nBits = calcScatter(iVal, iScatoff, nNumer, nDenom, nHurdl, maxBits, true);
               } // if(nBits < 0)
            } else {
               nBits = pBitsLib->calcFast(iVal, iScatoff, nHurdl);
            } // !if(pBitsLib == NULL)
         } else {
            // new 05-06-25: bShuffle; qxl: support pBitsLib == NULL:
#ifdef DIDNT_LIKE
            Shuffled *pSF = vcShuffs[whichColor];
            whichColor = (whichColor + 1) % 3;
#endif // DIDNT_LIKE

            iScatoff = pSF->getCurrent(&anMT, false);                // false: !bPeek.
            nBits    = pBitsLib->calcFast(iVal, iScatoff, nHurdl);
         } // !if(!bShuffle)
      } // if(bScatter)


#ifdef TEST_CODE
      qprintf(fp_bts, "%d\n", nBits);
#endif // TEST_CODE

      int newVal = iVal;

      if(nBits > 0) {
         newVal = (((iVal + iScatoff) & lmask[nBits]) | (iShift & lmrev[nBits]))    - iScatoff;
         iShift   >>= nBits;                // expose next bits
         nValBits  -= nBits;
         bitsStuft += nBits;

         *ucPic     = (UCHAR)newVal;

         vcTotBits[iVal] += nBits;
      } // if(nBits > 0)

      vcNpixies[iVal]++;

      // 06-04-13 (jdl): more precise (?) measure of when we've stuffed enough:
      if((iRet < 0) && (bitsStuft >= bitsNeeded)) {
         // then we should have stuffed all the real data by now.
         int jRet = (int)(ucPic - ajg.getBaseBufLoc());           // how far we had to go through the file to finish.
         iRet = ajg.getByteOffset(ucPic);
         qprintf(fp_dbg, "jRet %d; iRet %d; diff %d.\n", jRet, iRet, (jRet - iRet));
      } // if((iRet < 0) && (bitsStuft >= bitsNeeded))


      if(!bFillEntireFile && (iRet >= 0)) break;                 // we've stuffed all we need to.

   } // while((ucPic = ajg.getNextBufLoc(ucPic)) != NULL) {





#ifdef TEST_CODE
   if(fp_bts != NULL)
      {fclose(fp_bts); fp_bts=NULL;}
#endif // TEST_CODE

   ajg.clrHists();

   int bytesStuft = bitsStuft / BITS_PER_BYTE;

   if(!bScatter) {
      qprintf(fp_dbg, "applyBytesg3(): iRet %d, bitsStuft %d (bytes %d).\n", iRet, bitsStuft, bytesStuft);
      // qprintf(stdout, "applyBytesg3(): iRet %d, bitsStuft %d (bytes %d).\n", iRet, bitsStuft, bytesStuft);
   } else {
      qprintf(fp_dbg, "applyBytesg3(): iRet %d, bitsStuft %d (bytes %d); bits not lost to scatter %d.\n", iRet, bitsStuft, bytesStuft, nLostScat);
      // qprintf(stdout, "applyBytesg3(): iRet %d, bitsStuft %d (bytes %d); bits not lost to scatter %d.\n", iRet, bitsStuft, bytesStuft, nLostScat);

      bool bDbg = true;
      if(bDbg && bScatter) {
         // qxtemp:
//       double safety = -LOG_2((double)nNumer / (double)nDenom);
         double safety =  LOG_2((double)nDenom / (double)nNumer);

         qprintf(fp_dbg, "\nUsing %d/%d (safety %.2f):\n\n", nNumer, nDenom, safety);

         for(i=0; i<256; i++) {
            int nPix = vcNpixies[i];
            if(nPix == 0) {
               qprintf(fp_dbg, "   %3d  (not represented)\n", i);
            } else {
               double dBits = (double)vcTotBits[i] / (double)nPix;
               qprintf(fp_dbg, "   %3d  %8.6f   (%7d samples): change from predicted %8.6f: %9.6f (errs? %9.6f)\n", i, dBits, nPix, bitsPerIval[i], (dBits - bitsPerIval[i]), errsPerIval[i]);
            } // if(nPix == 0)
         } // for(i=0; i<256; i++)
      } // if(bDbg)

   } // !if(!bScatter)

   if(iRet < 0) {
      qprintf(fp_dbg, "bitsNeeded %d; bitsStuft %d; shy %d.\n", bitsNeeded, bitsStuft, (bitsNeeded - bitsStuft));
   } // if(iRet < 0)

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








//
//
// new 09-22-11, from extractBytesg2(); this version uses a stuff ratio: 
//    . If I could extract 7 bits, maybe I extract 3 or 4.
//    . If I could extract 3 bits, maybe I extract 1 or 2.
//
// Input:
//    this: the bitmap stuffed with steganography bytes
//    cBytes: array of bytes to be extracted.
//    nValidIn: max number of bytes to be extracted into cBytes.
//    nNumer: numerator of the stuff ratio.  Should form a proper fraction with the denominator.
//    nDenom: denominator of the stuff ratio.
//    maxBits
//    anMT: instance of class MT64 to get pseudo-random numbers from.
//    bBackward: if true, start from end of file and work backward.
//    bScatter: if true, try to make + and - about balance out.
//    pBitsLib (default NULL): if non-NULL, shortcut to avoid calls to calcScatter().
//    bShuffle (default false): if true and bScatter, less completely random iScatOff.
//    bConserv (default false): if true and !bScatter, fewer calls to MT64 random functions.
//
// this routine extracts a hidden sequence of bytes in the image.  We
// don't know how much to extract, so we'll suck out the whole file.
//
// The caller may allocate cBytes (nValidIn worth); we're pissed off if
// nValidIn isn't enough.  Or, we will allocate the data here.
//
// This is the trickiest of the four.  The number of steg bits we encode into a given pic byte depends upon
// the magnitude of the pic byte.
//
// This method may spread a pic byte's data across more than one steg byte.  So we use bit-shifting tricks.
//
//
// return value: number of bytes filled.
//
//
//
// 06-03-13 (jdl): bBackward.
// 07-05-13 (jdl): allow 1/1 (or any n/n) fraction.
// 09-23-23 (jdl): bScatter.
// 01-04-24 (jdl): maxBits.
// 06-03-24 (jdl): pBitsLib (default NULL).
// 05-06-25 (jdl): bShuffle (default false).
// 05-08-25 (jdl): bConserv (default false).
// 05-17-25 (jdl): bTrim    (default false).
// 10-08-25: make ajg const (using mutable variables)
//
// qxextractBytesg3()
//


int extractBytesg3(const jgrLil &ajg, UCHAR *&cBytes, int nValidIn, int nNumer, int nDenom, int maxBits, MT64 &anMT, bool bBackward, bool bScatter, const bitsLib *pBitsLib, bool bShuffle, bool bConserv, bool bTrim) {
   int i;
   int nBumped=0, nUnbumped=0;
   int nBitsLost = 0;
   bool bDbg = false;

   if(pBitsLib != NULL) {
      JASSERT(pBitsLib->numer   == nNumer);
      JASSERT(pBitsLib->denom   == nDenom);
      JASSERT(pBitsLib->maxBits == maxBits);
   } // if(pBitsLib != NULL)

   qprintf(fp_dbg, "\nThis is extractBytesg3() with bBackward %d, nValidIn %d, nNumer %d; nDenom %d, bScatter %d, bShuffle %d, bTrim %d, bConserv %d.\n", bBackward, nValidIn, nNumer, nDenom, bScatter, bShuffle, bTrim, bConserv);


   if(nDenom <= 0) return NADA;
   if(nNumer <= 0) return NADA;
   if(nNumer == nDenom) {
      // special case: stuff everybody.  not a proper fraction, but we'll allow it.
   } else {
      int nGuardInt = nNumer / nDenom;
      int nGuardRem = nNumer % nDenom;   // remainder; should be nNumer.

      if(nGuardInt != 0)           return NADA;   // I said a PROPER fraction!
      if(nGuardRem != nNumer) return NADA;   // I said a PROPER fraction!
   } // !if(nNumer == nDenom)


   if(bScatter && bShuffle && (pBitsLib == NULL))
      return NADA;                         // qxl: support?

// #ifdef OLD_STUFF
   Shuffled *pSF = NULL;
   std::vector<int> vcBitOrder;
   if(bScatter && bShuffle) {
      Shuffled::makeVec(nNumer, nDenom, vcBitOrder, bTrim, pBitsLib->mostestBits);
      pSF = new Shuffled(0, &anMT, true, false, &vcBitOrder);    // true: bOneForAll, 256 in length; false: !bRestrict.
   } // if(bShuffle)
   SmartPtr<Shuffled> aspSF(pSF);
// #endif // OLD_STUFF

#ifdef DIDNT_LIKE
   std::vector<SmartPtr<Shuffled>> vcShuffs;
   int whichColor = 0;
   std::vector<int> vcBitOrder;
   if(bShuffle) {
      Shuffled::makeVec(nNumer, nDenom, vcBitOrder, bTrim, pBitsLib->mostestBits);
      vcShuffs.resize(3);
      for(int i=0; i<3; i++)
         vcShuffs[i].newData(new Shuffled(0, &anMT, true, false, &vcBitOrder, true));    // true: bOneForAll, 256 in length; false: !bRestrict.
   } // if(bShuffle)
#endif // DIDNT_LIKE

   BitSrc aBtS;



// qxhere
   int bytesOut  = NADA;
   int maxOut    = bytesCanTakeg3(ajg, nNumer, nDenom, maxBits);      // approximate...
   int maxMore   = (int)(0.5 + (double)maxOut * 1.5);                 // should be plenty to suck the whole file.


   int didAllo;
   if(nValidIn == 0) {
      // we will allocate now:
      JASSERT(cBytes == NULL);
      cBytes = new UCHAR[maxMore];
      bytesOut = maxMore;                                             // but we probably won't get this far!
      didAllo = 1;
   } else {
      didAllo = 0;
      JASSERT(cBytes != NULL);

      bytesOut = nValidIn;                                            // we can go this far, but probably won't, unless small array passed in.

   }


   // we can do it!
   // set up masks:
   int lmask[9], lmrev[9];
   for(i=0; i<=8; i++) {
      lmask[i] = 0xFFFFFFFF << i;
      lmrev[i] = ~lmask[i];
   } // for(i=0; i<=8; i++)


   const UCHAR *ucPic = NULL;  // signal to start
   int nValBits = 0;           // number of valid bits in the shift register.
   int iShift   = 0;           // actual shift register
   int iByte    = 0;           // index of input array
   const int *pBits = getnBitsArr256();
   const int *qBits = getqBitsArr256();


   while((ucPic = ajg.getNextBufLoc(ucPic, bBackward)) != NULL) {

      int iVal    = (int)(*ucPic);

      int iScatoff = 0;                  // scatter offset
      int nHurdl = anMT.rand_n_quick(nDenom);   // hurdle the remainder must surpass to add a bit.

      // how many bits come out of this?  pseudo-random:
      int nBits  = NADA;

      if(!bScatter) {
         int nGuard = 1;   // to start with...
         int nBitsHi = pBits[iVal] - nGuard;  // number of bits we can suck this time, before ratioing down.

         // if((nBitsHi < 1) && !bScatter) continue;            // nothing to stuff!  bScatter fear: subtract
         if(nBitsHi < 1) {
            // continue;                                           // bScatter can't save.
            nBits = 0;
         } else {
            // normal:

            int nuNumer = nBitsHi * nNumer;
            int nuInt   = nuNumer / nDenom;
            int nuRem   = nuNumer % nDenom;

            nBits   = nuInt;                 // may be increased in a moment...

            if(nuRem > nHurdl) {
               nBumped++;
               nBits++;
            } else {
               nUnbumped++;
            } // !if(nuRem > nHurdl)


            // 07-04-13 (jdl): possibly reduce for pegged concerns:
            int nBitsH2 = qBits[iVal];  // already "0-based"; no need to subtract anything.
            if(nBitsH2 < nBits) {
               // mprintf(fp_dbg, "0x%02X: Reducing %2d bits to %2d bits.\n", iVal, nBits, nBitsH2);
               nBitsLost += (nBits - nBitsH2);
               nBits      = nBitsH2;
            } // if(nBitsH2 < nBits)
         } // !if(nBitsHi < 1)

      } else {
         // new: bScatter:
         if(!bShuffle) {
            // always, before 05-06-25:
            if(!bConserv) {
               iScatoff = anMT.rand_n_quick(256);
            } else {
               iScatoff = aBtS.getNbits(8, &anMT);
            } // !if(!bConserv)

            if(pBitsLib == NULL) {
               nBits = calcScatter(iVal, iScatoff, nNumer, nDenom, nHurdl, maxBits, bDbg);
            } else {
               nBits = pBitsLib->calcFast(iVal, iScatoff, nHurdl);
            } // !if(pBitsLib == NULL)
         } else {
            // new 05-06-25: bShuffle; qxl: support pBitsLib == NULL:
#ifdef DIDNT_LIKE
            Shuffled *pSF = vcShuffs[whichColor];
            whichColor = (whichColor + 1) % 3;
#endif // DIDNT_LIKE

            iScatoff = pSF->getCurrent(&anMT, false);                // false: !bPeek.
            nBits    = pBitsLib->calcFast(iVal, iScatoff, nHurdl);
         } // !if(!bShuffle)
      } // if(bScatter)


      int nVal  = (iVal + iScatoff) & lmrev[nBits];   // keep low bits...
      iShift   |= nVal << nValBits;
      nValBits += nBits;

      // empty iShift if we have a full 8 bits ready to go:
      if(nValBits >= BITS_PER_BYTE) {
         int iOut        = iShift & 0xFF;
         cBytes[iByte++] = (UCHAR)iOut;
         iShift          = iShift >> BITS_PER_BYTE;
         nValBits       -= BITS_PER_BYTE;

         // are we done?
         if(iByte >= bytesOut) break;
      } // if(nValBits < BITS_PER_BYTE)

   } // while((ucPic = ajg.getNextBufLoc(ucPic)) != NULL) {


   qprintf(fp_dbg, "extractBytesg3(): maxOut %d, maxMore %d; fantasy bytesOut %d; nBumped %d, nUnbumped %d; am returning %d bytes; nBitsNotLost %d.\n", maxOut, maxMore, bytesOut, nBumped, nUnbumped, iByte, nBitsLost);

   return iByte;  // not the inflated bytesOut;
} // end of jGraph::extractBytesg3(UCHAR *&cBytes, int nValidIn, int nNumer, int nDenom, MT64 &anMT)...







//
// new 08-30-08, from bytesCanTakec().  We deal in guard bits.
//
// This is the trickiest of the four.  The number of steg bits we encode into a given pic byte depends upon
// the magnitude of the pic byte.
//
// This method may spread a pic byte's data across more than one steg byte.  So we use bit-shifting tricks.
//
//
// Input:
//    nGuard: must be at least 1.  I think that would be called nGuard 0 in .wav guy (nob.exe).
//
//
// 10-08-25 (jdl): return bits, not bytes.
// 10-08-25: make ajg const (using mutable variables)
//
// qxbitsCanTakeg()
//

int bitsCanTakeg(const jgrLil &ajg, int nGuard) {

   // sanity checks:
   if(nGuard  <= 0) return NADA;   // silly "1-based" nGuard convention.
   if(nGuard   > 7) return NADA;   // that's a reasonable max.

   int bytesAvail   = ajg.nHoriz() * ajg.nVert() * ajg.colorBytesPerPixel();   // used only for sanity check.

   int bitsCanTake = 0;
   int spotsCum    = 0;   // sanity check against bytesAvail...
   for(int iBits=0; iBits<=8; iBits++) {
      int nSpotsThis = ajg.histLil(iBits);    // number of pixel colors which have this number of bits in brightness.
      spotsCum += nSpotsThis;
      int nbitsThis  = nSpotsThis * (iBits - nGuard);
      bitsCanTake += MAX(nbitsThis, 0);         // we'll never take information away!
   } // for(iBits=0; iBits<=8; iBits++)

   // JASSERT(spotsCum == bytesAvail);

   int bytesCanTake  = bitsCanTake / BITS_PER_BYTE;

// return bytesCanTake;
   return bitsCanTake;
} // end of bitsCanTakeg()...







//
// new 09-23-11.  This returns a ROUGH value, dependent upon (pseudo)random fluctuations when we actually pack/unpack.
//
//
// Input:
//    nNumer: numerator of number of guard bits.  Should form a proper fraction with the denominator.
//    nDenom: denominator of number of guard bits.
//
// Output:
//    return value: approximate number of bytes "this" can hold; NADA if something went wrong.
//
//
// 07-05-13 (jdl): rework for new pegged restrictions.
// 10-08-25 (jdl): return bits, not bytes.
// 10-08-25: make ajg const (using mutable variables)
// 10-09-25: maxBits.
//
// qxbitsCanTakeg3()
//

int bitsCanTakeg3(const jgrLil &ajg, int nNumer, int nDenom, int maxBits) {

   double dMaxB = (double)((maxBits <= 0) ? (8) : (maxBits));

// qxhere

   if(nDenom <= 0) return NADA;
   if(nNumer <= 0) return NADA;
   if(nNumer == nDenom) {
      // special case: stuff everybody.  not a proper fraction, but we'll allow it.
   } else {
      int nInt = nNumer / nDenom;
      int nRem = nNumer % nDenom;   // remainder; should be nNumer.

      if(nInt != 0)           return NADA;   // I said a PROPER fraction!
      if(nRem != nNumer) return NADA;   // I said a PROPER fraction!
   } // !if(nNumer == nDenom)


   double dValp, dValq, dValMin, dBitsThisClass;
   double dRat   = (double)nNumer / (double)nDenom;
   double dBitsTot = 0.0;
   int npTot = 0;
   int nqTot = 0;
   for(int i=0; i<7; i++) {
      int np = ajg.histp(i);   // m_histp[i];    // np: number of pixels that support i p-bits. (brightness 00 - 7F)
      int nq = ajg.histq(i);   // m_histq[i];    // nq: number of pixels that support i q-bits. (brightness 80 - FF)
      npTot += np;
      nqTot += nq;

      // p-side calc (pixel brightness 00 - 7F):
      dValp = (double)i * dRat;
      dValq = (double)7;
      dValMin = MI3(dValp, dValq, dMaxB);   // always dValp here (unless dMaxB), but let's go thru the exercise...
      dBitsThisClass = dValMin * (double)np;
      dBitsTot += dBitsThisClass;

      // q-side calc (pixel brightness 80 - FF):
      dValp = (double)7 * dRat;
      dValq = (double)i;
      dValMin = MI3(dValp, dValq, dMaxB);   // could be either, depending upon dRat.
      dBitsThisClass = dValMin * (double)nq;
      dBitsTot += dBitsThisClass;

   } // for(int i=0; i<7; i++)
   int nTot = npTot + nqTot;
   int bytesAvail   = ajg.nHoriz() * ajg.nVert() * ajg.colorBytesPerPixel();   // used only for sanity check.
   JASSERT(nTot == bytesAvail);

   int nBitsTot = (int)dBitsTot;
   int nBytesAvg = nBitsTot / BITS_PER_BYTE;

   // just for fun:
   int nBytesFull  = bytesCanTakegPegged(ajg, 1);     // the maximum number; the n "1" is phantom.
   double dBytes   = (double)nBytesFull * (double)nNumer / (double)nDenom;
   int nBytesOther = (int)dBytes;


   qprintf(fp_dbg, "bitsCanTakeg3(): %d/%d = %9.6f (maxBits %d); we return %d bits, about %d bytes (compare %d).\n", nNumer, nDenom, dRat, maxBits, nBitsTot, nBytesAvg, nBytesOther);


// return nBytesAvg;
   return nBitsTot;
} // end of bitsCanTakeg3()...






//
// new 07-05-13, from bytesCanTakeg().  Reduced horribly for pegged bits.
//
//
//
//
// Input:
//    nGuard: must be at least 1.  I think that would be called nGuard 0 in .wav guy (nob.exe).
//
//
// 10-08-25: make ajg const (using mutable variables)
//
// qxbytesCanTakegPegged()
//

int bytesCanTakegPegged(const jgrLil &ajg, int nGuard) {

   // sanity checks:
   if(nGuard  <= 0) return NADA;   // silly "1-based" nGuard convention.
   if(nGuard   > 7) return NADA;   // that's a reasonable max.

   int bytesAvail   = ajg.nHoriz() * ajg.nVert() * ajg.colorBytesPerPixel();   // used only for sanity check.

   int bitsCanTake = 0;
   int spotsCum    = 0;   // sanity check against bytesAvail...
   for(int iBits=0; iBits<=8; iBits++) {
      int nSpotsThis = ajg.histLi2(iBits);    // number of pixel colors which have this number of bits in brightness.
      spotsCum += nSpotsThis;
      int nbitsThis  = nSpotsThis * (iBits - nGuard);
      bitsCanTake += MAX(nbitsThis, 0);         // we'll never take information away!
   } // for(iBits=0; iBits<=8; iBits++)

   JASSERT(spotsCum == bytesAvail);

   int bytesCanTake  = bitsCanTake / BITS_PER_BYTE;  // round off down.

   return bytesCanTake;
} // end of bytesCanTakegPegged()...



