Blob Blame Raw


#include "trop.h"
#include "loop_macros.h"
#include "tpixelutils.h"

#ifndef TNZCORE_LIGHT
#include "tpalette.h"
#include "tcolorstyles.h"
#endif

/*
#ifndef __sgi
#include <algorithm>
#endif
*/

// The following must be old IRIX code. Should be re-tested.
// It seems that gcc compiles it, but requiring a LOT of
// resources... very suspect...

/*#ifdef __LP64__
#include "optimize_for_lp64.h"
#endif*/

//=============================================================================
//=============================================================================
//=============================================================================

#ifdef OPTIMIZE_FOR_LP64
void quickResample_optimized(const TRasterP &dn, const TRasterP &up,
                             const TAffine &aff,
                             TRop::ResampleFilterType filterType);
#endif

namespace {

inline TPixel32 applyColorScale(const TPixel32 &color,
                                const TPixel32 &colorScale,
                                bool toBePremultiplied = false) {
  /*--
   * 半透明のラスタをViewer上で半透明にquickputするとき、色が暗くなってしまうのを防ぐ
   * --*/
  if (colorScale.r == 0 && colorScale.g == 0 && colorScale.b == 0) {
    /*--
     * toBePremultipliedがONのときは、後でPremultiplyをするので、ここでは行わない
     * --*/
    if (toBePremultiplied)
      return TPixel32(color.r, color.g, color.b, color.m * colorScale.m / 255);
    else
      return TPixel32(
          color.r * colorScale.m / 255, color.g * colorScale.m / 255,
          color.b * colorScale.m / 255, color.m * colorScale.m / 255);
  }
  int r = color.r + colorScale.r;
  int g = color.g + colorScale.g;
  int b = color.b + colorScale.b;

  return premultiply(TPixel32(r > 255 ? 255 : r, g > 255 ? 255 : g,
                              b > 255 ? 255 : b, color.m * colorScale.m / 255));
}

//------------------------------------------------------------------------------

inline TPixel32 applyColorScaleCMapped(const TPixel32 &color,
                                       const TPixel32 &colorScale) {
  int r = color.r + colorScale.r;
  int g = color.g + colorScale.g;
  int b = color.b + colorScale.b;

  return premultiply(TPixel32(r > 255 ? 255 : r, g > 255 ? 255 : g,
                              b > 255 ? 255 : b, color.m * colorScale.m / 255));
}

//------------------------------------------------------------------------------

void doQuickPutFilter(const TRaster32P &dn, const TRaster32P &up,
                      const TAffine &aff) {
  //  se aff e' degenere la controimmagine di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));
  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel successivo
  //  comporta l'incremento (deltaXD, deltaYD) delle coordinate del pixel
  //  corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  // segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  //	naturale predecessore di up->getLx() - 1
  int lxPred = (up->getLx() - 2) * (1 << PADN);

  //	naturale predecessore di up->getLy() - 1
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();
  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel32 *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //	(1)    equazione k-parametrica della y-esima
    //               scanline di boundingBoxD:
    //	       (xMin, y) + k*(1, 0),  k = 0, ..., (xMax - xMin)

    //	(2)    equazione k-parametrica dell'immagine mediante
    //               invAff di (1):
    //	       invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //	       k = kMin, ..., kMax
    //               con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente intersecando la (2)
    //  con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff
    //  della porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //        TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //	(xL0, yL0) sono le coordinate di a in versione "TLonghizzata"
    //	0 <= xL0 + k*deltaXL
    //          <= (up->getLx() - 2)*(1<<PADN), 0
    //          <= kMinX
    //          <= kMin
    //          <= k
    //          <= kMax
    //          <= kMaxX
    //          <= (xMax - xMin)
    //
    //	0 <= yL0 + k*deltaYL
    //          <= (up->getLy() - 2)*(1<<PADN), 0
    //          <= kMinY
    //          <= kMin
    //          <= k
    //          <= kMax
    //          <= kMaxY
    //          <= (yMax - yMin)
    int xL0 = tround(a.x * (1 << PADN));  //  xL0 inizializzato
    int yL0 = tround(a.y * (1 << PADN));  //  yL0 inizializzato

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

    //        0 <= xL0 + k*deltaXL <= (up->getLx() - 2)*(1<<PADN)
    //                   <=>
    //        0 <= xL0 + k*deltaXL <= lxPred
    //
    //
    //	0 <= yL0 + k*deltaYL <= (up->getLy() - 2)*(1<<PADN)
    //                   <=>
    //        0 <= yL0 + k*deltaYL <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      //  [a, b] verticale esterno ad up contratto
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      if (lxPred < xL0)  //  [a, b] esterno ad up+(bordo destro)
        continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      if (xL0 < 0)  //  [a, b] esterno ad up contratto
        continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up contratto
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      //  altrimenti usa solo
      //  kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      if (lyPred < yL0)  //  [a, b] esterno ad up contratto
        continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      if (yL0 < 0)  //  [a, b] esterno ad up contratto
        continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clipping su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata"
    //	del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN))
      //  e' approssimato con (xI, yI)
      int xI = xL >> PADN;  //	troncato
      int yI = yL >> PADN;  //	troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixel32 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixel32 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixel32 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixel32 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo dei pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;
      int yWeight1 = (yL & MASKN);
      int yWeight0 = (1 << PADN) - yWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int rColDownTmp =
          (xWeight0 * (upPix00->r) + xWeight1 * ((upPix10)->r)) >> PADN;

      int gColDownTmp =
          (xWeight0 * (upPix00->g) + xWeight1 * ((upPix10)->g)) >> PADN;

      int bColDownTmp =
          (xWeight0 * (upPix00->b) + xWeight1 * ((upPix10)->b)) >> PADN;

      int rColUpTmp =
          (xWeight0 * ((upPix01)->r) + xWeight1 * ((upPix11)->r)) >> PADN;

      int gColUpTmp =
          (xWeight0 * ((upPix01)->g) + xWeight1 * ((upPix11)->g)) >> PADN;

      int bColUpTmp =
          (xWeight0 * ((upPix01)->b) + xWeight1 * ((upPix11)->b)) >> PADN;

      unsigned char rCol =
          (unsigned char)((yWeight0 * rColDownTmp + yWeight1 * rColUpTmp) >>
                          PADN);

      unsigned char gCol =
          (unsigned char)((yWeight0 * gColDownTmp + yWeight1 * gColUpTmp) >>
                          PADN);

      unsigned char bCol =
          (unsigned char)((yWeight0 * bColDownTmp + yWeight1 * bColUpTmp) >>
                          PADN);

      TPixel32 upPix = TPixel32(rCol, gCol, bCol, upPix00->m);

      if (upPix.m == 0)
        continue;
      else if (upPix.m == 255)
        *dnPix = upPix;
      else
        *dnPix = quickOverPix(*dnPix, upPix);
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
//=============================================================================

void doQuickPutNoFilter(const TRaster32P &dn, const TRaster32P &up,
                        const TAffine &aff, const TPixel32 &colorScale,
                        bool doPremultiply, bool whiteTransp, bool firstColumn,
                        bool doRasterDarkenBlendedView) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate del
  //  pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  //  TINT32 predecessore di up->getLx()
  int lxPred = up->getLx() * (1 << PADN) - 1;

  //  TINT32 predecessore di up->getLy()
  int lyPred = up->getLy() * (1 << PADN) - 1;

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel32 *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //  (1)  equazione k-parametrica della y-esima scanline di boundingBoxD:
    //       (xMin, y) + k*(1, 0),  k = 0, ..., (xMax - xMin)

    //  (2)  equazione k-parametrica dell'immagine mediante invAff di (1):
    //       invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //       k = kMin, ..., kMax con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente
    //  intersecando la (2) con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff della
    //  porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //  TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
    //  in versione "TLonghizzata"
    //  0 <= xL0 + k*deltaXL
    //    <  up->getLx()*(1<<PADN)
    //
    //  0 <= kMinX
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxX
    //    <= (xMax - xMin)
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)

    //  0 <= kMinY
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxY
    //    <= (xMax - xMin)

    //  xL0 inizializzato per il round
    int xL0 = tround((a.x + 0.5) * (1 << PADN));

    //  yL0 inizializzato per il round
    int yL0 = tround((a.y + 0.5) * (1 << PADN));

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    //  0 <= xL0 + k*deltaXL
    //    < up->getLx()*(1<<PADN)
    //           <=>
    //  0 <= xL0 + k*deltaXL
    //    <= lxPred
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)
    //           <=>
    //  0 <= yL0 + k*deltaYL
    //    <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      // [a, b] verticale esterno ad up+(bordo destro/basso)
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up+(bordo destro/basso)
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      // altrimenti usa solo
      // kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixel32 upPix = *(upBasePix + (yI * upWrap + xI));

      if (firstColumn) upPix.m = 255;
      if (upPix.m == 0 || (whiteTransp && upPix == TPixel::White)) continue;

      if (colorScale != TPixel32::Black)
        upPix = applyColorScale(upPix, colorScale, doPremultiply);

      if (doRasterDarkenBlendedView)
        *dnPix = quickOverPixDarkenBlended(*dnPix, upPix);
      else {
        if (upPix.m == 255)
          *dnPix = upPix;
        else if (doPremultiply)
          *dnPix = quickOverPixPremult(*dnPix, upPix);
        else
          *dnPix = quickOverPix(*dnPix, upPix);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
//=============================================================================

void doQuickPutNoFilter(const TRaster32P &dn, const TRaster64P &up,
                        const TAffine &aff, bool doPremultiply,
                        bool firstColumn) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate del
  //  pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  //  TINT32 predecessore di up->getLx()
  int lxPred = up->getLx() * (1 << PADN) - 1;

  //  TINT32 predecessore di up->getLy()
  int lyPred = up->getLy() * (1 << PADN) - 1;

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel64 *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //  (1)  equazione k-parametrica della y-esima scanline di boundingBoxD:
    //       (xMin, y) + k*(1, 0),  k = 0, ..., (xMax - xMin)

    //  (2)  equazione k-parametrica dell'immagine mediante invAff di (1):
    //       invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //       k = kMin, ..., kMax con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente
    //  intersecando la (2) con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff della
    //  porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //  TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
    //  in versione "TLonghizzata"
    //  0 <= xL0 + k*deltaXL
    //    <  up->getLx()*(1<<PADN)
    //
    //  0 <= kMinX
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxX
    //    <= (xMax - xMin)
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)

    //  0 <= kMinY
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxY
    //    <= (xMax - xMin)

    //  xL0 inizializzato per il round
    int xL0 = tround((a.x + 0.5) * (1 << PADN));

    //  yL0 inizializzato per il round
    int yL0 = tround((a.y + 0.5) * (1 << PADN));

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    //  0 <= xL0 + k*deltaXL
    //    < up->getLx()*(1<<PADN)
    //           <=>
    //  0 <= xL0 + k*deltaXL
    //    <= lxPred
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)
    //           <=>
    //  0 <= yL0 + k*deltaYL
    //    <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      // [a, b] verticale esterno ad up+(bordo destro/basso)
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up+(bordo destro/basso)
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      // altrimenti usa solo
      // kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixel64 *upPix           = upBasePix + (yI * upWrap + xI);
      if (firstColumn) upPix->m = 65535;
      if (upPix->m == 0)
        continue;
      else if (upPix->m == 65535)
        *dnPix = PixelConverter<TPixel32>::from(*upPix);
      else if (doPremultiply)
        *dnPix =
            quickOverPixPremult(*dnPix, PixelConverter<TPixel32>::from(*upPix));
      else
        *dnPix = quickOverPix(*dnPix, PixelConverter<TPixel32>::from(*upPix));
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================

void doQuickPutNoFilter(const TRaster32P &dn, const TRasterGR8P &up,
                        const TAffine &aff, const TPixel32 &colorScale) {
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;
  const int PADN = 16;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  int deltaXL = tround(deltaXD * (1 << PADN));

  int deltaYL = tround(deltaYD * (1 << PADN));

  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred = up->getLx() * (1 << PADN) - 1;

  int lyPred = up->getLy() * (1 << PADN) - 1;

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow      = dn->pixels(yMin);
  TPixelGR8 *upBasePix = up->pixels();

  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);

    int xL0 = tround((a.x + 0.5) * (1 << PADN));

    int yL0 = tround((a.y + 0.5) * (1 << PADN));

    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up+(bordo destro/basso)
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      // altrimenti usa solo
      // kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixelGR8 *upPix = upBasePix + (yI * upWrap + xI);
      if (colorScale == TPixel32::Black) {
        if (upPix->value == 0)
          dnPix->r = dnPix->g = dnPix->b = 0;
        else if (upPix->value == 255)
          dnPix->r = dnPix->g = dnPix->b = upPix->value;
        else
          *dnPix = quickOverPix(*dnPix, *upPix);
        dnPix->m = 255;
      } else {
        TPixel32 upPix32(upPix->value, upPix->value, upPix->value, 255);
        upPix32 = applyColorScale(upPix32, colorScale);

        if (upPix32.m == 255)
          *dnPix = upPix32;
        else
          *dnPix = quickOverPix(*dnPix, upPix32);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================

void doQuickPutFilter(const TRaster32P &dn, const TRaster32P &up, double sx,
                      double sy, double tx, double ty) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;

  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //	nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //    successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //    pixels corrispondenti di up

  //    nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //    successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //    pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //	(1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //	       (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //	         kX = 0, ..., (xMax - xMin),
  //             kY = 0, ..., (yMax - yMin)

  //	(2)  equazione (kX, kY)-parametrica dell'immagine
  //         mediante invAff di (1):
  //	       invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //	         kX = kMinX, ..., kMaxX
  //               con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //	         kY = kMinY, ..., kMaxY
  //               con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up (con gli estremi eventualmente invertiti) e'
  //  la controimmagine
  //  mediante aff della porzione di scanline  [ (xMin, yMin), (xMax, yMin) ]
  //  di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round) in
  //  versione "TLonghizzata"
  //
  //    0 <= xL0 + kX*deltaXL
  //      <= (up->getLx() - 2)*(1<<PADN),
  //    0 <= kMinX <= kX
  //      <= kMaxX <= (xMax - xMin)
  //
  //	0 <= yL0 + kY*deltaYL
  //      <= (up->getLy() - 2)*(1<<PADN),
  //    0 <= kMinY <= kY
  //      <= kMaxY <= (yMax - yMin)
  int xL0 = tround(a.x * (1 << PADN));  //  xL0 inizializzato
  int yL0 = tround(a.y * (1 << PADN));  //  yL0 inizializzato

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati
  //  di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di (up->getLx() - 1)
  int lxPred = (up->getLx() - 2) * (1 << PADN);

  //  TINT32 predecessore di (up->getLy() - 1)
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  //  0 <= xL0 + k*deltaXL
  //    <= (up->getLx() - 2)*(1<<PADN)
  //             <=>
  //  0 <= xL0 + k*deltaXL <= lxPred
  //
  //  0 <= yL0 + k*deltaYL
  //    <= (up->getLy() - 2)*(1<<PADN)
  //             <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con
  //  i lati (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(yL0 <= lyPred);
    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //	calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con
  //  i lati (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *upBasePix = up->pixels();
  TPixel32 *dnRow     = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata" del pixel corrente di up

  //  inizializza yL
  int yL = yL0 + (kMinY - 1) * deltaYL;

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    //  inizializza xL
    int xL = xL0 + (kMinX - 1) * deltaXL;
    yL += deltaYL;
    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  troncato

    //  filtro bilineare 4 pixels: calcolo degli y-pesi
    int yWeight1 = (yL & MASKN);
    int yWeight0 = (1 << PADN) - yWeight1;

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixel32 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixel32 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixel32 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixel32 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo degli x-pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int rColDownTmp =
          (xWeight0 * (upPix00->r) + xWeight1 * ((upPix10)->r)) >> PADN;

      int gColDownTmp =
          (xWeight0 * (upPix00->g) + xWeight1 * ((upPix10)->g)) >> PADN;

      int bColDownTmp =
          (xWeight0 * (upPix00->b) + xWeight1 * ((upPix10)->b)) >> PADN;

      int rColUpTmp =
          (xWeight0 * ((upPix01)->r) + xWeight1 * ((upPix11)->r)) >> PADN;

      int gColUpTmp =
          (xWeight0 * ((upPix01)->g) + xWeight1 * ((upPix11)->g)) >> PADN;

      int bColUpTmp =
          (xWeight0 * ((upPix01)->b) + xWeight1 * ((upPix11)->b)) >> PADN;

      unsigned char rCol =
          (unsigned char)((yWeight0 * rColDownTmp + yWeight1 * rColUpTmp) >>
                          PADN);

      unsigned char gCol =
          (unsigned char)((yWeight0 * gColDownTmp + yWeight1 * gColUpTmp) >>
                          PADN);

      unsigned char bCol =
          (unsigned char)((yWeight0 * bColDownTmp + yWeight1 * bColUpTmp) >>
                          PADN);

      TPixel32 upPix = TPixel32(rCol, gCol, bCol, upPix00->m);

      if (upPix.m == 0)
        continue;
      else if (upPix.m == 255)
        *dnPix = upPix;
      else
        *dnPix = quickOverPix(*dnPix, upPix);
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================
void doQuickPutNoFilter(const TRaster32P &dn, const TRaster32P &up, double sx,
                        double sy, double tx, double ty,
                        const TPixel32 &colorScale, bool doPremultiply,
                        bool whiteTransp, bool firstColumn,
                        bool doRasterDarkenBlendedView) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));
  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  // disponibili per la parte intera di xL, yL)

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //	(1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //	       (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //	         kX = 0, ..., (xMax - xMin),
  //             kY = 0, ..., (yMax - yMin)

  //	(2)  equazione (kX, kY)-parametrica dell'immagine
  //         mediante invAff di (1):
  //	       invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //	         kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //             kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up e' la controimmagine mediante aff della
  // porzione di scanline  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  // in versione "TLonghizzata"
  //	0 <= xL0 + kX*deltaXL
  //      < up->getLx()*(1<<PADN),
  //
  //    0 <= kMinX
  //      <= kX
  //      <= kMaxX
  //      <= (xMax - xMin)

  //	0 <= yL0 + kY*deltaYL
  //      < up->getLy()*(1<<PADN),
  //
  //    0 <= kMinY
  //      <= kY
  //      <= kMaxY
  //      <= (yMax - yMin)

  //  xL0 inizializzato per il round
  int xL0 = tround((a.x + 0.5) * (1 << PADN));

  //  yL0 inizializzato per il round
  int yL0 = tround((a.y + 0.5) * (1 << PADN));

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di up->getLx()
  int lxPred = up->getLx() * (1 << PADN) - 1;

  //  TINT32 predecessore di up->getLy()
  int lyPred = up->getLy() * (1 << PADN) - 1;

  //  0 <= xL0 + k*deltaXL < up->getLx()*(1<<PADN)
  //            <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL < up->getLy()*(1<<PADN)
  //            <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //  calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *upBasePix = up->pixels();
  TPixel32 *dnRow     = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata" del pixel corrente di up

  //  inizializza yL
  int yL = yL0 + (kMinY - 1) * deltaYL;

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    //  inizializza xL
    int xL = xL0 + (kMinX - 1) * deltaXL;
    yL += deltaYL;

    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  round

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //	round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixel32 upPix = *(upBasePix + (yI * upWrap + xI));

      if (firstColumn) upPix.m = 65535;

      if (upPix.m == 0 || (whiteTransp && upPix == TPixel::White)) continue;

      if (colorScale != TPixel32::Black)
        upPix = applyColorScale(upPix, colorScale, doPremultiply);

      if (doRasterDarkenBlendedView)
        *dnPix = quickOverPixDarkenBlended(*dnPix, upPix);
      else {
        if (upPix.m == 255)
          *dnPix = upPix;
        else if (doPremultiply)
          *dnPix = quickOverPixPremult(*dnPix, upPix);
        else
          *dnPix = quickOverPix(*dnPix, upPix);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
void doQuickPutNoFilter(const TRaster32P &dn, const TRasterGR8P &up, double sx,
                        double sy, double tx, double ty,
                        const TPixel32 &colorScale) {
  if ((sx == 0) || (sy == 0)) return;

  const int PADN = 16;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;
  int deltaXL    = tround(deltaXD * (1 << PADN));
  int deltaYL    = tround(deltaYD * (1 << PADN));
  if ((deltaXL == 0) || (deltaYL == 0)) return;
  TPointD a = invAff * TPointD(xMin, yMin);

  int xL0   = tround((a.x + 0.5) * (1 << PADN));
  int yL0   = tround((a.y + 0.5) * (1 << PADN));
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn
  int lxPred = up->getLx() * (1 << PADN) - 1;
  int lyPred = up->getLy() * (1 << PADN) - 1;

  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixelGR8 *upBasePix = up->pixels();
  TPixel32 *dnRow      = dn->pixels(yMin + kMinY);

  int yL = yL0 + (kMinY - 1) * deltaYL;

  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    //  inizializza xL
    int xL = xL0 + (kMinX - 1) * deltaXL;
    yL += deltaYL;

    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  round

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      int xI = xL >> PADN;  //	round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixelGR8 *upPix = upBasePix + (yI * upWrap + xI);
      if (colorScale == TPixel32::Black) {
        dnPix->r = dnPix->g = dnPix->b = upPix->value;
        dnPix->m                       = 255;
      } else {
        TPixel32 upPix32(upPix->value, upPix->value, upPix->value, 255);
        upPix32 = applyColorScale(upPix32, colorScale);

        if (upPix32.m == 255)
          *dnPix = upPix32;
        else
          *dnPix = quickOverPix(*dnPix, upPix32);
      }

      /*
if (upPix->value == 0)
dnPix->r = dnPix->g = dnPix->b = dnPix->m = upPix->value;
else if (upPix->value == 255)
dnPix->r = dnPix->g = dnPix->b = dnPix->m = upPix->value;
else
*dnPix = quickOverPix(*dnPix, *upPix);
*/
    }
  }
  dn->unlock();
  up->unlock();
}

void doQuickResampleFilter(const TRaster32P &dn, const TRaster32P &up,
                           const TAffine &aff) {
  //  se aff e' degenere la controimmagine di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));
  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)

  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate
  //  del pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  // naturale predecessore di up->getLx() - 1
  int lxPred = (up->getLx() - 2) * (1 << PADN);

  //  naturale predecessore di up->getLy() - 1
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel32 *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //  (1)  equazione k-parametrica della y-esima scanline di boundingBoxD:
    //         (xMin, y) + k*(1, 0),  k = 0, ..., (xMax - xMin)
    //
    //  (2)  equazione k-parametrica dell'immagine mediante invAff di (1):
    //         invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //           k = kMin, ..., kMax
    //           con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente intersecando
    //  la (2) con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff della
    //  porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //  TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //  (xL0, yL0) sono le coordinate di a in versione "TLonghizzata"
    //  0 <= xL0 + k*deltaXL
    //    <= (up->getLx() - 2)*(1<<PADN),
    //  0 <= kMinX
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxX
    //    <= (xMax - xMin)

    //  0 <= yL0 + k*deltaYL
    //    <= (up->getLy() - 2)*(1<<PADN),
    //  0 <= kMinY
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxY
    //    <= (xMax - xMin)

    //  xL0 inizializzato
    int xL0 = tround(a.x * (1 << PADN));

    //  yL0 inizializzato
    int yL0 = tround(a.y * (1 << PADN));

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    //  0 <= xL0 + k*deltaXL <= (up->getLx() - 2)*(1<<PADN)
    //             <=>
    //  0 <= xL0 + k*deltaXL <= lxPred

    //  0 <= yL0 + k*deltaYL <= (up->getLy() - 2)*(1<<PADN)
    //             <=>
    //  0 <= yL0 + k*deltaYL <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      //  [a, b] verticale esterno ad up contratto
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      //  [a, b] esterno ad up+(bordo destro)
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      //  [a, b] esterno ad up contratto
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      // [a, b] orizzontale esterno ad up contratto
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      //  altrimenti usa solo
      //  kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up contratto
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up contratto
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "longhizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //	troncato
      int yI = yL >> PADN;  //	troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixel32 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixel32 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixel32 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixel32 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo dei pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;
      int yWeight1 = (yL & MASKN);
      int yWeight0 = (1 << PADN) - yWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int rColDownTmp =
          (xWeight0 * (upPix00->r) + xWeight1 * ((upPix10)->r)) >> PADN;

      int gColDownTmp =
          (xWeight0 * (upPix00->g) + xWeight1 * ((upPix10)->g)) >> PADN;

      int bColDownTmp =
          (xWeight0 * (upPix00->b) + xWeight1 * ((upPix10)->b)) >> PADN;

      int mColDownTmp =
          (xWeight0 * (upPix00->m) + xWeight1 * ((upPix10)->m)) >> PADN;

      int rColUpTmp =
          (xWeight0 * ((upPix01)->r) + xWeight1 * ((upPix11)->r)) >> PADN;

      int gColUpTmp =
          (xWeight0 * ((upPix01)->g) + xWeight1 * ((upPix11)->g)) >> PADN;

      int bColUpTmp =
          (xWeight0 * ((upPix01)->b) + xWeight1 * ((upPix11)->b)) >> PADN;

      int mColUpTmp =
          (xWeight0 * ((upPix01)->m) + xWeight1 * ((upPix11)->m)) >> PADN;

      dnPix->r =
          (unsigned char)((yWeight0 * rColDownTmp + yWeight1 * rColUpTmp) >>
                          PADN);
      dnPix->g =
          (unsigned char)((yWeight0 * gColDownTmp + yWeight1 * gColUpTmp) >>
                          PADN);
      dnPix->b =
          (unsigned char)((yWeight0 * bColDownTmp + yWeight1 * bColUpTmp) >>
                          PADN);
      dnPix->m =
          (unsigned char)((yWeight0 * mColDownTmp + yWeight1 * mColUpTmp) >>
                          PADN);
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================

void doQuickResampleFilter(const TRaster32P &dn, const TRasterGR8P &up,
                           const TAffine &aff) {
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  const int PADN = 16;

  const int MASKN = (1 << PADN) - 1;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL    = tround(deltaXD * (1 << PADN));
  int deltaYL    = tround(deltaYD * (1 << PADN));
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred = (up->getLx() - 2) * (1 << PADN);
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow      = dn->pixels(yMin);
  TPixelGR8 *upBasePix = up->pixels();

  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);
    int xL0   = tround(a.x * (1 << PADN));
    int yL0   = tround(a.y * (1 << PADN));
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else {
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    if (deltaYL == 0) {
      if ((yL0 < 0) || (lyPred < yL0)) continue;
    } else if (deltaYL > 0) {
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      int xI = xL >> PADN;  //	troncato
      int yI = yL >> PADN;  //	troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixelGR8 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixelGR8 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixelGR8 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixelGR8 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo dei pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;
      int yWeight1 = (yL & MASKN);
      int yWeight0 = (1 << PADN) - yWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int colDownTmp =
          (xWeight0 * (upPix00->value) + xWeight1 * ((upPix10)->value)) >> PADN;

      int colUpTmp =
          (xWeight0 * ((upPix01)->value) + xWeight1 * ((upPix11)->value)) >>
          PADN;

      dnPix->r = dnPix->g = dnPix->b =
          (unsigned char)((yWeight0 * colDownTmp + yWeight1 * colUpTmp) >>
                          PADN);

      dnPix->m = 255;
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================

void doQuickResampleColorFilter(const TRaster32P &dn, const TRaster32P &up,
                                const TAffine &aff, UCHAR colorMask) {
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;
  const int PADN = 16;

  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);  //  clipping y su dn
  int yMax =
      std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);  //  clipping y su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);        //  clipping x su dn
  int xMax =
      std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);  //  clipping x su dn

  TAffine invAff = inv(aff);  //  inversa di aff

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred =
      up->getLx() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLx()
  int lyPred =
      up->getLy() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLy()

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel32 *upBasePix = up->pixels();

  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);
    int xL0   = tround((a.x + 0.5) * (1 << PADN));
    int yL0   = tround((a.y + 0.5) * (1 << PADN));
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn
    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX              = (lxPred - xL0) / deltaXL;          //  floor
      if (xL0 < 0) kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    } else                                                    //  (deltaXL < 0)
    {
      if (xL0 < 0) continue;
      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0)
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
    if (deltaYL == 0) {
      if ((yL0 < 0) || (lyPred < yL0)) continue;
    } else if (deltaYL > 0) {
      if (lyPred < yL0) continue;

      kMaxY              = (lyPred - yL0) / deltaYL;          //  floor
      if (yL0 < 0) kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    } else                                                    //  (deltaYL < 0)
    {
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0)
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
    int kMin           = std::max({kMinX, kMinY, (int)0});
    int kMax           = std::min({kMaxX, kMaxY, xMax - xMin});
    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;
    int xL             = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL             = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      if (colorMask == TRop::MChan)
        dnPix->r = dnPix->g = dnPix->b = (upBasePix + (yI * upWrap + xI))->m;
      else {
        TPixel32 *pix = upBasePix + (yI * upWrap + xI);
        dnPix->r      = ((colorMask & TRop::RChan) ? pix->r : 0);
        dnPix->g      = ((colorMask & TRop::GChan) ? pix->g : 0);
        dnPix->b      = ((colorMask & TRop::BChan) ? pix->b : 0);
      }
      dnPix->m = 255;
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================

//=============================================================================
//=============================================================================
//=============================================================================

void doQuickResampleColorFilter(const TRaster32P &dn, const TRaster64P &up,
                                const TAffine &aff, UCHAR colorMask) {
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;
  const int PADN = 16;

  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);  //  clipping y su dn
  int yMax =
      std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);  //  clipping y su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);        //  clipping x su dn
  int xMax =
      std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);  //  clipping x su dn

  TAffine invAff = inv(aff);  //  inversa di aff

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred =
      up->getLx() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLx()
  int lyPred =
      up->getLy() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLy()

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel64 *upBasePix = up->pixels();

  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);
    int xL0   = tround((a.x + 0.5) * (1 << PADN));
    int yL0   = tround((a.y + 0.5) * (1 << PADN));
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn
    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX              = (lxPred - xL0) / deltaXL;          //  floor
      if (xL0 < 0) kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    } else                                                    //  (deltaXL < 0)
    {
      if (xL0 < 0) continue;
      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0)
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
    if (deltaYL == 0) {
      if ((yL0 < 0) || (lyPred < yL0)) continue;
    } else if (deltaYL > 0) {
      if (lyPred < yL0) continue;

      kMaxY              = (lyPred - yL0) / deltaYL;          //  floor
      if (yL0 < 0) kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    } else                                                    //  (deltaYL < 0)
    {
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0)
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
    int kMin           = std::max({kMinX, kMinY, (int)0});
    int kMax           = std::min({kMaxX, kMaxY, xMax - xMin});
    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;
    int xL             = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL             = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      if (colorMask == TRop::MChan)
        dnPix->r = dnPix->g = dnPix->b =
            byteFromUshort((upBasePix + (yI * upWrap + xI))->m);
      else {
        TPixel64 *pix = upBasePix + (yI * upWrap + xI);
        dnPix->r = byteFromUshort(((colorMask & TRop::RChan) ? pix->r : 0));
        dnPix->g = byteFromUshort(((colorMask & TRop::GChan) ? pix->g : 0));
        dnPix->b = byteFromUshort(((colorMask & TRop::BChan) ? pix->b : 0));
      }
      dnPix->m = 255;
    }
  }
  dn->unlock();
  up->unlock();
}
//=============================================================================
//=============================================================================
//=============================================================================

void doQuickResampleFilter(const TRaster32P &dn, const TRaster32P &up,
                           double sx, double sy, double tx, double ty) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  // disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getSize())) *
      (aff * TRectD(0, 0, up->getLx() - /*1*/ 2, up->getLy() - /*1*/ 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e'
  //  un segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //  (1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //         (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //           kX = 0, ..., (xMax - xMin),
  //           kY = 0, ..., (yMax - yMin)

  //  (2)  equazione (kX, kY)-parametrica dell'immagine mediante invAff di (1):
  //         invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //           kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //           kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up (con gli estremi eventualmente invertiti) e'
  //  la controimmagine mediante aff della porzione di scanline
  //  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  0 <= xL0 + kX*deltaXL <= (up->getLx() - 2)*(1<<PADN),
  //  0 <= kMinX <= kX <= kMaxX <= (xMax - xMin)
  //  0 <= yL0 + kY*deltaYL <= (up->getLy() - 2)*(1<<PADN),
  //  0 <= kMinY <= kY <= kMaxY <= (yMax - yMin)

  int xL0 = tround(a.x * (1 << PADN));  //  xL0 inizializzato
  int yL0 = tround(a.y * (1 << PADN));  //  yL0 inizializzato

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di (up->getLx() - 1)
  int lxPred = (up->getLx() - /*1*/ 2) * (1 << PADN);

  //  TINT32 predecessore di (up->getLy() - 1)
  int lyPred = (up->getLy() - /*1*/ 2) * (1 << PADN);

  //  0 <= xL0 + k*deltaXL <= (up->getLx() - 2)*(1<<PADN)
  //               <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL <= (up->getLy() - 2)*(1<<PADN)
  //               <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //  calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();

  dn->lock();
  up->lock();
  TPixel32 *upBasePix = up->pixels();
  TPixel32 *dnRow     = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  del pixel corrente di up
  int yL = yL0 + (kMinY - 1) * deltaYL;  //  inizializza yL

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    int xL = xL0 + (kMinX - 1) * deltaXL;  //  inizializza xL
    yL += deltaYL;
    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  troncato

    //  filtro bilineare 4 pixels: calcolo degli y-pesi
    int yWeight1 = (yL & MASKN);
    int yWeight0 = (1 << PADN) - yWeight1;

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixel32 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixel32 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixel32 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixel32 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo degli x-pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int rColDownTmp =
          (xWeight0 * (upPix00->r) + xWeight1 * ((upPix10)->r)) >> PADN;

      int gColDownTmp =
          (xWeight0 * (upPix00->g) + xWeight1 * ((upPix10)->g)) >> PADN;

      int bColDownTmp =
          (xWeight0 * (upPix00->b) + xWeight1 * ((upPix10)->b)) >> PADN;

      int mColDownTmp =
          (xWeight0 * (upPix00->m) + xWeight1 * ((upPix10)->m)) >> PADN;

      int rColUpTmp =
          (xWeight0 * ((upPix01)->r) + xWeight1 * ((upPix11)->r)) >> PADN;

      int gColUpTmp =
          (xWeight0 * ((upPix01)->g) + xWeight1 * ((upPix11)->g)) >> PADN;

      int bColUpTmp =
          (xWeight0 * ((upPix01)->b) + xWeight1 * ((upPix11)->b)) >> PADN;

      int mColUpTmp =
          (xWeight0 * ((upPix01)->m) + xWeight1 * ((upPix11)->m)) >> PADN;

      dnPix->r =
          (unsigned char)((yWeight0 * rColDownTmp + yWeight1 * rColUpTmp) >>
                          PADN);
      dnPix->g =
          (unsigned char)((yWeight0 * gColDownTmp + yWeight1 * gColUpTmp) >>
                          PADN);
      dnPix->b =
          (unsigned char)((yWeight0 * bColDownTmp + yWeight1 * bColUpTmp) >>
                          PADN);
      dnPix->m =
          (unsigned char)((yWeight0 * mColDownTmp + yWeight1 * mColUpTmp) >>
                          PADN);
    }
  }
  dn->unlock();
  up->unlock();
}

//------------------------------------------------------------------------------------------

void doQuickResampleFilter(const TRaster32P &dn, const TRasterGR8P &up,
                           double sx, double sy, double tx, double ty) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  // disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getSize())) *
      (aff * TRectD(0, 0, up->getLx() - /*1*/ 2, up->getLy() - /*1*/ 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e'
  //  un segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //  (1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //         (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //           kX = 0, ..., (xMax - xMin),
  //           kY = 0, ..., (yMax - yMin)

  //  (2)  equazione (kX, kY)-parametrica dell'immagine mediante invAff di (1):
  //         invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //           kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //           kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up (con gli estremi eventualmente invertiti) e'
  //  la controimmagine mediante aff della porzione di scanline
  //  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  0 <= xL0 + kX*deltaXL <= (up->getLx() - 2)*(1<<PADN),
  //  0 <= kMinX <= kX <= kMaxX <= (xMax - xMin)
  //  0 <= yL0 + kY*deltaYL <= (up->getLy() - 2)*(1<<PADN),
  //  0 <= kMinY <= kY <= kMaxY <= (yMax - yMin)

  int xL0 = tround(a.x * (1 << PADN));  //  xL0 inizializzato
  int yL0 = tround(a.y * (1 << PADN));  //  yL0 inizializzato

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di (up->getLx() - 1)
  int lxPred = (up->getLx() - /*1*/ 2) * (1 << PADN);

  //  TINT32 predecessore di (up->getLy() - 1)
  int lyPred = (up->getLy() - /*1*/ 2) * (1 << PADN);

  //  0 <= xL0 + k*deltaXL <= (up->getLx() - 2)*(1<<PADN)
  //               <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL <= (up->getLy() - 2)*(1<<PADN)
  //               <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //  calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();

  dn->lock();
  up->lock();
  TPixelGR8 *upBasePix = up->pixels();
  TPixel32 *dnRow      = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  del pixel corrente di up
  int yL = yL0 + (kMinY - 1) * deltaYL;  //  inizializza yL

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    int xL = xL0 + (kMinX - 1) * deltaXL;  //  inizializza xL
    yL += deltaYL;
    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  troncato

    //  filtro bilineare 4 pixels: calcolo degli y-pesi
    int yWeight1 = (yL & MASKN);
    int yWeight0 = (1 << PADN) - yWeight1;

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  troncato

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      //  (xI, yI)
      TPixelGR8 *upPix00 = upBasePix + (yI * upWrap + xI);

      //  (xI + 1, yI)
      TPixelGR8 *upPix10 = upPix00 + 1;

      //  (xI, yI + 1)
      TPixelGR8 *upPix01 = upPix00 + upWrap;

      //  (xI + 1, yI + 1)
      TPixelGR8 *upPix11 = upPix00 + upWrap + 1;

      //  filtro bilineare 4 pixels: calcolo degli x-pesi
      int xWeight1 = (xL & MASKN);
      int xWeight0 = (1 << PADN) - xWeight1;

      //  filtro bilineare 4 pixels: media pesata sui singoli canali
      int colDownTmp =
          (xWeight0 * (upPix00->value) + xWeight1 * (upPix10->value)) >> PADN;

      int colUpTmp =
          (xWeight0 * ((upPix01)->value) + xWeight1 * (upPix11->value)) >> PADN;

      dnPix->m = 255;
      dnPix->r = dnPix->g = dnPix->b =
          (unsigned char)((yWeight0 * colDownTmp + yWeight1 * colUpTmp) >>
                          PADN);
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
//=============================================================================
template <typename PIX>
void doQuickResampleNoFilter(const TRasterPT<PIX> &dn, const TRasterPT<PIX> &up,
                             double sx, double sy, double tx, double ty) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));
  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //  (1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //         (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //           kX = 0, ..., (xMax - xMin),
  //           kY = 0, ..., (yMax - yMin)

  //  (2)  equazione (kX, kY)-parametrica dell'immagine mediante invAff di (1):
  //         invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //           kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //           kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up e' la controimmagine mediante aff della
  //  porzione di scanline  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  0 <= xL0 + kX*deltaXL < up->getLx()*(1<<PADN),
  //  0 <= kMinX <= kX <= kMaxX <= (xMax - xMin)

  //  0 <= yL0 + kY*deltaYL < up->getLy()*(1<<PADN),
  //  0 <= kMinY <= kY <= kMaxY <= (yMax - yMin)
  int xL0 =
      tround((a.x + 0.5) * (1 << PADN));  //  xL0 inizializzato per il round
  int yL0 =
      tround((a.y + 0.5) * (1 << PADN));  //  yL0 inizializzato per il round

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn
  int lxPred =
      up->getLx() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLx()
  int lyPred =
      up->getLy() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLy()

  //  0 <= xL0 + k*deltaXL < up->getLx()*(1<<PADN)
  //                  <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL < up->getLy()*(1<<PADN)
  //                  <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY  intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    assert(yL0 <= lyPred);  //  [a, b] interno ad up+(bordo destro/basso)
    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //	calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX  intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  PIX *upBasePix = up->pixels();
  PIX *dnRow     = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata" del pixel corrente di up
  int yL = yL0 + (kMinY - 1) * deltaYL;  //  inizializza yL

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    int xL = xL0 + (kMinX - 1) * deltaXL;  //  inizializza xL
    yL += deltaYL;
    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  round

    PIX *dnPix    = dnRow + xMin + kMinX;
    PIX *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      *dnPix = *(upBasePix + (yI * upWrap + xI));
    }
  }

  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
//=============================================================================

#ifndef TNZCORE_LIGHT

//=============================================================================
//=============================================================================
//=============================================================================
//=============================================================================
//
// doQuickPutCmapped
//
//=============================================================================

void doQuickPutCmapped(const TRaster32P &dn, const TRasterCM32P &up,
                       const TPaletteP &palette, const TAffine &aff,
                       const TPixel32 &globalColorScale, bool inksOnly) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate del
  //  pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  //  TINT32 predecessore di up->getLx()
  int lxPred = up->getLx() * (1 << PADN) - 1;

  //  TINT32 predecessore di up->getLy()
  int lyPred = up->getLy() * (1 << PADN) - 1;

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();

  std::vector<TPixel32> colors(palette->getStyleCount());
  // vector<TPixel32> inks(palette->getStyleCount());

  if (globalColorScale != TPixel::Black)
    for (int i  = 0; i < palette->getStyleCount(); i++)
      colors[i] = applyColorScaleCMapped(
          palette->getStyle(i)->getAverageColor(), globalColorScale);
  else
    for (int i  = 0; i < palette->getStyleCount(); i++)
      colors[i] = ::premultiply(palette->getStyle(i)->getAverageColor());

  dn->lock();
  up->lock();

  TPixel32 *dnRow       = dn->pixels(yMin);
  TPixelCM32 *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //  (1)  equazione k-parametrica della y-esima scanline di boundingBoxD:
    //       (xMin, y) + k*(1, 0),  k = 0, ..., (xMax - xMin)

    //  (2)  equazione k-parametrica dell'immagine mediante invAff di (1):
    //       invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //       k = kMin, ..., kMax con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente
    //  intersecando la (2) con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff della
    //  porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //  TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
    //  in versione "TLonghizzata"
    //  0 <= xL0 + k*deltaXL
    //    <  up->getLx()*(1<<PADN)
    //
    //  0 <= kMinX
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxX
    //    <= (xMax - xMin)
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)

    //  0 <= kMinY
    //    <= kMin
    //    <= k
    //    <= kMax
    //    <= kMaxY
    //    <= (xMax - xMin)

    //  xL0 inizializzato per il round
    int xL0 = tround((a.x + 0.5) * (1 << PADN));

    //  yL0 inizializzato per il round
    int yL0 = tround((a.y + 0.5) * (1 << PADN));

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn

    //  0 <= xL0 + k*deltaXL
    //    < up->getLx()*(1<<PADN)
    //           <=>
    //  0 <= xL0 + k*deltaXL
    //    <= lxPred
    //
    //  0 <= yL0 + k*deltaYL
    //    < up->getLy()*(1<<PADN)
    //           <=>
    //  0 <= yL0 + k*deltaYL
    //    <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      // [a, b] verticale esterno ad up+(bordo destro/basso)
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up+(bordo destro/basso)
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      // altrimenti usa solo
      // kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;

      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixelCM32 *upPix = upBasePix + (yI * upWrap + xI);
      int t             = upPix->getTone();
      int p             = upPix->getPaint();

      if (t == 0xff && p == 0)
        continue;
      else {
        int i = upPix->getInk();
        TPixel32 colorUp;
        if (inksOnly) switch (t) {
          case 0:
            colorUp = colors[i];
            break;
          case 255:
            colorUp = TPixel::Transparent;
            break;
          default:
            colorUp = antialias(colors[i], 255 - t);
            break;
          }
        else
          switch (t) {
          case 0:
            colorUp = colors[i];
            break;
          case 255:
            colorUp = colors[p];
            break;
          default:
            colorUp = blend(colors[i], colors[p], t, TPixelCM32::getMaxTone());
            break;
          }

        if (colorUp.m == 255)
          *dnPix = colorUp;
        else if (colorUp.m != 0)
          *dnPix = quickOverPix(*dnPix, colorUp);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//
// doQuickPutCmapped + transparencyCheck + inkCheck + paintcheck
//
//=============================================================================
/*
TPixel TransparencyCheckBlackBgInk = TPixel(255,255,255); //bg
TPixel TransparencyCheckWhiteBgInk = TPixel(0,0,0);    //ink
TPixel TransparencyCheckPaint = TPixel(127,127,127);  //paint*/

void doQuickPutCmapped(const TRaster32P &dn, const TRasterCM32P &up,
                       const TPaletteP &palette, const TAffine &aff,
                       const TRop::CmappedQuickputSettings &s)
/*const TPixel32& globalColorScale,
                 bool inksOnly,
                 bool transparencyCheck,
                 bool blackBgCheck,
                 int inkIndex,
                 int paintIndex)*/
{
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;
  const int PADN = 16;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));
  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;
  int yMin       = std::max(tfloor(boundingBoxD.y0), 0);
  int yMax       = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);
  int xMin       = std::max(tfloor(boundingBoxD.x0), 0);
  int xMax       = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);
  TAffine invAff = inv(aff);
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL    = tround(deltaXD * (1 << PADN));
  int deltaYL    = tround(deltaYD * (1 << PADN));
  if ((deltaXL == 0) && (deltaYL == 0)) return;
  int lxPred = up->getLx() * (1 << PADN) - 1;
  int lyPred = up->getLy() * (1 << PADN) - 1;
  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  std::vector<TPixel32> paints(palette->getStyleCount());
  std::vector<TPixel32> inks(palette->getStyleCount());

  if (s.m_transparencyCheck)
    for (int i = 0; i < palette->getStyleCount(); i++) {
      paints[i] = s.m_transpCheckPaint;
      inks[i]   = s.m_blackBgCheck ? s.m_transpCheckBg : s.m_transpCheckInk;
    }
  else if (s.m_globalColorScale == TPixel::Black)
    for (int i  = 0; i < palette->getStyleCount(); i++)
      paints[i] = inks[i] =
          ::premultiply(palette->getStyle(i)->getAverageColor());
  else
    for (int i  = 0; i < palette->getStyleCount(); i++)
      paints[i] = inks[i] = applyColorScaleCMapped(
          palette->getStyle(i)->getAverageColor(), s.m_globalColorScale);

  dn->lock();
  up->lock();

  TPixel32 *dnRow       = dn->pixels(yMin);
  TPixelCM32 *upBasePix = up->pixels();
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);
    int xL0   = tround((a.x + 0.5) * (1 << PADN));
    int yL0   = tround((a.y + 0.5) * (1 << PADN));
    int kMinX = 0, kMaxX = xMax - xMin;
    int kMinY = 0, kMaxY = xMax - xMin;
    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX              = (lxPred - xL0) / deltaXL;          //  floor
      if (xL0 < 0) kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    } else {
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0)
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
    if (deltaYL == 0) {
      if ((yL0 < 0) || (lyPred < yL0)) continue;
    } else if (deltaYL > 0) {
      if (lyPred < yL0) continue;

      kMaxY              = (lyPred - yL0) / deltaYL;
      if (yL0 < 0) kMinY = ((-yL0) + deltaYL - 1) / deltaYL;
    } else {
      if (yL0 < 0) continue;
      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0)
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;
    int xL             = xL0 + (kMin - 1) * deltaXL;
    int yL             = yL0 + (kMin - 1) * deltaYL;
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      int xI = xL >> PADN;
      int yI = yL >> PADN;
      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));
      TPixelCM32 *upPix = upBasePix + (yI * upWrap + xI);
      int t             = upPix->getTone();
      int p             = upPix->getPaint();

      if (t == 0xff && p == 0)
        continue;
      else {
        int i = upPix->getInk();
        TPixel32 colorUp;
        if (s.m_inksOnly) switch (t) {
          case 0:
            colorUp = (i == s.m_inkIndex) ? TPixel::Red : inks[i];
            break;
          case 255:
            colorUp = TPixel::Transparent;
            break;
          default: {
            TPixel inkColor;
            if (i == s.m_inkIndex) {
              inkColor = TPixel::Red;
              if (p == 0) {
                t = t / 2;  // transparency check(for a bug!) darken
                            // semitrasparent pixels; ghibli likes it, and wants
                            // it also for ink checks...
                // otherwise, ramps goes always from reds towards grey...
              }
            } else
              inkColor = inks[i];

            colorUp = antialias(inkColor, 255 - t);
            break;
          }
          }
        else
          switch (t) {
          case 0:
            colorUp = (i == s.m_inkIndex) ? TPixel::Red : inks[i];
            break;
          case 255:
            colorUp = (p == s.m_paintIndex) ? TPixel::Red : paints[p];
            break;
          default: {
            TPixel paintColor = (p == s.m_paintIndex) ? TPixel::Red : paints[p];
            TPixel inkColor;
            if (i == s.m_inkIndex) {
              inkColor = TPixel::Red;
              if (p == 0) {
                paintColor = TPixel::Transparent;
              }
            } else
              inkColor = inks[i];

            if (s.m_transparencyCheck) t = t / 2;

            colorUp = blend(inkColor, paintColor, t, TPixelCM32::getMaxTone());
            break;
          }
          }

        if (colorUp.m == 255)
          *dnPix = colorUp;
        else if (colorUp.m != 0)
          *dnPix = quickOverPix(*dnPix, colorUp);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
void doQuickPutCmapped(const TRaster32P &dn, const TRasterCM32P &up,
                       const TPaletteP &palette, double sx, double sy,
                       double tx, double ty, const TPixel32 &globalColorScale,
                       bool inksOnly) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));
  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  // disponibili per la parte intera di xL, yL)

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //	(1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //	       (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //	         kX = 0, ..., (xMax - xMin),
  //             kY = 0, ..., (yMax - yMin)

  //	(2)  equazione (kX, kY)-parametrica dell'immagine
  //         mediante invAff di (1):
  //	       invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //	         kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //             kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up e' la controimmagine mediante aff della
  // porzione di scanline  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  // in versione "TLonghizzata"
  //	0 <= xL0 + kX*deltaXL
  //      < up->getLx()*(1<<PADN),
  //
  //    0 <= kMinX
  //      <= kX
  //      <= kMaxX
  //      <= (xMax - xMin)

  //	0 <= yL0 + kY*deltaYL
  //      < up->getLy()*(1<<PADN),
  //
  //    0 <= kMinY
  //      <= kY
  //      <= kMaxY
  //      <= (yMax - yMin)

  //  xL0 inizializzato per il round
  int xL0 = tround((a.x + 0.5) * (1 << PADN));

  //  yL0 inizializzato per il round
  int yL0 = tround((a.y + 0.5) * (1 << PADN));

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di up->getLx()
  int lxPred = up->getLx() * (1 << PADN) - 1;

  //  TINT32 predecessore di up->getLy()
  int lyPred = up->getLy() * (1 << PADN) - 1;

  //  0 <= xL0 + k*deltaXL < up->getLx()*(1<<PADN)
  //            <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL < up->getLy()*(1<<PADN)
  //            <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //  calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up+(bordo destro/basso)
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();

  int count = std::max({palette->getStyleCount(), TPixelCM32::getMaxInk(),
                        TPixelCM32::getMaxPaint()});

  std::vector<TPixel32> paints(count, TPixel32::Red);
  std::vector<TPixel32> inks(count, TPixel32::Red);
  if (globalColorScale != TPixel::Black)
    for (int i  = 0; i < palette->getStyleCount(); i++)
      paints[i] = inks[i] = applyColorScaleCMapped(
          palette->getStyle(i)->getAverageColor(), globalColorScale);
  else
    for (int i  = 0; i < palette->getStyleCount(); i++)
      paints[i] = inks[i] =
          ::premultiply(palette->getStyle(i)->getAverageColor());

  dn->lock();
  up->lock();
  TPixelCM32 *upBasePix = up->pixels();
  TPixel32 *dnRow       = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata" del pixel corrente di up

  //  inizializza yL
  int yL = yL0 + (kMinY - 1) * deltaYL;

  //  scorre le scanline di boundingBoxD
  for (int kY = kMinY; kY <= kMaxY; kY++, dnRow += dnWrap) {
    //  inizializza xL
    int xL = xL0 + (kMinX - 1) * deltaXL;
    yL += deltaYL;

    //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e' approssimato
    //  con (xI, yI)
    int yI = yL >> PADN;  //  round

    TPixel32 *dnPix    = dnRow + xMin + kMinX;
    TPixel32 *dnEndPix = dnRow + xMin + kMaxX + 1;

    //  scorre i pixel sulla (yMin + kY)-esima scanline di dn
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //	round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixelCM32 *upPix = upBasePix + (yI * upWrap + xI);
      int t             = upPix->getTone();
      int p             = upPix->getPaint();
      assert(0 <= t && t < 256);
      assert(0 <= p && p < (int)paints.size());

      if (t == 0xff && p == 0)
        continue;
      else {
        int i = upPix->getInk();
        assert(0 <= i && i < (int)inks.size());
        TPixel32 colorUp;
        if (inksOnly) switch (t) {
          case 0:
            colorUp = inks[i];
            break;
          case 255:
            colorUp = TPixel::Transparent;
            break;
          default:
            colorUp = antialias(inks[i], 255 - t);
            break;
          }
        else
          switch (t) {
          case 0:
            colorUp = inks[i];
            break;
          case 255:
            colorUp = paints[p];
            break;
          default:
            colorUp = blend(inks[i], paints[p], t, TPixelCM32::getMaxTone());
            break;
          }

        if (colorUp.m == 255)
          *dnPix = colorUp;
        else if (colorUp.m != 0)
          *dnPix = quickOverPix(*dnPix, colorUp);
      }
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================
//=============================================================================
//=============================================================================

void doQuickResampleColorFilter(const TRaster32P &dn, const TRasterCM32P &up,
                                const TPaletteP &plt, const TAffine &aff,
                                UCHAR colorMask) {
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;
  const int PADN = 16;

  std::vector<TPixel32> paints(plt->getStyleCount());
  std::vector<TPixel32> inks(plt->getStyleCount());

  for (int i  = 0; i < plt->getStyleCount(); i++)
    paints[i] = inks[i] = ::premultiply(plt->getStyle(i)->getAverageColor());

  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));

  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);  //  clipping y su dn
  int yMax =
      std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);  //  clipping y su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);        //  clipping x su dn
  int xMax =
      std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);  //  clipping x su dn

  TAffine invAff = inv(aff);  //  inversa di aff

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred =
      up->getLx() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLx()
  int lyPred =
      up->getLy() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLy()

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow       = dn->pixels(yMin);
  TPixelCM32 *upBasePix = up->pixels();

  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    TPointD a = invAff * TPointD(xMin, y);
    int xL0   = tround((a.x + 0.5) * (1 << PADN));
    int yL0   = tround((a.y + 0.5) * (1 << PADN));
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn
    if (deltaXL == 0) {
      if ((xL0 < 0) || (lxPred < xL0)) continue;
    } else if (deltaXL > 0) {
      if (lxPred < xL0) continue;

      kMaxX              = (lxPred - xL0) / deltaXL;          //  floor
      if (xL0 < 0) kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    } else                                                    //  (deltaXL < 0)
    {
      if (xL0 < 0) continue;
      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0)
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
    if (deltaYL == 0) {
      if ((yL0 < 0) || (lyPred < yL0)) continue;
    } else if (deltaYL > 0) {
      if (lyPred < yL0) continue;

      kMaxY              = (lyPred - yL0) / deltaYL;          //  floor
      if (yL0 < 0) kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    } else                                                    //  (deltaYL < 0)
    {
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0)
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
    int kMin           = std::max({kMinX, kMinY, (int)0});
    int kMax           = std::min({kMaxX, kMaxY, xMax - xMin});
    TPixel32 *dnPix    = dnRow + xMin + kMin;
    TPixel32 *dnEndPix = dnRow + xMin + kMax + 1;
    int xL             = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL             = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      TPixelCM32 *upPix = upBasePix + (yI * upWrap + xI);
      int t             = upPix->getTone();
      int p             = upPix->getPaint();
      int i             = upPix->getInk();
      TPixel32 colorUp;
      switch (t) {
      case 0:
        colorUp = inks[i];
        break;
      case 255:
        colorUp = paints[p];
        break;
      default:
        colorUp = blend(inks[i], paints[p], t, TPixelCM32::getMaxTone());
        break;
      }

      if (colorMask == TRop::MChan)
        dnPix->r = dnPix->g = dnPix->b = colorUp.m;
      else {
        dnPix->r = ((colorMask & TRop::RChan) ? colorUp.r : 0);
        dnPix->g = ((colorMask & TRop::GChan) ? colorUp.g : 0);
        dnPix->b = ((colorMask & TRop::BChan) ? colorUp.b : 0);
      }
      dnPix->m = 255;
    }
  }
  dn->unlock();
  up->unlock();
}

//==========================================================

#endif  // TNZCORE_LIGHT

#ifdef OPTIMIZE_FOR_LP64
void doQuickResampleFilter_optimized(const TRaster32P &dn, const TRaster32P &up,
                                     const TAffine &aff) {
  //  se aff e' degenere la controimmagine di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));
  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)

  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate
  //  del pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;

  //  deltaXD "TLonghizzato" (round)
  int deltaXL = tround(deltaXD * (1 << PADN));

  //  deltaYD "TLonghizzato" (round)
  int deltaYL = tround(deltaYD * (1 << PADN));

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e' un
  //  segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  // naturale predecessore di up->getLx() - 1
  int lxPred = (up->getLx() - 2) * (1 << PADN);

  //  naturale predecessore di up->getLy() - 1
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *dnRow     = dn->pixels(yMin);
  TPixel32 *upBasePix = up->pixels();

  long c1;
  long c2;

  long c3;
  long c4;

  long c5;
  long c6;

  long s_rg;
  long s_br;
  long s_gb;

  UINT32 rColDownTmp;
  UINT32 gColDownTmp;
  UINT32 bColDownTmp;

  UINT32 rColUpTmp;
  UINT32 gColUpTmp;
  UINT32 bColUpTmp;

  unsigned char rCol;
  unsigned char gCol;
  unsigned char bCol;

  int xI;
  int yI;

  int xWeight1;
  int xWeight0;
  int yWeight1;
  int yWeight0;

  TPixel32 *upPix00;
  TPixel32 *upPix10;
  TPixel32 *upPix01;
  TPixel32 *upPix11;

  TPointD a;
  int xL0;
  int yL0;
  int kMinX;
  int kMaxX;
  int kMinY;
  int kMaxY;

  int kMin;
  int kMax;
  TPixel32 *dnPix;
  TPixel32 *dnEndPix;
  int xL;
  int yL;

  int y = yMin;
  ++yMax;
  //  scorre le scanline di boundingBoxD
  for (; y < yMax - 32; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST_X_32
  }
  for (; y < yMax - 16; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST_X_16
  }
  for (; y < yMax - 8; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST_X_8
  }
  for (; y < yMax - 4; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST_X_4
  }
  for (; y < yMax - 2; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST_X_2
  }
  for (; y < yMax; ++y, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_FIRST
  }
  dn->unlock();
  up->unlock();
}
#endif

//=============================================================================
//=============================================================================
//=============================================================================

#ifdef OPTIMIZE_FOR_LP64

void doQuickResampleFilter_optimized(const TRaster32P &dn, const TRaster32P &up,
                                     double sx, double sy, double tx,
                                     double ty) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((sx == 0) || (sy == 0)) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  maschera del filtro bilineare
  const int MASKN = (1 << PADN) - 1;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  // disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TAffine aff(sx, 0, tx, 0, sy, ty);
  TRectD boundingBoxD = TRectD(convert(dn->getSize())) *
                        (aff * TRectD(0, 0, up->getLx() - 2, up->getLy() - 2));

  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  //  clipping y su dn
  int yMin = std::max(tfloor(boundingBoxD.y0), 0);

  //  clipping y su dn
  int yMax = std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);

  //  clipping x su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);

  //  clipping x su dn
  int xMax = std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);

  //  inversa di aff
  TAffine invAff = inv(aff);

  //  nello scorrere le scanline di boundingBoxD, il passaggio alla scanline
  //  successiva comporta l'incremento (0, deltaYD) delle coordinate dei
  //  pixels corrispondenti di up

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, 0) delle coordinate del
  //  pixel corrispondente di up

  double deltaXD = invAff.a11;
  double deltaYD = invAff.a22;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e'
  //  un segmento (o un punto)
  if ((deltaXL == 0) || (deltaYL == 0)) return;

  //  (1)  equazione (kX, kY)-parametrica di boundingBoxD:
  //         (xMin, yMin) + kX*(1, 0) + kY*(0, 1),
  //           kX = 0, ..., (xMax - xMin),
  //           kY = 0, ..., (yMax - yMin)

  //  (2)  equazione (kX, kY)-parametrica dell'immagine mediante invAff di (1):
  //         invAff*(xMin, yMin) + kX*(deltaXD, 0) + kY*(0, deltaYD),
  //           kX = kMinX, ..., kMaxX
  //             con 0 <= kMinX <= kMaxX <= (xMax - xMin)
  //
  //           kY = kMinY, ..., kMaxY
  //             con 0 <= kMinY <= kMaxY <= (yMax - yMin)

  //  calcola kMinX, kMaxX, kMinY, kMaxY intersecando la (2) con i lati di up

  //  il segmento [a, b] di up (con gli estremi eventualmente invertiti) e'
  //  la controimmagine mediante aff della porzione di scanline
  //  [ (xMin, yMin), (xMax, yMin) ] di dn

  //  TPointD b = invAff*TPointD(xMax, yMin);
  TPointD a = invAff * TPointD(xMin, yMin);

  //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  0 <= xL0 + kX*deltaXL <= (up->getLx() - 2)*(1<<PADN),
  //  0 <= kMinX <= kX <= kMaxX <= (xMax - xMin)
  //  0 <= yL0 + kY*deltaYL <= (up->getLy() - 2)*(1<<PADN),
  //  0 <= kMinY <= kY <= kMaxY <= (yMax - yMin)

  int xL0 = tround(a.x * (1 << PADN));  //  xL0 inizializzato
  int yL0 = tround(a.y * (1 << PADN));  //  yL0 inizializzato

  //  calcola kMinY, kMaxY, kMinX, kMaxX intersecando la (2) con i lati di up
  int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
  int kMinY = 0, kMaxY = yMax - yMin;  //  clipping su dn

  //  TINT32 predecessore di (up->getLx() - 1)
  int lxPred = (up->getLx() - 2) * (1 << PADN);

  //  TINT32 predecessore di (up->getLy() - 1)
  int lyPred = (up->getLy() - 2) * (1 << PADN);

  //  0 <= xL0 + k*deltaXL <= (up->getLx() - 2)*(1<<PADN)
  //               <=>
  //  0 <= xL0 + k*deltaXL <= lxPred

  //  0 <= yL0 + k*deltaYL <= (up->getLy() - 2)*(1<<PADN)
  //               <=>
  //  0 <= yL0 + k*deltaYL <= lyPred

  //  calcola kMinY, kMaxY intersecando la (2) con i lati
  //  (y = yMin) e (y = yMax) di up
  if (deltaYL > 0)  //  (deltaYL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(yL0 <= lyPred);

    kMaxY = (lyPred - yL0) / deltaYL;  //  floor
    if (yL0 < 0) {
      kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
    }
  } else  //  (deltaYL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= yL0);

    kMaxY = yL0 / (-deltaYL);  //  floor
    if (lyPred < yL0) {
      kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
    }
  }
  //  calcola kMinY, kMaxY effettuando anche il clippind su dn
  kMinY = std::max(kMinY, (int)0);
  kMaxY = std::min(kMaxY, yMax - yMin);

  //  calcola kMinX, kMaxX intersecando la (2) con i lati
  //  (x = xMin) e (x = xMax) di up
  if (deltaXL > 0)  //  (deltaXL != 0)
  {
    //  [a, b] interno ad up contratto
    assert(xL0 <= lxPred);

    kMaxX = (lxPred - xL0) / deltaXL;  //  floor
    if (xL0 < 0) {
      kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
    }
  } else  //  (deltaXL < 0)
  {
    //  [a, b] interno ad up contratto
    assert(0 <= xL0);

    kMaxX = xL0 / (-deltaXL);  //  floor
    if (lxPred < xL0) {
      kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
    }
  }
  //  calcola kMinX, kMaxX effettuando anche il clippind su dn
  kMinX = std::max(kMinX, (int)0);
  kMaxX = std::min(kMaxX, xMax - xMin);

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  TPixel32 *upBasePix = up->pixels();
  TPixel32 *dnRow     = dn->pixels(yMin + kMinY);

  //  (xL, yL) sono le coordinate (inizializzate per il round)
  //  in versione "TLonghizzata"
  //  del pixel corrente di up
  int yL = yL0 + (kMinY - 1) * deltaYL;  //  inizializza yL

  long c1;
  long c2;

  long c3;
  long c4;

  long c5;
  long c6;

  long s_rg;
  long s_br;
  long s_gb;

  UINT32 rColDownTmp;
  UINT32 gColDownTmp;
  UINT32 bColDownTmp;

  UINT32 rColUpTmp;
  UINT32 gColUpTmp;
  UINT32 bColUpTmp;

  int xI;
  TPixel32 *upPix00;
  TPixel32 *upPix10;
  TPixel32 *upPix01;
  TPixel32 *upPix11;
  int xWeight1;
  int xWeight0;

  unsigned char rCol;
  unsigned char gCol;
  unsigned char bCol;

  int xL;
  int yI;
  int yWeight1;
  int yWeight0;
  TPixel32 *dnPix;
  TPixel32 *dnEndPix;
  int kY = kMinY;
  ++kMaxY;

  //  scorre le scanline di boundingBoxD
  for (; kY < kMaxY - 32; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND_X_32
  }
  for (; kY < kMaxY - 16; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND_X_16
  }
  for (; kY < kMaxY - 8; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND_X_8
  }
  for (; kY < kMaxY - 4; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND_X_4
  }
  for (; kY < kMaxY - 2; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND_X_2
  }
  for (; kY < kMaxY; kY++, dnRow += dnWrap) {
    EXTERNAL_LOOP_THE_SECOND
  }
  dn->unlock();
  up->unlock();
}
#endif

// namespace
};

#ifndef TNZCORE_LIGHT
//=============================================================================
//
// quickPut (paletted)
//
//=============================================================================

void TRop::quickPut(const TRasterP &dn, const TRasterCM32P &upCM32,
                    const TPaletteP &plt, const TAffine &aff,
                    const TPixel32 &globalColorScale, bool inksOnly) {
  TRaster32P dn32 = dn;
  if (dn32 && upCM32)
    if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
      doQuickPutCmapped(dn32, upCM32, plt, aff.a11, aff.a22, aff.a13, aff.a23,
                        globalColorScale, inksOnly);
    else
      doQuickPutCmapped(dn32, upCM32, plt, aff, globalColorScale, inksOnly);
  else
    throw TRopException("raster type mismatch");
}

//=============================================================================
//
// quickPut (paletted + transparency check + ink check + paint check)
//
//=============================================================================

void TRop::quickPut(const TRasterP &dn, const TRasterCM32P &upCM32,
                    const TPaletteP &plt, const TAffine &aff,
                    const CmappedQuickputSettings &settings)  // const TPixel32&
// globalColorScale, bool
// inksOnly, bool
// transparencyCheck, bool
// blackBgCheck, int inkIndex, int
// paintIndex)
{
  TRaster32P dn32 = dn;
  if (dn32 && upCM32)
    doQuickPutCmapped(dn32, upCM32, plt, aff,
                      settings);  // globalColorScale, inksOnly,
                                  // transparencyCheck, blackBgCheck, inkIndex,
                                  // paintIndex);
  else
    throw TRopException("raster type mismatch");
}

void TRop::quickResampleColorFilter(const TRasterP &dn, const TRasterP &up,
                                    const TAffine &aff, const TPaletteP &plt,
                                    UCHAR colorMask) {
  TRaster32P dn32     = dn;
  TRaster32P up32     = up;
  TRaster64P up64     = up;
  TRasterCM32P upCM32 = up;
  if (dn32 && up32)
    doQuickResampleColorFilter(dn32, up32, aff, colorMask);
  else if (dn32 && upCM32)
    doQuickResampleColorFilter(dn32, upCM32, plt, aff, colorMask);
  else if (dn32 && up64)
    doQuickResampleColorFilter(dn32, up64, aff, colorMask);
  // else if (dn32 && upCM32)
  //  doQuickResampleColorFilter(dn32, upCM32, aff, plt, colorMask);
  else
    throw TRopException("raster type mismatch");
}

#endif  // TNZCORE_LIGHT

//=============================================================================
//=============================================================================
//
// quickPut (Bilinear/Closest)
//
//=============================================================================

void quickPut(const TRasterP &dn, const TRasterP &up, const TAffine &aff,
              TRop::ResampleFilterType filterType, const TPixel32 &colorScale,
              bool doPremultiply, bool whiteTransp, bool firstColumn,
              bool doRasterDarkenBlendedView) {
  assert(filterType == TRop::Bilinear || filterType == TRop::ClosestPixel);

  bool bilinear = filterType == TRop::Bilinear;

  TRaster32P dn32 = dn;
  TRaster32P up32 = up;
  TRasterGR8P dn8 = dn;
  TRasterGR8P up8 = up;
  TRaster64P up64 = up;

  if (up8 && dn32) {
    assert(filterType == TRop::ClosestPixel);
    if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
      doQuickPutNoFilter(dn32, up8, aff.a11, aff.a22, aff.a13, aff.a23,
                         colorScale);
    else
      doQuickPutNoFilter(dn32, up8, aff, colorScale);
  } else if (dn32 && up32) {
    if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
      if (bilinear)
        doQuickPutFilter(dn32, up32, aff.a11, aff.a22, aff.a13, aff.a23);
      else
        doQuickPutNoFilter(dn32, up32, aff.a11, aff.a22, aff.a13, aff.a23,
                           colorScale, doPremultiply, whiteTransp, firstColumn,
                           doRasterDarkenBlendedView);
    else if (bilinear)
      doQuickPutFilter(dn32, up32, aff);
    else
      doQuickPutNoFilter(dn32, up32, aff, colorScale, doPremultiply,
                         whiteTransp, firstColumn, doRasterDarkenBlendedView);
  } else if (dn32 && up64)
    doQuickPutNoFilter(dn32, up64, aff, doPremultiply, firstColumn);
  else
    throw TRopException("raster type mismatch");
}

//=============================================================================
template <typename PIX>
void doQuickResampleNoFilter(const TRasterPT<PIX> &dn, const TRasterPT<PIX> &up,
                             const TAffine &aff) {
  //  se aff := TAffine(sx, 0, tx, 0, sy, ty) e' degenere la controimmagine
  //  di up e' un segmento (o un punto)
  if ((aff.a11 * aff.a22 - aff.a12 * aff.a21) == 0) return;

  //  contatore bit di shift
  const int PADN = 16;

  //  max dimensioni di up gestibili (limite imposto dal numero di bit
  //  disponibili per la parte intera di xL, yL)
  assert(std::max(up->getLx(), up->getLy()) <
         (1 << (8 * sizeof(int) - PADN - 1)));

  TRectD boundingBoxD =
      TRectD(convert(dn->getBounds())) *
      (aff * TRectD(-0.5, -0.5, up->getLx() - 0.5, up->getLy() - 0.5));
  //  clipping
  if (boundingBoxD.x0 >= boundingBoxD.x1 || boundingBoxD.y0 >= boundingBoxD.y1)
    return;

  int yMin = std::max(tfloor(boundingBoxD.y0), 0);  //  clipping y su dn
  int yMax =
      std::min(tceil(boundingBoxD.y1), dn->getLy() - 1);  //  clipping y su dn
  int xMin = std::max(tfloor(boundingBoxD.x0), 0);        //  clipping x su dn
  int xMax =
      std::min(tceil(boundingBoxD.x1), dn->getLx() - 1);  //  clipping x su dn

  TAffine invAff = inv(aff);  //  inversa di aff

  //  nel disegnare la y-esima scanline di dn, il passaggio al pixel
  //  successivo comporta l'incremento (deltaXD, deltaYD) delle coordinate
  //  del pixel corrispondente di up
  double deltaXD = invAff.a11;
  double deltaYD = invAff.a21;
  int deltaXL =
      tround(deltaXD * (1 << PADN));  //  deltaXD "TLonghizzato" (round)
  int deltaYL =
      tround(deltaYD * (1 << PADN));  //  deltaYD "TLonghizzato" (round)

  //  se aff "TLonghizzata" (round) e' degenere la controimmagine di up e'
  //  un segmento (o un punto)
  if ((deltaXL == 0) && (deltaYL == 0)) return;

  int lxPred =
      up->getLx() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLx()
  int lyPred =
      up->getLy() * (1 << PADN) - 1;  //  TINT32 predecessore di up->getLy()

  int dnWrap = dn->getWrap();
  int upWrap = up->getWrap();
  dn->lock();
  up->lock();

  PIX *dnRow     = dn->pixels(yMin);
  PIX *upBasePix = up->pixels();

  //  scorre le scanline di boundingBoxD
  for (int y = yMin; y <= yMax; y++, dnRow += dnWrap) {
    //  (1)  equazione k-parametrica della y-esima scanline di boundingBoxD:
    //         (xMin, y) + k*(1, 0),
    //           k = 0, ..., (xMax - xMin)

    //  (2)  equazione k-parametrica dell'immagine mediante invAff di (1):
    //         invAff*(xMin, y) + k*(deltaXD, deltaYD),
    //           k = kMin, ..., kMax
    //           con 0 <= kMin <= kMax <= (xMax - xMin)

    //  calcola kMin, kMax per la scanline corrente intersecando
    //  la (2) con i lati di up

    //  il segmento [a, b] di up e' la controimmagine mediante aff della
    //  porzione di scanline  [ (xMin, y), (xMax, y) ] di dn

    //  TPointD b = invAff*TPointD(xMax, y);
    TPointD a = invAff * TPointD(xMin, y);

    //  (xL0, yL0) sono le coordinate di a (inizializzate per il round)
    //  in versione "TLonghizzata"
    //  0 <= xL0 + k*deltaXL < up->getLx()*(1<<PADN),
    //  0 <= kMinX <= kMin <= k <= kMax <= kMaxX <= (xMax - xMin)

    //  0 <= yL0 + k*deltaYL < up->getLy()*(1<<PADN),
    //  0 <= kMinY <= kMin <= k <= kMax <= kMaxY <= (xMax - xMin)

    //  xL0 inizializzato per il round
    int xL0 = tround((a.x + 0.5) * (1 << PADN));

    //  yL0 inizializzato per il round
    int yL0 = tround((a.y + 0.5) * (1 << PADN));

    //  calcola kMinX, kMaxX, kMinY, kMaxY
    int kMinX = 0, kMaxX = xMax - xMin;  //  clipping su dn
    int kMinY = 0, kMaxY = xMax - xMin;  //  clipping su dn
    //  0 <= xL0 + k*deltaXL < up->getLx()*(1<<PADN)
    //               <=>
    //  0 <= xL0 + k*eltaXL <= lxPred

    //  0 <= yL0 + k*deltaYL < up->getLy()*(1<<PADN)
    //               <=>
    //  0 <= yL0 + k*deltaYL <= lyPred

    //  calcola kMinX, kMaxX
    if (deltaXL == 0) {
      //  [a, b] verticale esterno ad up+(bordo destro/basso)
      if ((xL0 < 0) || (lxPred < xL0)) continue;
      //  altrimenti usa solo
      //  kMinY, kMaxY ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaXL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lxPred < xL0) continue;

      kMaxX = (lxPred - xL0) / deltaXL;  //  floor
      if (xL0 < 0) {
        kMinX = ((-xL0) + deltaXL - 1) / deltaXL;  //  ceil
      }
    } else  //  (deltaXL < 0)
    {
      // [a, b] esterno ad up+(bordo destro/basso)
      if (xL0 < 0) continue;

      kMaxX = xL0 / (-deltaXL);  //  floor
      if (lxPred < xL0) {
        kMinX = (xL0 - lxPred - deltaXL - 1) / (-deltaXL);  //  ceil
      }
    }

    //  calcola kMinY, kMaxY
    if (deltaYL == 0) {
      //  [a, b] orizzontale esterno ad up+(bordo destro/basso)
      if ((yL0 < 0) || (lyPred < yL0)) continue;
      //  altrimenti usa solo
      //  kMinX, kMaxX ((deltaXL != 0) || (deltaYL != 0))
    } else if (deltaYL > 0) {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (lyPred < yL0) continue;

      kMaxY = (lyPred - yL0) / deltaYL;  //  floor
      if (yL0 < 0) {
        kMinY = ((-yL0) + deltaYL - 1) / deltaYL;  //  ceil
      }
    } else  //  (deltaYL < 0)
    {
      //  [a, b] esterno ad up+(bordo destro/basso)
      if (yL0 < 0) continue;

      kMaxY = yL0 / (-deltaYL);  //  floor
      if (lyPred < yL0) {
        kMinY = (yL0 - lyPred - deltaYL - 1) / (-deltaYL);  //  ceil
      }
    }

    //  calcola kMin, kMax effettuando anche il clippind su dn
    int kMin = std::max({kMinX, kMinY, (int)0});
    int kMax = std::min({kMaxX, kMaxY, xMax - xMin});

    PIX *dnPix    = dnRow + xMin + kMin;
    PIX *dnEndPix = dnRow + xMin + kMax + 1;

    //  (xL, yL) sono le coordinate (inizializzate per il round)
    //  in versione "TLonghizzata" del pixel corrente di up
    int xL = xL0 + (kMin - 1) * deltaXL;  //  inizializza xL
    int yL = yL0 + (kMin - 1) * deltaYL;  //  inizializza yL

    //  scorre i pixel sulla y-esima scanline di boundingBoxD
    for (; dnPix < dnEndPix; ++dnPix) {
      xL += deltaXL;
      yL += deltaYL;
      //  il punto di up TPointD(xL/(1<<PADN), yL/(1<<PADN)) e'
      //  approssimato con (xI, yI)
      int xI = xL >> PADN;  //  round
      int yI = yL >> PADN;  //  round

      assert((0 <= xI) && (xI <= up->getLx() - 1) && (0 <= yI) &&
             (yI <= up->getLy() - 1));

      *dnPix = *(upBasePix + (yI * upWrap + xI));
    }
  }
  dn->unlock();
  up->unlock();
}

//=============================================================================

#ifdef OPTIMIZE_FOR_LP64

void quickResample_optimized(const TRasterP &dn, const TRasterP &up,
                             const TAffine &aff,
                             TRop::ResampleFilterType filterType) {
  assert(filterType == TRop::Bilinear || filterType == TRop::ClosestPixel);

  bool bilinear = filterType == TRop::Bilinear;

  TRaster32P dn32 = dn;
  TRaster32P up32 = up;
  TRasterGR8P dn8 = dn;
  TRasterGR8P up8 = up;
  if (dn32 && up32)
    if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
      if (bilinear)
        doQuickResampleFilter_optimized(dn32, up32, aff.a11, aff.a22, aff.a13,
                                        aff.a23);
      else
        doQuickResampleNoFilter(dn32, up32, aff.a11, aff.a22, aff.a13, aff.a23);
    else if (bilinear)
      doQuickResampleFilter_optimized(dn32, up32, aff);
    else
      doQuickResampleNoFilter(dn32, up32, aff);
  else
    throw TRopException("raster type mismatch");
}

#endif

//=============================================================================

void quickResample(const TRasterP &dn, const TRasterP &up, const TAffine &aff,
                   TRop::ResampleFilterType filterType) {
#ifdef OPTIMIZE_FOR_LP64

  quickResample_optimized(dn, up, aff, filterType);

#else

  assert(filterType == TRop::Bilinear || filterType == TRop::ClosestPixel);

  bool bilinear = filterType == TRop::Bilinear;

  TRaster32P dn32     = dn;
  TRaster32P up32     = up;
  TRasterCM32P dnCM32 = dn;
  TRasterCM32P upCM32 = up;
  TRasterGR8P dn8     = dn;
  TRasterGR8P up8     = up;
  dn->clear();

  if (bilinear) {
    if (dn32 && up32) {
      if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
        doQuickResampleFilter(dn32, up32, aff.a11, aff.a22, aff.a13, aff.a23);
      else
        doQuickResampleFilter(dn32, up32, aff);
    } else if (dn32 && up8) {
      if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
        doQuickResampleFilter(dn32, up8, aff.a11, aff.a22, aff.a13, aff.a23);
      else
        doQuickResampleFilter(dn32, up8, aff);
    } else
      throw TRopException("raster type mismatch");
  } else {
    if (dn32 && up32) {
      if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
        doQuickResampleNoFilter(dn32, up32, aff.a11, aff.a22, aff.a13, aff.a23);
      else
        doQuickResampleNoFilter(dn32, up32, aff);

    } else if (dnCM32 && upCM32) {
      if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
        doQuickResampleNoFilter(dnCM32, upCM32, aff.a11, aff.a22, aff.a13,
                                aff.a23);
      else
        doQuickResampleNoFilter(dnCM32, upCM32, aff);
    } else if (dn8 && up8) {
      if (areAlmostEqual(aff.a12, 0) && areAlmostEqual(aff.a21, 0))
        doQuickResampleNoFilter(dn8, up8, aff.a11, aff.a22, aff.a13, aff.a23);
      else
        doQuickResampleNoFilter(dn8, up8, aff);
    } else
      throw TRopException("raster type mismatch");
  }

#endif
}

void quickPut(const TRasterP &out, const TRasterCM32P &up, const TAffine &aff,
              const TPixel32 &inkCol, const TPixel32 &paintCol);