| |
| |
| #include "toonz/tbinarizer.h" |
| #include "tpixelutils.h" |
| |
| namespace |
| { |
| |
| |
| |
| |
| inline void computeColor(int &color, int &value, int &chroma, const TPixel32 &pix) |
| { |
| int c[3] = {pix.r, pix.g, pix.b}; |
| |
| if (c[0] < c[1]) |
| qSwap(c[0], c[1]); |
| if (c[1] < c[2]) |
| qSwap(c[1], c[2]); |
| if (c[0] < c[1]) |
| qSwap(c[0], c[1]); |
| assert(c[0] >= c[1] && c[1] >= c[2]); |
| int cMax = c[0]; |
| int cMin = c[2]; |
| value = cMax; |
| chroma = cMax - cMin; |
| if (chroma == 0) { |
| color = 0; |
| return; |
| } |
| |
| if (pix.r == cMax) |
| color = 1; |
| else if (pix.g == cMax) |
| color = 2; |
| else |
| color = 3; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| bool hasAlpha(const TPixel32 *buffer, int w, int h) |
| { |
| int n = qMin(500, w * h); |
| for (int i = 0; i < n; i++) |
| if (buffer[i].m < 255) |
| return true; |
| return false; |
| } |
| |
| |
| void removeAlpha(TPixel32 *buffer, int w, int h) |
| { |
| TPixel32 *pix = buffer; |
| TPixel32 *endPix = pix + w * h; |
| while (pix < endPix) { |
| *pix = overPixOnWhite(*pix); |
| ++pix; |
| } |
| } |
| |
| } |
| |
| |
| |
| TBinarizer::TBinarizer() |
| : m_alphaEnabled(true) |
| { |
| } |
| |
| |
| |
| void TBinarizer::process(const TRaster32P &ras) |
| { |
| |
| static const TPixel32 colors[] = { |
| TPixel32(0, 0, 0), |
| TPixel32(255, 0, 0), TPixel32(0, 255, 0), TPixel32(0, 0, 255), |
| TPixel32(0, 255, 255), TPixel32(255, 0, 255), TPixel32(255, 255, 0)}; |
| |
| int w = ras->getLx(), h = ras->getLy(); |
| TPixel32 *buffer = ras->pixels(); |
| assert(ras->getWrap() == ras->getLx()); |
| |
| |
| if (hasAlpha(buffer, w, h)) |
| removeAlpha(buffer, w, h); |
| |
| |
| int b = 5; |
| int bsize = 1 << b; |
| int w1 = ((w - 1) >> b) + 1, h1 = ((h - 1) >> b) + 1; |
| |
| |
| std::vector<unsigned char> vBuffer(w * h, 0); |
| std::vector<unsigned char> thrBuffer(w1 * h1, 0); |
| std::vector<unsigned char> qBuffer(w1 * h1, 0); |
| std::vector<unsigned char> tBuffer(w * h, 255); |
| std::vector<unsigned char> sBuffer(w * h, 255); |
| |
| std::vector<int> boundary1, boundary2; |
| boundary1.reserve(w * h / 2); |
| boundary2.reserve(w * h / 2); |
| |
| int goodBgQuadCount = 0; |
| |
| |
| |
| for (int y1 = 0; y1 < h1; y1++) { |
| int ya = y1 * bsize; |
| int yb = qMin(h, ya + bsize) - 1; |
| for (int x1 = 0; x1 < w1; x1++) { |
| int xa = x1 * bsize; |
| int xb = qMin(w, xa + bsize) - 1; |
| |
| int tot = 0; |
| std::vector<int> histo(32, 0); |
| for (int y = ya; y <= yb; y++) { |
| for (int x = xa; x <= xb; x++) { |
| int k = y * w + x; |
| TPixel32 pix = buffer[k]; |
| int v = qMin(qMin(pix.r, pix.g), pix.b); |
| vBuffer[k] = v; |
| histo[v >> 3] += 1; |
| tot += 1; |
| } |
| } |
| |
| int bgThreshold = 31; |
| if (histo[31] > tot / 2 && histo[30] < 4) { |
| |
| goodBgQuadCount++; |
| bgThreshold = 29; |
| } else { |
| |
| int i = 31; |
| while (i >= 0 && histo[i] == 0) |
| i--; |
| int i0 = i; |
| |
| while (i > 0 && histo[i - 1] > histo[i]) |
| i--; |
| int i1 = i; |
| |
| int i2 = 2 * i1 - i0; |
| bgThreshold = i2 - 1; |
| } |
| |
| |
| int i = 0; |
| int c = histo[i]; |
| while (i < bgThreshold && c < 20) |
| c += histo[++i]; |
| int k1 = y1 * w1 + x1; |
| qBuffer[k1] = i * 255 / 31; |
| bgThreshold = bgThreshold * 255 / 31; |
| thrBuffer[k1] = bgThreshold; |
| } |
| } |
| |
| |
| bool goodBackground = goodBgQuadCount > w1 * h1 / 2; |
| |
| |
| |
| int thrDelta = 0; |
| if (!goodBackground) |
| thrDelta = 20; |
| |
| |
| for (int y = 0; y < h; y++) |
| tBuffer[y * w] = tBuffer[y * w + w - 1] = 20; |
| for (int x = 0; x < w; x++) |
| tBuffer[x] = tBuffer[(h - 1) * w + x] = 20; |
| |
| |
| for (int y1 = 0; y1 < h1; y1++) { |
| int ya = qMax(1, y1 * bsize); |
| int yb = qMin(h - 1, ya + bsize) - 1; |
| for (int x1 = 0; x1 < w1; x1++) { |
| int k1 = y1 * w1 + x1; |
| |
| if (qBuffer[k1] >= thrBuffer[k1] - thrDelta) |
| continue; |
| |
| for (int y = ya + 1; y <= yb; y++) { |
| int xa = qMax(1, x1 * bsize); |
| int xb = qMin(w - 1, xa + bsize) - 1; |
| for (int x = xa + 1; x <= xb; x++) { |
| int k = y * w + x; |
| |
| int vk = vBuffer[k]; |
| int vk2 = vk; |
| if (vk < thrBuffer[k1]) { |
| |
| bool isSeed = false; |
| if (vk2 <= vBuffer[k + 1] && vk2 <= vBuffer[k - 1] && vk2 <= vBuffer[k + w] && vk2 <= vBuffer[k - w]) { |
| |
| int color, value, chroma; |
| computeColor(color, value, chroma, buffer[k]); |
| |
| const int chromaThreshold = 40; |
| if (chroma > chromaThreshold) { |
| |
| |
| int m = 0; |
| int dd[] = {1, -1, w, -w, 1 + w, 1 - w, -1 + w, -1 - w}; |
| for (int j = 0; j < 8; j++) { |
| int c1, v1, ch1; |
| computeColor(c1, v1, ch1, buffer[k + dd[j]]); |
| if (ch1 > 20 && c1 == color) |
| m++; |
| } |
| |
| |
| if (m < 2) |
| chroma = chromaThreshold - 1; |
| } |
| |
| if (chroma > chromaThreshold) { |
| |
| tBuffer[k] = color; |
| isSeed = true; |
| } else if (chroma < 15 && vBuffer[k] * 100 < qBuffer[k1] * 25 + (thrBuffer[k1] - thrDelta) * 75) { |
| |
| tBuffer[k] = 0; |
| isSeed = true; |
| } |
| } |
| if (isSeed) { |
| |
| |
| sBuffer[k] = (vBuffer[k] + thrBuffer[k1]) / 2; |
| boundary1.push_back(k); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| for (int it = 0; it < 10 && !boundary1.empty(); it++) { |
| |
| for (int i = 0; i < (int)boundary1.size(); i++) { |
| int k = boundary1[i]; |
| |
| const int dd[] = {1, -1, w, -w}; |
| for (int j = 0; j < 4; j++) { |
| int ka = dd[j] + k; |
| |
| if (tBuffer[ka] > 20 && vBuffer[ka] < sBuffer[k]) { |
| |
| tBuffer[ka] = tBuffer[k]; |
| sBuffer[ka] = sBuffer[k]; |
| boundary2.push_back(ka); |
| } |
| } |
| } |
| |
| boundary1.clear(); |
| boundary1.swap(boundary2); |
| } |
| |
| |
| TPixel32 bgColor = m_alphaEnabled ? TPixel32(0, 0, 0, 0) : TPixel32(255, 255, 255); |
| for (int y = 0; y < h; y++) { |
| for (int x = 0; x < w; x++) { |
| int k = y * w + x; |
| if (tBuffer[k] <= 6) |
| buffer[k] = colors[tBuffer[k]]; |
| else |
| buffer[k] = bgColor; |
| } |
| } |
| } |
| |