Blob Blame Raw


#include "tsystem.h"

#include <set>

#include "tropcm.h"

#include "tcolorstyles.h"
#include "tpixelutils.h"
#include "ttile.h"
#include "tpalette.h"
#include "timage_io.h"
#include "trasterimage.h"

//#include "tlevel.h"
//#include "ttoonzimage.h"
//#include "tgeometry.h"
//#include "timage_io.h"

extern "C" {
#include "toonz4.6/raster.h"
}

#ifdef _WIN32
#define USE_SSE2
#endif

#ifdef USE_SSE2
#include <emmintrin.h>

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

namespace {

__declspec(align(16)) class TPixelFloat {
public:
  TPixelFloat() : b(0), g(0), r(0), m(0) {}

  TPixelFloat(float rr, float gg, float bb, float mm)
      : b(bb), g(gg), r(rr), m(mm) {}

  TPixelFloat(const TPixel32 &pix) : b(pix.b), g(pix.g), r(pix.r), m(pix.m) {}

  float b, g, r, m;
};

}  // anonymous namespace

#endif

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

bool renderRas32(const TTile &tileOut, const TTile &tileIn,
                 const TPaletteP palette);

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

const TPixel32 c_transparencyCheckPaint = TPixel32(80, 80, 80, 255);
const TPixel32 c_transparencyCheckInk   = TPixel32::Black;

void TRop::convert(const TRaster32P &rasOut, const TRasterCM32P &rasIn,
                   const TPaletteP palette, bool transparencyCheck) {
  int count = palette->getStyleCount();
  int count2 =
      std::max({count, TPixelCM32::getMaxInk(), TPixelCM32::getMaxPaint()});

  // per poter utilizzare lo switch (piu' efficiente) si utilizza 255
  // anziche' TPixelCM32::getMaxTone()
  assert(TPixelCM32::getMaxTone() == 255);

  int rasLx = rasOut->getLx();
  int rasLy = rasOut->getLy();

  rasOut->lock();
  rasIn->lock();
#ifdef _WIN32
  if (TSystem::getCPUExtensions() & TSystem::CpuSupportsSse2) {
    __m128i zeros = _mm_setzero_si128();
    TPixelFloat *paints =
        (TPixelFloat *)_aligned_malloc(count2 * sizeof(TPixelFloat), 16);
    TPixelFloat *inks =
        (TPixelFloat *)_aligned_malloc(count2 * sizeof(TPixelFloat), 16);

    std::vector<TPixel32> paints2(count2);
    std::vector<TPixel32> inks2(count2);
    if (transparencyCheck) {
      for (int i = 0; i < palette->getStyleCount(); i++) {
        paints2[i] = c_transparencyCheckPaint;
        inks2[i]   = c_transparencyCheckInk;
        paints[i]  = TPixelFloat(paints2[i]);
        inks[i]    = TPixelFloat(inks2[i]);
      }
      paints2[0] = TPixel32::Transparent;
      paints[0]  = TPixelFloat(TPixel32::Transparent);
    }
    /*
else if (true)
{
            for(int i=0;i<palette->getStyleCount();i++)
{
paints2[i] = c_transparencyCheckPaint;
                    inks2[i]   = c_transparencyCheckInk;
paints[i]  = TPixelFloat(paints2[i]);
                    inks[i]    = TPixelFloat(inks2[i]);
            paints2[i] = TPixel32::Transparent;
            paints[i] = TPixelFloat(TPixel32::Transparent);
}
            paints2[0] = TPixel32::Transparent;
            paints[0] = TPixelFloat(TPixel32::Transparent);
}
*/
    else
      for (int i = 0; i < palette->getStyleCount(); i++) {
        TPixel32 color = ::premultiply(palette->getStyle(i)->getAverageColor());
        paints[i] = inks[i] = TPixelFloat(color);
        paints2[i] = inks2[i] = color;
      }

    float maxTone     = (float)TPixelCM32::getMaxTone();
    __m128 den_packed = _mm_load1_ps(&maxTone);

    for (int y = 0; y < rasLy; ++y) {
      TPixel32 *pix32   = rasOut->pixels(y);
      TPixelCM32 *pixIn = rasIn->pixels(y);

      TPixelCM32 *endPixIn = pixIn + rasLx;

      while (pixIn < endPixIn) {
        int tt = pixIn->getTone();
        int p  = pixIn->getPaint();
        int i  = pixIn->getInk();
        switch (tt) {
        case 255:
          *pix32++ = paints2[p];
          break;
        case 0:
          *pix32++ = inks2[i];
          break;
        default: {
          float t         = (float)tt;
          __m128 a_packed = _mm_load_ps((float *)&(inks[i]));
          __m128 b_packed = _mm_load_ps((float *)&(paints[p]));

          __m128 num_packed  = _mm_load1_ps(&t);
          __m128 diff_packed = _mm_sub_ps(den_packed, num_packed);

          // calcola in modo vettoriale out = ((den-num)*a + num*b)/den
          __m128 outPix_packed = _mm_mul_ps(diff_packed, a_packed);
          __m128 tmpPix_packed = _mm_mul_ps(num_packed, b_packed);

          outPix_packed = _mm_add_ps(outPix_packed, tmpPix_packed);
          outPix_packed = _mm_div_ps(outPix_packed, den_packed);

          // converte i canali da float a char
          __m128i outPix_packed_i = _mm_cvtps_epi32(outPix_packed);
          outPix_packed_i         = _mm_packs_epi32(outPix_packed_i, zeros);
          outPix_packed_i         = _mm_packus_epi16(outPix_packed_i, zeros);

          *(DWORD *)(pix32) = _mm_cvtsi128_si32(outPix_packed_i);
          ++pix32;
        }
        }
        ++pixIn;
      }
    }

    _aligned_free(paints);
    _aligned_free(inks);

  } else  // SSE2 not supported
#endif    // _WIN32
  {

    std::vector<TPixel32> paints(count2, TPixel32(255, 0, 0));
    std::vector<TPixel32> inks(count2, TPixel32(255, 0, 0));
    if (transparencyCheck) {
      for (int i = 0; i < palette->getStyleCount(); i++) {
        paints[i] = c_transparencyCheckPaint;
        inks[i]   = c_transparencyCheckInk;
      }
      paints[0] = TPixel32::Transparent;
    } else
      for (int i  = 0; i < palette->getStyleCount(); i++)
        paints[i] = inks[i] =
            ::premultiply(palette->getStyle(i)->getAverageColor());

    for (int y = 0; y < rasLy; ++y) {
      TPixel32 *pix32      = rasOut->pixels(y);
      TPixelCM32 *pixIn    = rasIn->pixels(y);
      TPixelCM32 *endPixIn = pixIn + rasLx;

      while (pixIn < endPixIn) {
        int t = pixIn->getTone();
        int p = pixIn->getPaint();
        int i = pixIn->getInk();

        if (t == TPixelCM32::getMaxTone())
          *pix32++ = paints[p];
        else if (t == 0)
          *pix32++ = inks[i];
        else
          *pix32++ = blend(inks[i], paints[p], t, TPixelCM32::getMaxTone());

        ++pixIn;
      }
    }
  }
  rasOut->unlock();
  rasIn->unlock();
}

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

static void do_convert(const TTile &dst, const TTile &src,
                       const TPaletteP palette, bool transparencyCheck,
                       bool applyFx) {
  // assert(palette);
  // assert(_rasOut && _rasIn);
  // assert(rasOut->getSize() == rasIn->getSize());

  if (applyFx && renderRas32(dst, src, palette)) return;

  TRaster32P rasOut;
  TRasterCM32P rasIn;

  if (dst.getRaster()->getSize() != src.getRaster()->getSize()) {
    TRect rect = TRect(convert(dst.m_pos), dst.getRaster()->getSize()) *
                 TRect(convert(src.m_pos), src.getRaster()->getSize());
    TRect rectOut = rect - convert(dst.m_pos);
    rasOut        = dst.getRaster()->extract(rectOut);
    TRect rectIn  = rect - convert(src.m_pos);
    rasIn         = src.getRaster()->extract(rectIn);
  } else {
    rasOut = dst.getRaster();
    rasIn  = src.getRaster();
  }
  TRop::convert(rasOut, rasIn, palette, transparencyCheck);
}

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

void TRop::convert(
    const TRaster32P &rasOut, const TRasterCM32P &rasIn, TPaletteP palette,
    const TRect &theClipRect,  // il rect su cui e' applicata la conversione
    bool transparencyCheck, bool applyFx) {
  assert(palette);
  assert(rasIn && rasOut);

  TRect clipRect(theClipRect);
  if (clipRect.isEmpty())
    clipRect = rasIn->getBounds();
  else {
    if (!clipRect.overlaps(rasIn->getBounds())) return;
    clipRect = clipRect * rasIn->getBounds();
  }
  if (clipRect.isEmpty()) return;

  TRect clipRectIn, clipRectOut;

  if (applyFx && palette->getFxRects(clipRect, clipRectIn, clipRectOut)) {
    TRect rAux = clipRectIn;
    TRasterP rAuxIn =
        rasIn->extract(clipRectIn);  // la extract modifica clipRectIn
    if (rAux != clipRectIn && rAux != rasIn->getBounds()) {
      TRasterCM32P rNew(rAux.getSize());
      TRect tmpRect = clipRectIn - rAux.getP00();
      rNew->extract(tmpRect)->copy(rAuxIn);
      rAuxIn     = rNew;
      clipRectIn = rAux;
    }

    TTile tileIn(rAuxIn, ::convert(clipRectIn.getP00()));

    rAux = clipRectOut;
    TRasterP rAuxOut =
        rasOut->extract(clipRectOut);  // la extract modifica clipRectOut
    TTile tileOut(rAuxOut, ::convert(clipRectOut.getP00()));
    TRop::convert(tileOut, tileIn, palette, transparencyCheck, true);
  } else {
    TRect clipRectIn  = clipRect;
    TRect clipRectOut = clipRect;
    TRasterP _rasOut  = rasOut->extract(clipRectOut);
    TRasterP _rasIn   = rasIn->extract(clipRectIn);

    TTile t1(_rasOut, ::convert(clipRectOut.getP00()));
    TTile t2(_rasIn, ::convert(clipRectIn.getP00()));

    TRop::convert(t1, t2, palette, transparencyCheck, false);
  }
}

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

void TRop::convert(const TTile &dst, const TTile &src, const TPaletteP plt,
                   bool transparencyCheck, bool applyFxs) {
  // if (dst->getSize() != src->getSize())
  //  throw TRopException("convert: size mismatch");

  // assert(plt);
  if ((TRaster32P)dst.getRaster())
    do_convert(dst, src, plt, transparencyCheck, applyFxs);
  else if ((TRaster64P)dst.getRaster()) {
    TRaster32P aux(dst.getRaster()->getLx(), dst.getRaster()->getLy());
    TTile taux(aux, dst.m_pos);
    do_convert(taux, src, plt, transparencyCheck, applyFxs);
    TRop::convert(dst.getRaster(), aux);
  } else
    throw TRopException("unsupported pixel type");
}

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

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

namespace {

TRasterP putSinglePaintInRaster(TRasterCM32P &rasIn, int paintId,
                                TPixel32 color) {
  TRaster32P rasOut;

  for (int y = 0; y < rasIn->getLy(); y++) {
    TPixelCM32 *pixIn  = rasIn->pixels(y);
    TPixelCM32 *endPix = pixIn + rasIn->getLx();
    TPixel32 *pixOut   = 0;

    if (rasOut) pixOut = rasOut->pixels(y);

    while (pixIn < endPix) {
      if (pixIn->getPaint() == paintId) {
        if (!rasOut) {
          rasOut = TRaster32P(rasIn->getLx(), rasIn->getLy());
          rasOut->fill(TPixel32::Transparent);
          pixOut = rasOut->pixels(y) + (pixIn - rasIn->pixels(y));
        }

        if (!pixIn->isPureInk()) *pixOut = color;
        /*else if (!pixIn->isPureInk())
  {
        assert(TPixelCM32::getMaxTone()==0xff);
        pixOut->m = pixIn->getTone();
        pixOut->r = 255*color.r/pixOut->m;
        pixOut->g = 255*color.g/pixOut->m;
        pixOut->b = 255*color.b/pixOut->m;
}*/
        pixIn->setPaint(0);
        // pixIn->setTone(0);
      }
      pixIn++;
      pixOut++;
    }
  }
  return rasOut;
}

/*------------------------------------------------------------------------*/

TRasterP putSingleInkInRasterGR8(TRasterCM32P &rasIn, int inkId) {
  TRasterGR8P rasOut;

  for (int y = 0; y < rasIn->getLy(); y++) {
    TPixelCM32 *pixIn  = rasIn->pixels(y);
    TPixelCM32 *endPix = pixIn + rasIn->getLx();
    TPixelGR8 *pixOut;

    if (rasOut) pixOut = rasOut->pixels(y);

    while (pixIn < endPix) {
      if (pixIn->getInk() == inkId) {
        assert(TPixelCM32::getMaxTone() == 0xff);
        if (!rasOut) {
          rasOut = TRasterGR8P(rasIn->getLx(), rasIn->getLy());
          rasOut->fill(0);
          pixOut = rasOut->pixels(y) + (pixIn - rasIn->pixels(y));
        }

        *pixOut = 255 - pixIn->getTone();
        // pixIn->setInk(0);
        pixIn->setTone(TPixelCM32::getMaxTone());
      }

      pixIn++;
      pixOut++;
    }
  }
  return rasOut;
}

/*------------------------------------------------------------------------*/
TRasterP putSingleInkInRasterRGBM(TRasterCM32P &rasIn, int inkId) {
  TRaster32P rasOut;

  for (int y = 0; y < rasIn->getLy(); y++) {
    TPixelCM32 *pixIn  = rasIn->pixels(y);
    TPixelCM32 *endPix = pixIn + rasIn->getLx();
    TPixel *pixOut;

    if (rasOut) pixOut = rasOut->pixels(y);

    while (pixIn < endPix) {
      if (pixIn->getInk() == inkId) {
        assert(TPixelCM32::getMaxTone() == 0xff);
        if (!rasOut) {
          rasOut = TRaster32P(rasIn->getLx(), rasIn->getLy());
          rasOut->fill(TPixel32::Transparent);
          pixOut = rasOut->pixels(y) + (pixIn - rasIn->pixels(y));
        }

        pixOut->r = pixOut->g = pixOut->b = pixOut->m = 255 - pixIn->getTone();
        // pixIn->setInk(0);
        pixIn->setTone(TPixelCM32::getMaxTone());
      }
      pixIn++;
      if (rasOut) pixOut++;
    }
  }
  return rasOut;
}

/*------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/

// filename!!
// interactive!!

bool computePaletteFx(const std::vector<std::pair<TColorStyle *, int>> &fx,
                      const TTile &tileOut, const TTile &tileIn,
                      const TPaletteP plt) {
  int i;
  TRasterCM32P rasIn = tileIn.getRaster();
  TRaster32P rAux32, rasOut = tileOut.getRaster();
  TRasterGR8P rAuxGR;

  int frame = plt->getFrame();

  std::vector<TRasterP> paintLayers(fx.size());
  std::vector<TRasterP> inkLayers(fx.size());

  // tolgo dal raster d'ingresso gli ink e i paint con gli effetti, mettendoli
  // in dei raster layer
  for (i = 0; i < (int)fx.size(); i++) {
    TRasterStyleFx *rfx = fx[i].first->getRasterStyleFx();

    if (rfx->isPaintStyle())
      paintLayers[i] = putSinglePaintInRaster(rasIn, fx[i].second,
                                              fx[i].first->getMainColor());
    if (rfx->isInkStyle()) {
      if (rfx->inkFxNeedRGBMRaster())
        inkLayers[i] = putSingleInkInRasterRGBM(rasIn, fx[i].second);
      else
        inkLayers[i] = putSingleInkInRasterGR8(rasIn, fx[i].second);
    }
  }

  // raster d'ingresso senza i colori fx, viene renderizzato nel raster d'uscita

  do_convert(tileOut, tileIn, plt, false, false);

  // ogni layer viene "effettato".; se il risultato e' non nullo, viene
  // sovrapposto sul raster d'uscita
  // prima vengono messi tutti i layer di paint, poi quelli di ink

  TRect rectOut =
      TRect(convert(tileOut.m_pos), tileOut.getRaster()->getSize()) -
      convert(tileIn.m_pos);

  for (i = 0; i < (int)fx.size(); i++)
    if (paintLayers[i]) {
      TRasterStyleFx::Params params(paintLayers[i], convert(tileIn.m_pos),
                                    rasIn, fx[i].second, frame);
      if (fx[i].first->getRasterStyleFx()->compute(params))
        TRop::over(rasOut, paintLayers[i]->extract(rectOut), rasOut);
    }

  for (i = 0; i < (int)fx.size(); i++)
    if (inkLayers[i]) {
      TRasterStyleFx *rfx = fx[i].first->getRasterStyleFx();

      TRasterStyleFx::Params params(inkLayers[i], convert(tileIn.m_pos), rasIn,
                                    fx[i].second, frame);
      if (rfx->compute(params)) {
        if (rfx->inkFxNeedRGBMRaster())
          TRop::over(rasOut, rasOut, inkLayers[i]->extract(rectOut));
        else
          TRop::over(rasOut, inkLayers[i]->extract(rectOut),
                     fx[i].first->getMainColor());
      }
    }

  return true;
}

/*------------------------------------------------------------------------*/

}  // namespace

/*------------------------------------------------------------------------*/

bool renderRas32(const TTile &tileOut, const TTile &tileIn,
                 const TPaletteP palette) {
  assert(TRect(convert(tileIn.m_pos), tileIn.getRaster()->getSize())
             .contains(TRect(convert(tileOut.m_pos),
                             tileOut.getRaster()->getSize())));
  assert((TRasterCM32P)tileIn.getRaster());

  // Shrink = shrink;
  // INIT_TCM(rin)

  /* mark up are made apart */
  // computeMarkup(rasIn, palette);

  std::vector<std::pair<TColorStyle *, int>> fxArray;

  for (int i = 0; i < palette->getStyleCount(); i++)
    if (palette->getStyle(i)->isRasterStyle())
      fxArray.push_back(std::pair<TColorStyle *, int>(palette->getStyle(i), i));

  if (fxArray.empty()) return false;

  TTile _tileIn(tileIn.getRaster()->clone(), tileIn.m_pos);

  tileIn.getRaster()->lock();
  tileOut.getRaster()->lock();
  computePaletteFx(fxArray, tileOut, _tileIn, palette);
  tileIn.getRaster()->unlock();
  tileOut.getRaster()->unlock();

  return true;
}

/*------------------------------------------------------------------------*/

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

namespace {

void addColor(TPaletteP plt, int index, const TPaletteP &upPlt,
              std::map<int, int> &usedInks) {
  TColorStyle *cs = upPlt->getStyle(index);
  if (cs && cs->getMainColor() == plt->getStyle(index)->getMainColor()) {
    usedInks[index] = index;
    return;
  }
  int firstStyleId                     = plt->getFirstUnpagedStyle();
  if (firstStyleId == -1) firstStyleId = plt->getStyleCount();
  usedInks[index]                      = firstStyleId;
  plt->getPage(0)->addStyle(TPixel32::Red);
}

void addColor(TPaletteP plt, int index, std::map<int, int> &usedInks) {
  int firstStyleId                     = plt->getFirstUnpagedStyle();
  if (firstStyleId == -1) firstStyleId = plt->getStyleCount();
  usedInks[index]                      = firstStyleId;
  plt->getPage(0)->addStyle(TPixel32::Red);
}

//------------------------------------------------------------
// std::map<int,int>& usedInk  upInkId -> downInkId
void doMergeCmapped(TRasterCM32P rasOut, const TRasterCM32P &rasUp,
                    const TPaletteP &pltOut, bool onlyInks,
                    int matchlinePrevalence, std::map<int, int> &usedInks) {
  double val = matchlinePrevalence / 100.0;  // matchlinePrevalence ==0 always
                                             // ink down; matchlinePrevalence ==
                                             // 100 always ink up;
  assert(rasOut && rasUp);
  assert(rasOut->getSize() == rasUp->getSize());

  TPaletteP downPlt = pltOut->clone();
  std::map<int, int>::iterator it;
  for (it = usedInks.begin(); it != usedInks.end(); it++)
    downPlt->getPage(0)->addStyle(TPixel32::Red);
  for (int y = 0; y < rasOut->getLy(); y++) {
    TPixelCM32 *pixDown = rasOut->pixels(y),
               *endPix  = pixDown + rasOut->getLx();
    TPixelCM32 *pixUp   = rasUp->pixels(y);

    while (pixDown < endPix) {
      // there is lines in matchline
      if (!pixUp->isPurePaint()) {
        int toneDown = pixDown->getTone();
        int toneUp   = pixUp->getTone();
        if (usedInks.find(pixUp->getInk()) == usedInks.end())
          addColor(downPlt, pixUp->getInk(), usedInks);

        if (val == 1) {  // Matchline is on top, with gap
          pixDown->setTone(toneUp);
          pixDown->setInk(usedInks[pixUp->getInk()]);
        } else if (val == 0) {  // Matchline is on bottom, with gap
          if (pixDown->isPurePaint()) {
            pixDown->setTone(toneUp);
            pixDown->setInk(usedInks[pixUp->getInk()]);
          } else {
            //*pixOut = *pixDown;
          }
        } else {
          if ((val > 0 && toneUp < toneDown) ||
              (val == 0 && toneDown == 255))  //(toneUp<toneDown)
            pixDown->setTone(toneUp);

          if ((255 - toneDown) * (1 - val) <=
              val * (255 - toneUp - 1))  // val==0 -> if (toneDown== 255)....
            // val==0.5 -> if (toneup<toneDown)...
            // val==1 -> if (toneup<255)...
            pixDown->setInk(usedInks[pixUp->getInk()]);
        }
      }
      int paintIndex;
      if (!onlyInks && (paintIndex = pixUp->getPaint()) > 0) {
        if (usedInks.find(paintIndex) == usedInks.end())
          addColor(downPlt, paintIndex, usedInks);
        pixDown->setPaint(usedInks[paintIndex]);
      }
      pixUp++;
      pixDown++;
      // pixOut++;
    }
  }
}
/*------------------------------------------------------------------------*/

void addColors(const TPixelCM32 &color, TPaletteP plt, const TPaletteP &upPlt,
               std::map<int, int> &usedColors) {
  if (usedColors.find(color.getInk()) == usedColors.end())
    addColor(plt, color.getInk(), upPlt, usedColors);
  if (usedColors.find(color.getPaint()) == usedColors.end())
    addColor(plt, color.getPaint(), upPlt, usedColors);
}

//---------------------------------------------------------------------------
void addColors(const TPixelCM32 &color, TPaletteP plt,
               std::map<int, int> &usedColors) {
  if (usedColors.find(color.getInk()) == usedColors.end())
    addColor(plt, color.getInk(), usedColors);
  if (usedColors.find(color.getPaint()) == usedColors.end())
    addColor(plt, color.getPaint(), usedColors);
}

/*------------------------------------------------------------------------*/

void doApplyMatchLines(TRasterCM32P rasOut, const TRasterCM32P &rasUp,
                       int inkIndex, int matchlinePrevalence) {
  double val = matchlinePrevalence / 100.0;  // matchlinePrevalence ==0->always
                                             // ink down; matchlinePrevalence ==
                                             // 100 always ink up;

  assert(rasOut && rasUp);
  assert(rasOut->getSize() == rasUp->getSize());

  for (int y = 0; y < rasOut->getLy(); y++) {
    TPixelCM32 *pixDown = rasOut->pixels(y),
               *endPix  = pixDown + rasOut->getLx();
    TPixelCM32 *pixUp   = rasUp->pixels(y);

    while (pixDown < endPix) {
      if (!pixUp->isPurePaint()) {
        int toneDown = pixDown->getTone();
        int toneUp   = pixUp->getTone();
        if (val == 1) {  // Matchline is on top, with gap
          pixDown->setTone(toneUp);
          pixDown->setInk(inkIndex);
        } else if (val == 0) {  // Matchline is on bottom, with gap
          if (pixDown->isPurePaint()) {
            pixDown->setTone(toneUp);
            pixDown->setInk(inkIndex);
          } else {
          }
          //*pixOut = *pixDown;
        }
        if ((val > 0 && toneUp < toneDown) || (val == 0 && toneDown == 255))
          pixDown->setTone(toneUp);

        if ((255 - toneDown) * (1 - val) <=
            val * (255 - toneUp - 1))  // val==0 -> if (toneDown == 255)....
          // val==0.5 -> if (toneup<toneDown)...
          // val==1 -> if (toneup<255)...
          pixDown->setInk(inkIndex);
      }

      pixUp++;
      pixDown++;
    }
  }
}
/*------------------------------------------------------------------------*/

}  // namespace

#ifdef LEVO

void TRop::eraseColors(TRasterCM32P ras, vector<int> &colorIds, bool eraseInks,
                       bool keepColor) {
  assert(ras);

  vector<int> curColorIds;
  std::sort(colorIds.begin(), colorIds.end());

  if (!keepColor)
    curColorIds = colorIds;
  else {
    // prendo tutti i colori ECCETTO quelli nel vettore colorIds
    unsigned int count1 = 0, count2 = 0;
    int size = eraseInks ? TPixelCM32::getMaxInk() : TPixelCM32::getMaxPaint();

    curColorIds.resize(size + 1 - colorIds.size());
    for (int i = 0; i < size; i++)
      if (count1 < colorIds.size() && colorIds[count1] == i)
        count1++;
      else
        curColorIds[count2++] = i;
  }

  for (int y = 0; y < ras->getLy(); y++) {
    TPixelCM32 *pix = ras->pixels(y), *endPix = pix + ras->getLx();

    while (pix < endPix) {
      unsigned int i;
      int color = eraseInks ? pix->getInk() : pix->getPaint();

      if (color != 0)
        for (i = 0; i < curColorIds.size() && curColorIds[i] <= color; i++)
          if (color == curColorIds[i]) {
            *pix = eraseInks ? TPixelCM32(0, pix->getPaint(),
                                          TPixelCM32::getMaxTone())
                             : TPixelCM32(pix->getInk(), 0, pix->getTone());
            break;
          }
      pix++;
    }
  }
}

#endif

/*------------------------------------------------------------------------*/

void TRop::eraseColors(TRasterCM32P ras, std::vector<int> *colorIds,
                       bool eraseInks) {
  if (colorIds) std::sort(colorIds->begin(), colorIds->end());

  for (int y = 0; y < ras->getLy(); y++) {
    TPixelCM32 *pix = ras->pixels(y), *endPix = pix + ras->getLx();

    for (; pix < endPix; pix++) {
      unsigned int i = 0;
      int color      = eraseInks ? pix->getInk() : pix->getPaint();

      if (color == 0) continue;

      if (colorIds) {
        while (i < colorIds->size() && (*colorIds)[i] < color) i++;
        if (i == colorIds->size() || color != (*colorIds)[i]) continue;
      }

      if (eraseInks) {
        pix->setInk(0);
        pix->setTone(TPixelCM32::getMaxTone());
      } else
        pix->setPaint(0);
    }
  }
}

//--------------------------------
/*
void TRop::overaCmapped(TRasterCM32P rasOut, const TRasterCM32P& rasUp, const
TPaletteP &pltOut, int matchlinePrevalence, std::map<int,int>& usedColors)
{
doMergeCmapped(rasOut, rasUp, pltOut, false, matchlinePrevalence, usedColors);
}
*/
//-----------------------------------------------------------------

void TRop::applyMatchLines(TRasterCM32P rasOut, const TRasterCM32P &rasUp,
                           const TPaletteP &pltOut, int inkIndex,
                           int matchlinePrevalence,
                           std::map<int, int> &usedInks) {
  assert(matchlinePrevalence >= 0);
  if (inkIndex == -1)
    doMergeCmapped(rasOut, rasUp, pltOut, true, matchlinePrevalence, usedInks);
  else
    doApplyMatchLines(rasOut, rasUp, inkIndex, matchlinePrevalence);
}

/*------------------------------------------------------------------------*/

void TRop::eraseStyleIds(TToonzImage *image, const std::vector<int> styleIds) {
  assert(image);
  TRasterCM32P ras = image->getRaster();

  int i;
  for (i = 0; i < (int)styleIds.size(); i++) {
    int styleId = styleIds[i];
    ras->lock();
    for (int y = 0; y < ras->getLy(); y++) {
      TPixelCM32 *pix = ras->pixels(y), *endPix = pix + ras->getLx();
      while (pix < endPix) {
        bool isPaint = (pix->getPaint() == styleId);
        bool isInk   = (pix->getInk() == styleId);
        if (!isPaint && !isInk) {
          pix++;
          continue;
        } else if (isPaint && !isInk)
          *pix = TPixelCM32(pix->getInk(), 0, pix->getTone());
        else if (!isPaint && isInk)
          *pix = TPixelCM32(0, pix->getPaint(), TPixelCM32::getMaxTone());
        else if (isPaint && isInk)
          *pix = TPixelCM32(0, 0, pix->getTone());
        else
          assert(0);
        pix++;
      }
    }
    ras->unlock();
  }
}

/*------------------------------------------------------------------------*/

inline bool isTransparent(TPixelCM32 *pix) {
  return (((*((ULONG *)pix)) & 0x000fffff) == 0xff);
}

void TRop::overlayCmapped(TRasterCM32P rasOut, const TRasterCM32P &rasUp,
                          const TPaletteP &pltOut, const TPaletteP &upPlt,
                          std::map<int, int> &usedColors) {
  assert(rasOut && rasUp);
  assert(rasOut->getSize() == rasUp->getSize());

  TPaletteP downPlt = pltOut->clone();
  std::map<int, int>::iterator it;
  for (it = usedColors.begin(); it != usedColors.end(); it++)
    downPlt->getPage(0)->addStyle(TPixel32::Red);

  for (int y = 0; y < rasOut->getLy(); y++) {
    TPixelCM32 *pixDown = rasOut->pixels(y);
    TPixelCM32 *pixUp   = rasUp->pixels(y);

    for (int x = 0; x < rasOut->getLx(); x++, pixUp++, pixDown++) {
      if (isTransparent(pixUp))  // WARNING! cannot check transparent pixels
                                 // with *pixup==TPixelCM32() since also
                                 // 0x183000ff i.e., is a valid transparent
                                 // value
        continue;
      int tone = pixUp->getTone();
      if (isTransparent(pixDown) || tone == 255 || tone == 0 ||
          pixUp->getPaint() !=
              0)  // up e' punto interno, o esterno non antialiasato
      {
        addColors(*pixUp, downPlt, upPlt, usedColors);
        pixDown->setInk(usedColors[pixUp->getInk()]);
        pixDown->setPaint(usedColors[pixUp->getPaint()]);
        pixDown->setTone(tone);
      } else if (tone <= pixDown->getTone() || tone < 128)  // up e' bordo
                                                            // esterno
                                                            // antialiasato piu'
                                                            // opaco di down
      {
        int ink = pixUp->getInk();
        if (usedColors.find(ink) == usedColors.end())
          addColor(downPlt, ink, upPlt, usedColors);
        pixDown->setInk(usedColors[ink]);
        pixDown->setTone(tone < 128 ? 0 : tone);
      }
    }
  }
}

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

namespace {

#define MAGICFAC (257U * 256U + 1U)
/*
#define PIX_CM32_PENMAP_IDX(PIX) ((PIX)>>12 & 0xfff00 | (PIX) & 0xff)
#define PIX_CM32_COLMAP_IDX(PIX) ((PIX) & 0xfffff)


#define PIX_CM32_PENMAP_COLMAP_TO_RGBM(PIX,PENMAP,COLMAP,RES) \
  { \
  ULONG _r = (PIX); \
  ULONG _s = ((ULONG *)(PENMAP))[MY_PIX_CM32_PENMAP_IDX(_r)] + \
             ((ULONG *)(COLMAP))[MY_PIX_CM32_COLMAP_IDX(_r)]; \
  (RES) = *(LPIXEL *)(&_s); \
  }
  */

/*
void fillCmapRamp (RAS_CMAP& cmap, const TPixel32& color, int index)
{
index = index << cmap.info.tone_bits;
int xedni = index + cmap.info.n_tones-1;

TPixel32 _color=color;
        UINT fac = MAGICFAC * _color.m;
        _color.r=(UINT)(_color.r * fac + (1U<<23))>>24;
        _color.b=(UINT)(_color.b * fac + (1U<<23))>>24;
        _color.g=(UINT)(_color.g * fac + (1U<<23))>>24;

for (int tone = 0; tone < cmap.info.n_tones; tone++)
  {
  LPIXEL val;
  UINT magic_tone = tone * MAGICFAC;
  val.r = (UCHAR)((_color.r * magic_tone + (1<<23)) >> 24);
  val.g = (UCHAR)((_color.g * magic_tone + (1<<23)) >> 24);
  val.b = (UCHAR)((_color.b * magic_tone + (1<<23)) >> 24);
  val.m = (UCHAR)((_color.m * magic_tone + (1<<23)) >> 24);

  cmap.colbuffer[index++] = val;
  cmap.penbuffer[xedni--] = val;
  }
}
*/

/*---------------------------------------------------------------------------*/

}  // namespace

/*---------------------------------------------------------------------------*/

// \b NOTE: Starting from Toonz 6.1, some important improvements are introduced:
// a) The passed raster is now referenced by the returned _RASTER*, just the
// same way
//    smartpointer to rasters do.
// b) The cache is made aware of the passed raster, mainly because these old 4.6
// raster
//    structures are essentially used for memory-consuming operations with tlv
//    fxs and may
//    need to be shipped to hard disk on critical situations (a matter handled
//    by the cache).
// c) The lockRaster and unlockRaster functions are provided. They are meant to
// specify whether
//    the raster is actively referenced by the raster pointer, or is rather in a
//    lazy state -
//    so that the cache may move it to hard disk if necessary.

/*
static const TCM_INFO Tcm_my_default_info  = {  8,  8, 12,  20, 12,
                                               0x0000, 0x00ff,
                                               256, 4096, 256
                                             };*/

/*---------------------------------------------------------------------------*/

inline LPIXEL premultiplyLPIXEL(const TPixel32 &pix) {
  // int MAGICFAC = (257U * 256U + 1U);
  UINT fac = MAGICFAC * pix.m;
  LPIXEL out;
  out.r = ((UINT)(pix.r * fac + (1U << 23)) >> 24);
  out.g = ((UINT)(pix.g * fac + (1U << 23)) >> 24);
  out.b = ((UINT)(pix.b * fac + (1U << 23)) >> 24);
  out.m = pix.m;
  return out;
}

_RASTER *TRop::convertRaster50to46(const TRasterP &inRas,
                                   const TPaletteP &inPalette) {
  int lx   = inRas->getLx();
  int ly   = inRas->getLy();
  int wrap = inRas->getWrap();

  assert(lx > 0 && ly > 0);
  assert(inRas->getRawData());
  TRasterGR8P rgr8   = (TRasterGR8P)inRas;
  TRasterGR16P rgr16 = (TRasterGR16P)inRas;
  TRaster32P r32     = (TRaster32P)inRas;
  TRaster64P r64     = (TRaster64P)inRas;
  TRasterCM32P rcm   = (TRasterCM32P)inRas;

  RASTER *rout = new RASTER;
  memset(rout, 0, sizeof(RASTER));

  std::string id(TImageCache::instance()->getUniqueId());
  rout->cacheIdLength = id.size();
  rout->cacheId       = new char[rout->cacheIdLength];
  memcpy(rout->cacheId, id.data(), sizeof(char) * rout->cacheIdLength);
  {
    TImageP img;
    if (rcm)
      img = TToonzImageP(rcm, rcm->getBounds());  // saveBox is not considered
                                                  // in RASTER struct anyway
    else
      img = TRasterImageP(inRas);
    TImageCache::instance()->add(id, img);
  }
  inRas->addRef();

  rout->buffer        = inRas->getRawData();
  TRasterP parent     = inRas->getParent();
  rout->native_buffer = (parent) ? parent->getRawData() : inRas->getRawData();
  rout->lx            = lx;
  rout->ly            = ly;
  rout->wrap          = wrap;

  if (rgr8)
    rout->type = RAS_GR8;
  else if (rgr16)
    rout->type = RAS_GR16;
  else if (r32)
    rout->type = RAS_RGBM;
  else if (r64)
    rout->type = RAS_RGBM64;
  else if (rcm)
    rout->type = RAS_CM32;
  else
    assert(!"raster type not convertible!");

  if (rout->type != RAS_CM32) return rout;

  if (!inPalette) {
    assert(!"missing palette!");
    return NULL;
  }

  rout->cmap.info   = Tcm_32_default_info;
  rout->cmap.buffer = new LPIXEL[TCM_CMAP_PENBUFFER_SIZE(rout->cmap.info)];
  // rout->cmap.penbuffer = new LPIXEL[TCM_CMAP_PENBUFFER_SIZE
  // (rout->cmap.info)];
  // rout->cmap.colbuffer = new LPIXEL[TCM_CMAP_COLBUFFER_SIZE
  // (rout->cmap.info)];

  for (int i = 0; i < inPalette->getStyleCount(); i++)
    rout->cmap.buffer[i] =
        ::premultiplyLPIXEL(inPalette->getStyle(i)->getAverageColor());
  // fillCmapRamp (rout->cmap, inPalette->getStyle(i)->getMainColor(), i);

  return rout;
}

/*------------------------------------------------------------------------*/

void TRop::releaseRaster46(_RASTER *&r, bool doReleaseBuffer) {
  // Buffer release no more supported. These are now intended as smart
  // pointers to rasters - they release themselves on their own.
  assert(!doReleaseBuffer);

  if (r->type == RAS_CM32) {
    delete[] r->cmap.buffer;
    // delete [] r->cmap.penbuffer;
    // delete [] r->cmap.colbuffer;
  }

  if (doReleaseBuffer && r->native_buffer == r->buffer)
    delete r->buffer;  // Should not happen

  // Unlock if locked, and remove the cache reference
  if (r->buffer) unlockRaster(r);
  TImageCache::instance()->remove(std::string(r->cacheId, r->cacheIdLength));
  delete[] r->cacheId;

  delete r;
  r = 0;
}

/*------------------------------------------------------------------------*/

void TRop::lockRaster(RASTER *raster) {
  TImageP img(TImageCache::instance()->get(
      std::string(raster->cacheId, raster->cacheIdLength), true));
  TRasterP cacheRas;
  if (raster->type == RAS_CM32)
    cacheRas = TToonzImageP(img)->getRaster();
  else
    cacheRas = TRasterImageP(img)->getRaster();
  cacheRas->addRef();
  raster->buffer  = cacheRas->getRawData();
  TRasterP parent = cacheRas->getParent();
  raster->native_buffer =
      (parent) ? parent->getRawData() : cacheRas->getRawData();
}

/*------------------------------------------------------------------------*/

void TRop::unlockRaster(RASTER *raster) {
  TImageP img(TImageCache::instance()->get(
      std::string(raster->cacheId, raster->cacheIdLength), true));
  TRasterP cacheRas;
  if (raster->type == RAS_CM32)
    cacheRas = TToonzImageP(img)->getRaster();
  else
    cacheRas = TRasterImageP(img)->getRaster();
  cacheRas->release();
  raster->buffer        = 0;
  raster->native_buffer = 0;
}

/*------------------------------------------------------------------------*/

_RASTER *TRop::readRaster46(const char *filename) {
  // No more called in Toonz...

  TImageP img;

  TImageReader::load(TFilePath(filename), img);

  if ((TToonzImageP)img) return 0;

  TRasterImageP rimg = (TRasterImageP)img;

  if (!rimg) return 0;
  TRasterP ras = rimg->getRaster();

  return TRop::convertRaster50to46(ras, 0);
}

namespace {

inline TPixel32 getPix32(const TPixelCM32 &pixcm,
                         const std::vector<TPixel32> &colors) {
  int t = pixcm.getTone();
  int p = pixcm.getPaint();
  int i = pixcm.getInk();

  if (t == TPixelCM32::getMaxTone())
    return colors[p];
  else if (t == 0)
    return colors[i];
  else
    return blend(colors[i], colors[p], t, TPixelCM32::getMaxTone());
}

}  // namespace

// from 4.6

#ifdef LEVO

void TRop::zoomOutCm32Rgbm(const TRasterCM32P &rin, TRaster32P &rout,
                           const TPalette &plt, int x1, int y1, int x2, int y2,
                           int newx, int newy, int absZoomLevel) {
  TPixelCM32 *rowin, *pixin, *in, win;
  TPixel32 *rowout, *pixout, valin, valout;
  int tmp_r, tmp_g, tmp_b, tmp_m;
  int wrapin, wrapout;
  int x, y, lx, ly, xlast, ylast, xrest, yrest, i, j;
  int factor, fac_fac_2_bits;
  int fac_fac, yrest_fac, fac_xrest, yrest_xrest;
  int fac_fac_2, yrest_fac_2, fac_xrest_2, yrest_xrest_2;
  int fac_fac_4;

  int count = plt.getStyleCount();
  int count2 =
      std::max(count, TPixelCM32::getMaxInk(), TPixelCM32::getMaxPaint());
  std::vector<TPixel32> colors(count2);
  for (i      = 0; i < plt.getStyleCount(); i++)
    colors[i] = ::premultiply(plt.getStyle(i)->getAverageColor());

  lx             = x2 - x1 + 1;
  ly             = y2 - y1 + 1;
  factor         = 1 << absZoomLevel;
  xrest          = lx & (factor - 1);
  yrest          = ly & (factor - 1);
  xlast          = x2 - xrest + 1;
  ylast          = y2 - yrest + 1;
  fac_fac        = factor * factor;
  fac_fac_2      = fac_fac >> 1;
  fac_fac_4      = fac_fac >> 2;
  fac_fac_2_bits = 2 * absZoomLevel - 1;
  yrest_fac      = yrest * factor;
  yrest_fac_2    = yrest_fac >> 1;
  fac_xrest      = factor * xrest;
  fac_xrest_2    = fac_xrest >> 1;
  yrest_xrest    = yrest * xrest;
  yrest_xrest_2  = yrest_xrest >> 1;
  wrapin         = rin->getWrap();
  wrapout        = rout->getWrap();
  valout.m       = 0xff;

  rowin  = (TPixelCM32 *)rin->getRawData() + wrapin * y1 + x1;
  rowout = (TPixel32 *)rout->getRawData() + wrapout * newy + newx;
  for (y = y1; y < ylast; y += factor) {
    pixin  = rowin;
    pixout = rowout;
    for (x = x1; x < xlast; x += factor) {
      tmp_r = tmp_g = tmp_b = tmp_m = 0;
      in                            = pixin;
      for (j = 0; j < factor; j += 2) {
        for (i = 0; i < factor; i += 2) {
          win   = *in;
          valin = getPix32(win, colors);
          tmp_r += valin.r;
          tmp_g += valin.g;
          tmp_b += valin.b;
          tmp_m += valin.m;
          in += wrapin;
          in++;
          win   = *in;
          valin = getPix32(win, colors);
          tmp_r += valin.r;
          tmp_g += valin.g;
          tmp_b += valin.b;
          tmp_m += valin.m;
          in -= wrapin;
          in++;
        }
        in += 2 * wrapin - factor;
      }
      valout.r  = (tmp_r + fac_fac_4) >> fac_fac_2_bits;
      valout.g  = (tmp_g + fac_fac_4) >> fac_fac_2_bits;
      valout.b  = (tmp_b + fac_fac_4) >> fac_fac_2_bits;
      valout.m  = (tmp_m + fac_fac_4) >> fac_fac_2_bits;
      *pixout++ = valout;
      pixin += factor;
    }
    if (xrest) {
      tmp_r = tmp_g = tmp_b = tmp_m = 0;
      for (j = 0; j < factor; j++)
        for (i = 0; i < xrest; i++) {
          win   = pixin[i + j * wrapin];
          valin = getPix32(win, colors);
          tmp_r += valin.r;
          tmp_g += valin.g;
          tmp_b += valin.b;
          tmp_m += valin.m;
        }
      valout.r = (tmp_r + fac_xrest_2) / fac_xrest;
      valout.g = (tmp_g + fac_xrest_2) / fac_xrest;
      valout.b = (tmp_b + fac_xrest_2) / fac_xrest;
      valout.m = (tmp_m + fac_xrest_2) / fac_xrest;
      *pixout  = valout;
    }
    rowin += wrapin * factor;
    rowout += wrapout;
  }
  if (yrest) {
    pixin  = rowin;
    pixout = rowout;
    for (x = x1; x < xlast; x += factor) {
      tmp_r = tmp_g = tmp_b = tmp_m = 0;
      for (j = 0; j < yrest; j++)
        for (i = 0; i < factor; i++) {
          win   = pixin[i + j * wrapin];
          valin = getPix32(win, colors);
          tmp_r += valin.r;
          tmp_g += valin.g;
          tmp_b += valin.b;
          tmp_m += valin.m;
        }
      valout.r  = (tmp_r + yrest_fac_2) / yrest_fac;
      valout.g  = (tmp_g + yrest_fac_2) / yrest_fac;
      valout.b  = (tmp_b + yrest_fac_2) / yrest_fac;
      valout.m  = (tmp_m + yrest_fac_2) / yrest_fac;
      *pixout++ = valout;
      pixin += factor;
    }
    if (xrest) {
      tmp_r = tmp_g = tmp_b = tmp_m = 0;
      for (j = 0; j < yrest; j++)
        for (i = 0; i < xrest; i++) {
          win   = pixin[i + j * wrapin];
          valin = getPix32(win, colors);
          tmp_r += valin.r;
          tmp_g += valin.g;
          tmp_b += valin.b;
          tmp_m += valin.m;
        }
      valout.r = (tmp_r + yrest_xrest_2) / yrest_xrest;
      valout.g = (tmp_g + yrest_xrest_2) / yrest_xrest;
      valout.b = (tmp_b + yrest_xrest_2) / yrest_xrest;
      valout.m = (tmp_m + yrest_xrest_2) / yrest_xrest;
      *pixout  = valout;
    }
  }
}

#endif

/*-----------------------------------------------------------------------------*/

#ifdef LEVO

void TRop::makeIcon(TRaster32P &_rout, const TRasterCM32P &rin,
                    const TPaletteP &palette, bool onBlackBg) {
  int i, j;
  int lx = rin->getLx();
  int ly = rin->getLy();

  int count = palette->getStyleCount();
  int count2 =
      std::max(count, TPixelCM32::getMaxInk(), TPixelCM32::getMaxPaint());
  std::vector<TPixel32> colors(count2);
  for (i = 0; i < palette->getStyleCount(); i++)
    colors[i] =
        /*::premultiply(*/ palette->getStyle(i)->getAverageColor();  //);

  TDimension dim(_rout->getSize());
  TRaster32P rout;

  double arIn  = (double)lx / ly;
  double arOut = (double)dim.lx / dim.ly;
  if (!areAlmostEqual(arIn, arOut, 1e-2))  // do not want a stretched icon! I
                                           // extract a subraster with same
                                           // aspect ration of rin
  {
    int newlx, newly;
    if (arOut < arIn) {
      newlx = dim.lx;
      newly = dim.lx / arIn;
    } else {
      newly = dim.ly;
      newlx = dim.ly * arIn;
    }
    _rout->clear();
    rout = _rout->extract((dim.lx - newlx) / 2, (dim.ly - newly) / 2,
                          (dim.lx - newlx) / 2 + newlx - 1,
                          (dim.ly - newly) / 2 + newly - 1);
    dim = rout->getSize();
  } else
    rout = _rout;

  TPixel32 *pixOut0 = (TPixel32 *)rout->getRawData();

  int countY   = 0;
  bool newRow  = true;
  int currTone = TPixelCM32::getMaxTone();

  for (i = 0; i < ly; i++) {
    TPixelCM32 *pixIn = (TPixelCM32 *)rin->pixels(i);
    TPixel32 *pixOut  = pixOut0;
    int countX        = 0;
    for (j = 0; j < lx; j++) {
      if (newRow || currTone > pixIn->getTone()) {
        currTone = pixIn->getTone();
        if (onBlackBg)
          *pixOut = overPixOnBlack(getPix32(*pixIn, colors));
        else
          *pixOut = overPixOnWhite(getPix32(*pixIn, colors));
      }
      pixIn++;
      countX += dim.lx;
      if (countX >= lx)
        countX -= lx, pixOut++, currTone = TPixelCM32::getMaxTone();
    }

    countY += dim.ly;
    if (countY >= ly) {
      countY -= ly;
      pixOut0 += rout->getWrap();
      currTone = TPixelCM32::getMaxTone();
      newRow   = true;
    } else
      newRow = false;
  }
}

#endif

/*-----------------------------------------------------------------------------*/

void TRop::makeIcon(TRasterCM32P &_rout, const TRasterCM32P &rin) {
  int i, j;
  int lx = rin->getLx();
  int ly = rin->getLy();

  TDimension dim(_rout->getSize());
  TRasterCM32P rout;

  rout = _rout;

  rout->lock();
  rin->lock();
  TPixelCM32 *pixOut0 = (TPixelCM32 *)rout->getRawData();

  int countY  = 0;
  bool newRow = true;

  for (i = 0; i < ly; i++) {
    TPixelCM32 *pixIn  = (TPixelCM32 *)rin->pixels(i);
    TPixelCM32 *pixOut = pixOut0;
    int countX         = 0;
    bool newColumn     = true;
    for (j = 0; j < lx; j++) {
      if ((newRow && newColumn) || pixOut->getTone() > pixIn->getTone())
        *pixOut = *pixIn;

      pixIn++;
      countX += dim.lx;
      if (countX >= lx) {
        countX -= lx, pixOut++;
        newColumn = true;
      } else
        newColumn = false;
    }

    countY += dim.ly;
    if (countY >= ly) {
      countY -= ly;
      pixOut0 += rout->getWrap();
      newRow = true;
    } else
      newRow = false;
  }
  rout->unlock();
  rin->unlock();
}

/*-----------------------------------------------------------------------------*/