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