Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
#include "toonz/tbinarizer.h"
Toshihiro Shimizu 890ddd
#include "tpixelutils.h"
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
namespace {
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//
Shinya Kitaoka 120a6e
// computeColor: RGB =>  colore(0=Black, 1=Red,2=Green,3=Blue), valore
Shinya Kitaoka 120a6e
// (=max(r,g,b)), croma (= max(r,g,b)-min(r,g,b))
Toshihiro Shimizu 890ddd
//
Shinya Kitaoka 120a6e
inline void computeColor(int &color, int &value, int &chroma,
Shinya Kitaoka 120a6e
                         const TPixel32 &pix) {
Shinya Kitaoka 120a6e
  int c[3] = {pix.r, pix.g, pix.b};
Shinya Kitaoka 120a6e
  // unrolled bubble-sort
Shinya Kitaoka 120a6e
  if (c[0] < c[1]) qSwap(c[0], c[1]);
Shinya Kitaoka 120a6e
  if (c[1] < c[2]) qSwap(c[1], c[2]);
Shinya Kitaoka 120a6e
  if (c[0] < c[1]) qSwap(c[0], c[1]);
Shinya Kitaoka 120a6e
  assert(c[0] >= c[1] && c[1] >= c[2]);
Shinya Kitaoka 120a6e
  int cMax = c[0];
Shinya Kitaoka 120a6e
  int cMin = c[2];
Shinya Kitaoka 120a6e
  value    = cMax;
Shinya Kitaoka 120a6e
  chroma   = cMax - cMin;
Shinya Kitaoka 120a6e
  if (chroma == 0) {
Shinya Kitaoka 120a6e
    color = 0;
Shinya Kitaoka 120a6e
    return;
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  if (pix.r == cMax)
Shinya Kitaoka 120a6e
    color = 1;
Shinya Kitaoka 120a6e
  else if (pix.g == cMax)
Shinya Kitaoka 120a6e
    color = 2;
Shinya Kitaoka 120a6e
  else
Shinya Kitaoka 120a6e
    color = 3;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  /*
Shinya Kitaoka 120a6e
// quando gestiremo anche ciano, magenta e giallo
Shinya Kitaoka 120a6e
int cD0 = c[0]-c[1], cD1 = c[1]-c[2];
Shinya Kitaoka 120a6e
if(cD0>cD1)
Toshihiro Shimizu 890ddd
{
Shinya Kitaoka 120a6e
if(pix.r==cMax) color=1;
Shinya Kitaoka 120a6e
else if(pix.g==cMax) color=2;
Shinya Kitaoka 120a6e
else color=3;
Shinya Kitaoka 120a6e
}
Shinya Kitaoka 120a6e
else
Shinya Kitaoka 120a6e
{
Shinya Kitaoka 120a6e
if(pix.r==cMin) color=4;
Shinya Kitaoka 120a6e
else if(pix.g==cMin) color=5;
Shinya Kitaoka 120a6e
else color=6;
Shinya Kitaoka 120a6e
}
Shinya Kitaoka 120a6e
*/
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
// ritorna true se i primi pixel del buffer non sono tutti opachi
Shinya Kitaoka 120a6e
bool hasAlpha(const TPixel32 *buffer, int w, int h) {
shun-iwasawa a0d489
  int n = std::min(500, w * h);
Shinya Kitaoka 120a6e
  for (int i = 0; i < n; i++)
Shinya Kitaoka 120a6e
    if (buffer[i].m < 255) return true;
Shinya Kitaoka 120a6e
  return false;
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
// aggiunge un bianco opaco all'immagine (dovremmo gia' avere una funzione del
Shinya Kitaoka 120a6e
// genere, ma non l'ho trovata)
Shinya Kitaoka 120a6e
void removeAlpha(TPixel32 *buffer, int w, int h) {
Shinya Kitaoka 120a6e
  TPixel32 *pix    = buffer;
Shinya Kitaoka 120a6e
  TPixel32 *endPix = pix + w * h;
Shinya Kitaoka 120a6e
  while (pix < endPix) {
Shinya Kitaoka 120a6e
    *pix = overPixOnWhite(*pix);
Shinya Kitaoka 120a6e
    ++pix;
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
}
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
}  // namespace
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//=========================================================
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
TBinarizer::TBinarizer() : m_alphaEnabled(true) {}
Toshihiro Shimizu 890ddd
Toshihiro Shimizu 890ddd
//---------------------------------------------------------
Toshihiro Shimizu 890ddd
Shinya Kitaoka 120a6e
void TBinarizer::process(const TRaster32P &ras) {
Shinya Kitaoka 120a6e
  // palette di colori puri che verranno usati per l'output: nero, rosso, verde,
Shinya Kitaoka 120a6e
  // blu
Shinya Kitaoka 120a6e
  static const TPixel32 colors[] = {
Shinya Kitaoka 120a6e
      TPixel32(0, 0, 0),    TPixel32(255, 0, 0),   TPixel32(0, 255, 0),
Shinya Kitaoka 120a6e
      TPixel32(0, 0, 255),  TPixel32(0, 255, 255), TPixel32(255, 0, 255),
Shinya Kitaoka 120a6e
      TPixel32(255, 255, 0)};
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  int w = ras->getLx(), h = ras->getLy();
Shinya Kitaoka 120a6e
  TPixel32 *buffer = ras->pixels();  // WARNING! non funziona sui sotto-raster
Shinya Kitaoka 120a6e
  assert(ras->getWrap() == ras->getLx());
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // mi cautelo contro immagini trasparenti
Shinya Kitaoka 120a6e
  if (hasAlpha(buffer, w, h)) removeAlpha(buffer, w, h);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // divido l'immagine in quadrati grandi 2^b pixel
Shinya Kitaoka 120a6e
  int b     = 5;
Shinya Kitaoka 120a6e
  int bsize = 1 << b;
Shinya Kitaoka 120a6e
  int w1 = ((w - 1) >> b) + 1, h1 = ((h - 1) >> b) + 1;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // buffer di appoggio
Shinya Kitaoka 120a6e
  std::vector<unsigned char=""> vBuffer(</unsigned>
Shinya Kitaoka 120a6e
      w * h, 0);  // per ogni pixel: v = min(r,g,b) (inchiostro scuro su sfondo
Shinya Kitaoka 120a6e
                  // chiaro: mi sembra che min funzioni meglio di max)
Shinya Kitaoka 120a6e
  std::vector<unsigned char=""> thrBuffer(w1 * h1, 0);  // per ogni quadrato: se</unsigned>
Shinya Kitaoka 120a6e
                                                     // v>thrBuffer[k1] allora
Shinya Kitaoka 120a6e
                                                     // siamo sicuro che e'
Shinya Kitaoka 120a6e
                                                     // sfondo
Shinya Kitaoka 120a6e
  std::vector<unsigned char=""> qBuffer(w1 * h1, 0);  // per ogni quadrato: quel v</unsigned>
Shinya Kitaoka 120a6e
                                                   // tale che solo 20 pixel in
Shinya Kitaoka 120a6e
                                                   // tutto il quadrato sono
Shinya Kitaoka 120a6e
                                                   // piu' scuri
Shinya Kitaoka 120a6e
  std::vector<unsigned char=""> tBuffer(w * h, 255);  // per ogni pixel: 'tipo' del</unsigned>
Shinya Kitaoka 120a6e
                                                   // pixel (0..6=>colore,
Shinya Kitaoka 120a6e
                                                   // 20=cornice esterna, )
Shinya Kitaoka 120a6e
  std::vector<unsigned char=""> sBuffer(w * h, 255);  // per ogni pixel: quando</unsigned>
Shinya Kitaoka 120a6e
                                                   // faccio il fill il colore
Shinya Kitaoka 120a6e
                                                   // si puo' estendere solo sui
Shinya Kitaoka 120a6e
                                                   // vicini con v
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  std::vector<int> boundary1, boundary2;  // usati per il fill</int>
Shinya Kitaoka 120a6e
  boundary1.reserve(w * h / 2);
Shinya Kitaoka 120a6e
  boundary2.reserve(w * h / 2);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  int goodBgQuadCount = 0;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // per ogni quadrato costruisco l'istrogramma e determino il threshold per il
Shinya Kitaoka 120a6e
  // bg
Shinya Kitaoka 120a6e
  // conto i quadrati che hanno un "buon" background
Shinya Kitaoka 120a6e
  for (int y1 = 0; y1 < h1; y1++) {
Shinya Kitaoka 120a6e
    int ya = y1 * bsize;
shun-iwasawa a0d489
    int yb = std::min(h, ya + bsize) - 1;
Shinya Kitaoka 120a6e
    for (int x1 = 0; x1 < w1; x1++) {
Shinya Kitaoka 120a6e
      int xa = x1 * bsize;
shun-iwasawa a0d489
      int xb = std::min(w, xa + bsize) - 1;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
      int tot = 0;
Shinya Kitaoka 120a6e
      std::vector<int> histo(32, 0);</int>
Shinya Kitaoka 120a6e
      for (int y = ya; y <= yb; y++) {
Shinya Kitaoka 120a6e
        for (int x = xa; x <= xb; x++) {
Shinya Kitaoka 120a6e
          int k        = y * w + x;
Shinya Kitaoka 120a6e
          TPixel32 pix = buffer[k];
shun-iwasawa a0d489
          int v        = std::min({pix.r, pix.g, pix.b});
Shinya Kitaoka 120a6e
          vBuffer[k]   = v;
Shinya Kitaoka 120a6e
          histo[v >> 3] += 1;
Shinya Kitaoka 120a6e
          tot += 1;
Shinya Kitaoka 120a6e
        }
Shinya Kitaoka 120a6e
      }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
      int bgThreshold = 31;
Shinya Kitaoka 120a6e
      if (histo[31] > tot / 2 && histo[30] < 4) {
Shinya Kitaoka 120a6e
        // "buon" background. Picco su histo[31] che finisce in histo[30]
Shinya Kitaoka 120a6e
        goodBgQuadCount++;
Shinya Kitaoka 120a6e
        bgThreshold = 29;
Shinya Kitaoka 120a6e
      } else {
Shinya Kitaoka 120a6e
        // background normale. salto eventuali zeri (lo sfondo puo' essere
Shinya Kitaoka 120a6e
        // grigio, anche scuro)
Shinya Kitaoka 120a6e
        int i = 31;
Shinya Kitaoka 120a6e
        while (i >= 0 && histo[i] == 0) i--;
Shinya Kitaoka 120a6e
        int i0 = i;
Shinya Kitaoka 120a6e
        // cerco il massimo
Shinya Kitaoka 120a6e
        while (i > 0 && histo[i - 1] > histo[i]) i--;
Shinya Kitaoka 120a6e
        int i1 = i;
MCCCS a0ce32
        // presuppongo che il picco del BG sia symmetrico: i0-i1 == i1-i2
Shinya Kitaoka 120a6e
        int i2      = 2 * i1 - i0;
Shinya Kitaoka 120a6e
        bgThreshold = i2 - 1;
Shinya Kitaoka 120a6e
      }
Shinya Kitaoka 120a6e
      // calcolo qBuffer[k1] : e' un valore di v tale che pochi pixel (<20)
Shinya Kitaoka 120a6e
      // hanno un valore inferiore.
Shinya Kitaoka 120a6e
      // Se qBuffer[k1] e' molto grande vuol dire che il quadrato e' vuoto
Shinya Kitaoka 120a6e
      int i = 0;
Shinya Kitaoka 120a6e
      int c = histo[i];
Shinya Kitaoka 120a6e
      while (i < bgThreshold && c < 20) c += histo[++i];
Shinya Kitaoka 120a6e
      int k1        = y1 * w1 + x1;
Shinya Kitaoka 120a6e
      qBuffer[k1]   = i * 255 / 31;
Shinya Kitaoka 120a6e
      bgThreshold   = bgThreshold * 255 / 31;
Shinya Kitaoka 120a6e
      thrBuffer[k1] = bgThreshold;
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // L'immagine ha un buon background se la maggior parte dei quadrati ha un
Shinya Kitaoka 120a6e
  // buon background
Shinya Kitaoka 120a6e
  bool goodBackground = goodBgQuadCount > w1 * h1 / 2;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // thrDelta e' una correzione sul threshold per immagini con cattivo
Shinya Kitaoka 120a6e
  // background
Shinya Kitaoka 120a6e
  // un quadrato che abbia meno di 20 (v.s sopra c<20) sotto
Shinya Kitaoka 120a6e
  // thrBuffer[k1]-thrDelta e' considerato sfondo
Shinya Kitaoka 120a6e
  int thrDelta                  = 0;
Shinya Kitaoka 120a6e
  if (!goodBackground) thrDelta = 20;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // inizializzo la cornice dell'immagine (mi serve per non sconfinare quando
Shinya Kitaoka 120a6e
  // faccio fill)
Shinya Kitaoka 120a6e
  for (int y = 0; y < h; y++) tBuffer[y * w] = tBuffer[y * w + w - 1] = 20;
Shinya Kitaoka 120a6e
  for (int x = 0; x < w; x++) tBuffer[x] = tBuffer[(h - 1) * w + x] = 20;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // cerco i pixel sicuramente colorati e i sicuramente neri
Shinya Kitaoka 120a6e
  for (int y1 = 0; y1 < h1; y1++) {
shun-iwasawa a0d489
    int ya = std::max(1, y1 * bsize);
shun-iwasawa a0d489
    int yb = std::min(h - 1, ya + bsize) - 1;
Shinya Kitaoka 120a6e
    for (int x1 = 0; x1 < w1; x1++) {
Shinya Kitaoka 120a6e
      int k1 = y1 * w1 + x1;
Shinya Kitaoka 120a6e
      // salto i quadrati completamente bianchi
Shinya Kitaoka 120a6e
      if (qBuffer[k1] >= thrBuffer[k1] - thrDelta) continue;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
      for (int y = ya + 1; y <= yb; y++) {
shun-iwasawa a0d489
        int xa = std::max(1, x1 * bsize);
shun-iwasawa a0d489
        int xb = std::min(w - 1, xa + bsize) - 1;
Shinya Kitaoka 120a6e
        for (int x = xa + 1; x <= xb; x++) {
Shinya Kitaoka 120a6e
          int k = y * w + x;
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
          int vk  = vBuffer[k];
Shinya Kitaoka 120a6e
          int vk2 = vk;
Shinya Kitaoka 120a6e
          if (vk < thrBuffer[k1]) {
Shinya Kitaoka 120a6e
            // vk
Shinya Kitaoka 120a6e
            bool isSeed = false;
Shinya Kitaoka 120a6e
            if (vk2 <= vBuffer[k + 1] && vk2 <= vBuffer[k - 1] &&
Shinya Kitaoka 120a6e
                vk2 <= vBuffer[k + w] && vk2 <= vBuffer[k - w]) {
Shinya Kitaoka 120a6e
              // il pixel e' un minimo locale. calcolo il colore
Shinya Kitaoka 120a6e
              int color, value, chroma;
Shinya Kitaoka 120a6e
              computeColor(color, value, chroma, buffer[k]);
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
              const int chromaThreshold = 40;
Shinya Kitaoka 120a6e
              if (chroma > chromaThreshold) {
Shinya Kitaoka 120a6e
                // il pixel e' un buon candidato ad essere "sicuramente
Shinya Kitaoka 120a6e
                // colorato": controllo i vicini
Shinya Kitaoka 120a6e
                // per evitare i pixel colorati sparsi (sulle linee nere).
Shinya Kitaoka 120a6e
                int m    = 0;
Shinya Kitaoka 120a6e
                int dd[] = {1, -1, w, -w, 1 + w, 1 - w, -1 + w, -1 - w};
Shinya Kitaoka 120a6e
                for (int j = 0; j < 8; j++) {
Shinya Kitaoka 120a6e
                  int c1, v1, ch1;
Shinya Kitaoka 120a6e
                  computeColor(c1, v1, ch1, buffer[k + dd[j]]);
Shinya Kitaoka 120a6e
                  if (ch1 > 20 && c1 == color) m++;
Shinya Kitaoka 120a6e
                }
Shinya Kitaoka 120a6e
                // scarto se intorno non ci sono almeno due pixel dello stesso
Shinya Kitaoka 120a6e
                // colore
Shinya Kitaoka 120a6e
                // n.b. lascio un valore alto per chroma per evitare che il
Shinya Kitaoka 120a6e
                // pixel possa diventare "sicuramente nero". vedi sotto
Shinya Kitaoka 120a6e
                if (m < 2) chroma = chromaThreshold - 1;
Shinya Kitaoka 120a6e
              }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
              if (chroma > chromaThreshold) {
Shinya Kitaoka 120a6e
                // "sicuramente" colorato
Shinya Kitaoka 120a6e
                tBuffer[k] = color;
Shinya Kitaoka 120a6e
                isSeed     = true;
Shinya Kitaoka 120a6e
              } else if (chroma < 15 &&
Shinya Kitaoka 120a6e
                         vBuffer[k] * 100 <
Shinya Kitaoka 120a6e
                             qBuffer[k1] * 25 +
Shinya Kitaoka 120a6e
                                 (thrBuffer[k1] - thrDelta) * 75) {
Shinya Kitaoka 120a6e
                // molto poco colorato e scuro (al 25% della distanza fra
Shinya Kitaoka 120a6e
                // qBuffer[] e bg) => "sicuramente" nero
Shinya Kitaoka 120a6e
                tBuffer[k] = 0;
Shinya Kitaoka 120a6e
                isSeed     = true;
Shinya Kitaoka 120a6e
              }
Shinya Kitaoka 120a6e
            }
Shinya Kitaoka 120a6e
            if (isSeed) {
Shinya Kitaoka 120a6e
              // se il pixel e' sicuramente colorato o sicuramente nero diventa
Shinya Kitaoka 120a6e
              // un seme per il fill
Shinya Kitaoka 120a6e
              // il fill si deve fermare quando sono al 50% della distanza verso
Shinya Kitaoka 120a6e
              // il bg
Shinya Kitaoka 120a6e
              sBuffer[k] = (vBuffer[k] + thrBuffer[k1]) / 2;
Shinya Kitaoka 120a6e
              boundary1.push_back(k);
Shinya Kitaoka 120a6e
            }
Shinya Kitaoka 120a6e
          }
Shinya Kitaoka 120a6e
        }
Shinya Kitaoka 120a6e
      }
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // vado avanti finche' ci sono semi, ma non oltre 10 iterazioni
Shinya Kitaoka 120a6e
  for (int it = 0; it < 10 && !boundary1.empty(); it++) {
Shinya Kitaoka 120a6e
    // per tutti i semi
Shinya Kitaoka 120a6e
    for (int i = 0; i < (int)boundary1.size(); i++) {
Shinya Kitaoka 120a6e
      int k = boundary1[i];
Shinya Kitaoka 120a6e
      // per ogni direzione
Shinya Kitaoka 120a6e
      const int dd[] = {1, -1, w, -w};
Shinya Kitaoka 120a6e
      for (int j = 0; j < 4; j++) {
Shinya Kitaoka 120a6e
        int ka = dd[j] + k;
Shinya Kitaoka 120a6e
        // se il pixel adiacente non e' gia' colorato (e non e' la cornice
Shinya Kitaoka 120a6e
        // esterna) e ha un v ancora abbastanza scuro...
Shinya Kitaoka 120a6e
        if (tBuffer[ka] > 20 && vBuffer[ka] < sBuffer[k]) {
Shinya Kitaoka 120a6e
          // lo coloro e lo faccio diventare un nuovo seme
Shinya Kitaoka 120a6e
          tBuffer[ka] = tBuffer[k];
Shinya Kitaoka 120a6e
          sBuffer[ka] = sBuffer[k];
Shinya Kitaoka 120a6e
          boundary2.push_back(ka);
Shinya Kitaoka 120a6e
        }
Shinya Kitaoka 120a6e
      }
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
    // scambio i buffer
Shinya Kitaoka 120a6e
    boundary1.clear();
Shinya Kitaoka 120a6e
    boundary1.swap(boundary2);
Shinya Kitaoka 120a6e
  }
Shinya Kitaoka 120a6e
Shinya Kitaoka 120a6e
  // output
Shinya Kitaoka 120a6e
  TPixel32 bgColor =
Shinya Kitaoka 120a6e
      m_alphaEnabled ? TPixel32(0, 0, 0, 0) : TPixel32(255, 255, 255);
Shinya Kitaoka 120a6e
  for (int y = 0; y < h; y++) {
Shinya Kitaoka 120a6e
    for (int x = 0; x < w; x++) {
Shinya Kitaoka 120a6e
      int k = y * w + x;
Shinya Kitaoka 120a6e
      if (tBuffer[k] <= 6)
Shinya Kitaoka 120a6e
        buffer[k] = colors[tBuffer[k]];
Shinya Kitaoka 120a6e
      else
Shinya Kitaoka 120a6e
        buffer[k] = bgColor;
Shinya Kitaoka 120a6e
    }
Shinya Kitaoka 120a6e
  }
Toshihiro Shimizu 890ddd
}