| |
| |
| #include "toonz/Naa2TlvConverter.h" |
| |
| #include "toonz/tcenterlinevectorizer.h" |
| #include "toonz/stage.h" |
| #include "tpixelutils.h" |
| #include "tpalette.h" |
| |
| #include <QSet> |
| #include <QTime> |
| #include <QDebug> |
| #include <QMultiMap> |
| |
| QString RegionInfo::getTypeString() const |
| { |
| switch (type) { |
| case RegionInfo::Background: |
| return "Background"; |
| case RegionInfo::Ink: |
| return "Ink"; |
| case RegionInfo::ThinInk: |
| return "ThinInk"; |
| case RegionInfo::MainInk: |
| return "Main ink"; |
| case RegionInfo::SyntheticInk: |
| return "SyntheticInk"; |
| case RegionInfo::Paint: |
| return "Paint"; |
| case RegionInfo::LargePaint: |
| return "LargePaint"; |
| case RegionInfo::SmallPaint: |
| return "SmallPaint"; |
| case RegionInfo::Unused: |
| return "Unused"; |
| case RegionInfo::Unknown: |
| return "Unknown"; |
| default: |
| return "??????"; |
| } |
| } |
| |
| Naa2TlvConverter::Naa2TlvConverter() |
| : MaxColorCount(1000), m_regionRas(0), m_borderRas(0), m_dotRas(0), m_syntheticInkRas(0), m_inkThickness(0), m_palette(0), m_valid(false) |
| |
| { |
| } |
| |
| Naa2TlvConverter::~Naa2TlvConverter() |
| { |
| delete m_regionRas; |
| delete m_borderRas; |
| delete m_dotRas; |
| delete m_syntheticInkRas; |
| if (m_palette) |
| m_palette->release(); |
| } |
| |
| void Naa2TlvConverter::setPalette(TPalette *palette) |
| { |
| if (m_palette != palette) { |
| if (palette) |
| palette->addRef(); |
| if (m_palette) |
| m_palette->release(); |
| m_palette = palette; |
| } |
| } |
| |
| void Naa2TlvConverter::process(const TRaster32P &srcRas) |
| { |
| m_valid = false; |
| setSourceImage(srcRas); |
| if (!m_valid) |
| return; |
| |
| separateRegions(); |
| |
| computeLinks(); |
| |
| findRegionBorders(); |
| erodeRegions(); |
| |
| findLargePaints(); |
| |
| findBackgroundRegions(); |
| findMainInks(); |
| |
| |
| findThinInks(); |
| |
| measureThickness(); |
| |
| |
| findPaints2(); |
| |
| findThinPaints(); |
| |
| findSuspectInks(); |
| |
| addBorderInks(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::setSourceImage(const TRaster32P &srcRas) |
| { |
| int lx = srcRas->getSize().lx; |
| int ly = srcRas->getSize().ly; |
| |
| m_colors.clear(); |
| m_regions.clear(); |
| delete m_regionRas; |
| m_regionRas = new WorkRaster<unsigned short>(lx, ly); |
| delete m_borderRas; |
| m_borderRas = 0; |
| |
| delete m_dotRas; |
| m_dotRas = 0; |
| |
| delete m_syntheticInkRas; |
| m_syntheticInkRas = 0; |
| |
| QMap<TPixel32, int> colorTable; |
| for (int y = 0; y < ly; y++) { |
| TPixel32 *srcScanLine = srcRas->pixels(y); |
| unsigned short *regionScanLine = m_regionRas->pixels(y); |
| for (int x = 0; x < lx; x++) { |
| TPixel32 srcPix = overPixOnWhite(srcScanLine[x]); |
| QMap<TPixel32, int>::ConstIterator it = colorTable.find(srcPix); |
| if (it == colorTable.end()) { |
| |
| RegionInfo r; |
| |
| r.colorIndex = m_colors.count(); |
| m_colors.append(srcPix); |
| r.pixelCount = 1; |
| |
| int regionIndex = m_regions.count(); |
| m_regions.append(r); |
| |
| regionScanLine[x] = regionIndex; |
| colorTable.insert(srcPix, regionIndex); |
| if (m_colors.count() > MaxColorCount) { |
| return; |
| } |
| } else { |
| |
| int regionIndex = it.value(); |
| regionScanLine[x] = regionIndex; |
| m_regions[regionIndex].pixelCount++; |
| } |
| } |
| } |
| m_valid = true; |
| } |
| |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::separateRegions() |
| { |
| if (!m_regionRas) |
| return; |
| const int lx = m_regionRas->getLx(); |
| const int ly = m_regionRas->getLy(); |
| const int wrap = lx; |
| |
| |
| m_regions.clear(); |
| |
| WorkRaster<int> wb(lx, ly); |
| |
| QVector<int> regionMap; |
| QList<int> freeRegions; |
| |
| for (int y = 0; y < ly; y++) { |
| unsigned short *waScanLine = m_regionRas->pixels(y); |
| int *wbScanLine = wb.pixels(y); |
| for (int x = 0; x < lx; x++) { |
| int c = waScanLine[x]; |
| int cUp = y > 0 ? waScanLine[x - wrap] : -1; |
| int cLeft = x > 0 ? waScanLine[x - 1] : -1; |
| int cUpLeft = x > 0 && y > 0 ? waScanLine[x - wrap - 1] : -1; |
| |
| if (c != cUp && c != cLeft && c != cUpLeft) { |
| |
| Q_ASSERT(0 <= c && c < m_colors.count()); |
| RegionInfo region; |
| region.colorIndex = c; |
| int regionIndex; |
| if (!freeRegions.empty()) { |
| |
| regionIndex = freeRegions.back(); |
| freeRegions.pop_back(); |
| m_regions[regionIndex] = region; |
| } else { |
| |
| regionIndex = m_regions.count(); |
| m_regions.append(region); |
| } |
| wbScanLine[x] = regionMap.count(); |
| regionMap.append(regionIndex); |
| } else { |
| |
| if (c == cUpLeft) |
| wbScanLine[x] = wbScanLine[x - wrap - 1]; |
| else if (c == cUp) |
| wbScanLine[x] = wbScanLine[x - wrap]; |
| else { |
| Q_ASSERT(c == cLeft); |
| wbScanLine[x] = wbScanLine[x - 1]; |
| } |
| } |
| |
| |
| if (cUp == cLeft && cUp >= 0 && regionMap[wbScanLine[x - 1]] != regionMap[wbScanLine[x - wrap]]) { |
| |
| int pixelToDiscard = wbScanLine[x - 1]; |
| int pixelToKeep = wbScanLine[x - wrap]; |
| Q_ASSERT(pixelToDiscard != pixelToKeep); |
| int regionToDiscard = regionMap[pixelToDiscard]; |
| int regionToKeep = regionMap[pixelToKeep]; |
| Q_ASSERT(regionToDiscard != regionToKeep); |
| Q_ASSERT(m_regions[regionToDiscard].type != RegionInfo::Unused); |
| Q_ASSERT(m_regions[regionToKeep].type != RegionInfo::Unused); |
| for (int i = 0; i < regionMap.count(); i++) |
| if (regionMap[i] == regionToDiscard) |
| regionMap[i] = regionToKeep; |
| |
| m_regions[regionToDiscard].type = RegionInfo::Unused; |
| freeRegions.append(regionToDiscard); |
| } |
| } |
| } |
| |
| |
| for (;;) { |
| |
| if (!m_regions.empty() && m_regions.back().type == RegionInfo::Unused) { |
| m_regions.pop_back(); |
| int k = m_regions.count(); |
| freeRegions.removeAll(k); |
| } else if (!freeRegions.empty()) { |
| |
| int regionToDiscard = m_regions.count() - 1; |
| int regionToKeep = freeRegions.back(); |
| freeRegions.pop_back(); |
| Q_ASSERT(m_regions[regionToKeep].type == RegionInfo::Unused); |
| Q_ASSERT(m_regions[regionToDiscard].type != RegionInfo::Unused); |
| Q_ASSERT(regionToKeep < regionToDiscard); |
| m_regions[regionToKeep] = m_regions[regionToDiscard]; |
| |
| m_regions.pop_back(); |
| |
| for (int i = 0; i < regionMap.count(); i++) |
| if (regionMap[i] == regionToDiscard) |
| regionMap[i] = regionToKeep; |
| } else |
| break; |
| } |
| |
| Q_ASSERT(freeRegions.empty()); |
| |
| for (int i = 0; i < m_regions.count(); i++) |
| m_regions[i].pixelCount = 0; |
| |
| |
| for (int y = 0; y < ly; y++) { |
| unsigned short *regionScanLine = m_regionRas->pixels(y); |
| int *wbScanLine = wb.pixels(y); |
| for (int x = 0; x < lx; x++) { |
| int regionIndex = regionMap[wbScanLine[x]]; |
| regionScanLine[x] = (unsigned short)regionIndex; |
| m_regions[regionIndex].pixelCount++; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::computeLinks() |
| { |
| if (!m_regionRas || m_regions.empty()) |
| return; |
| const int lx = m_regionRas->getLx(); |
| const int ly = m_regionRas->getLy(); |
| const int wrap = lx; |
| unsigned short *waScanLine; |
| int x; |
| for (int i = 0; i < m_regions.count(); i++) |
| m_regions[i].perimeter = 0; |
| for (int y = 0; y < ly; y++) { |
| waScanLine = m_regionRas->pixels(y); |
| for (x = 0; x < lx; x++) { |
| int c = waScanLine[x]; |
| int cUp = y > 0 ? waScanLine[x - wrap] : -1; |
| int cLeft = x > 0 ? waScanLine[x - 1] : -1; |
| if (c != cUp) { |
| m_regions[c].touchRegion(cUp); |
| m_regions[c].perimeter++; |
| if (cUp >= 0) { |
| m_regions[cUp].touchRegion(c); |
| m_regions[cUp].perimeter++; |
| } |
| } |
| if (c != cLeft) { |
| m_regions[c].touchRegion(cLeft); |
| m_regions[c].perimeter++; |
| if (cLeft >= 0) { |
| m_regions[cLeft].touchRegion(c); |
| m_regions[cLeft].perimeter++; |
| } |
| } |
| } |
| m_regions[waScanLine[lx - 1]].touchRegion(-1); |
| } |
| waScanLine = m_regionRas->pixels(ly - 1); |
| for (x = 0; x < lx; x++) { |
| m_regions[waScanLine[x]].touchRegion(-1); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::findBackgroundRegions() |
| { |
| if (!m_regionRas || m_regions.empty()) |
| return; |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| |
| int bgColorIndex = -1; |
| int maxV = 0; |
| for (int i = 0; i < m_colors.count(); i++) { |
| TPixel color = m_colors.at(i); |
| int v = color.r + color.g + color.b; |
| int a = qMin(color.r, qMin(color.g, color.b)); |
| if (a < 230) |
| continue; |
| if (v > maxV) { |
| bgColorIndex = i; |
| maxV = v; |
| } |
| } |
| if (bgColorIndex < 0) { |
| |
| return; |
| } |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.colorIndex == bgColorIndex && region.links.contains(-1)) { |
| region.type = RegionInfo::Background; |
| ; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::findRegionBorders() |
| { |
| if (!m_regionRas) |
| return; |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| delete m_borderRas; |
| m_borderRas = new WorkRaster<unsigned char>(lx, ly); |
| |
| static const int dd[][2] = {{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; |
| |
| |
| for (int y = 0; y < ly; y++) { |
| unsigned short *workScanLine = m_regionRas->pixels(y); |
| unsigned char *borderScanLine = m_borderRas->pixels(y); |
| |
| for (int x = 0; x < lx; x++) { |
| int k = workScanLine[x]; |
| Q_ASSERT(0 <= k && k < m_regions.count()); |
| int isBoundary = 0; |
| for (int j = 0; j < 8; j++) { |
| int x1 = x + dd[j][0], y1 = y + dd[j][1]; |
| if (0 <= x1 && x1 < lx && 0 <= y1 && y1 < ly) { |
| int k1 = m_regionRas->pixels(y1)[x1]; |
| if (k1 != k) { |
| isBoundary = 1; |
| break; |
| } |
| } |
| } |
| borderScanLine[x] = isBoundary; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::erodeRegions() |
| { |
| QTime clock; |
| clock.start(); |
| if (!m_regionRas || !m_borderRas) |
| return; |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| static const int dd[][2] = {{-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}}; |
| |
| for (int iter = 0; iter < 10; iter++) { |
| for (int y = 0; y < ly; y++) { |
| unsigned short *workScanLine = m_regionRas->pixels(y); |
| unsigned char *borderScanLine = m_borderRas->pixels(y); |
| for (int x = 0; x < lx; x++) { |
| if (borderScanLine[x] != iter + 1) |
| continue; |
| int c = workScanLine[x]; |
| for (int j = 0; j < 8; j++) { |
| int x1 = x + dd[j][0], y1 = y + dd[j][1]; |
| if (!(0 <= x1 && x1 < lx && 0 <= y1 && y1 < ly)) |
| continue; |
| int c1 = m_regionRas->pixels(y1)[x1]; |
| int b1 = m_borderRas->pixels(y1)[x1]; |
| if (c1 == c && b1 == 0) |
| m_borderRas->pixels(y1)[x1] = iter + 2; |
| } |
| } |
| } |
| } |
| |
| for (int i = 0; i < m_regions.count(); i++) |
| m_regions[i].boundaries.clear(); |
| for (int y = 0; y < ly; y++) { |
| unsigned short *workScanLine = m_regionRas->pixels(y); |
| unsigned char *borderScanLine = m_borderRas->pixels(y); |
| for (int x = 0; x < lx; x++) { |
| int c = workScanLine[x]; |
| int b = borderScanLine[x]; |
| RegionInfo &r = m_regions[c]; |
| QList<int> &bs = r.boundaries; |
| while (bs.count() <= b) |
| bs.append(0); |
| bs[b]++; |
| if (b == bs.count() - 1) |
| r.pos = QPoint(x, y); |
| if (r.x0 > r.x1) { |
| r.x0 = r.x1 = x; |
| r.y0 = r.y1 = y; |
| } else { |
| if (x < r.x0) |
| r.x0 = x; |
| else if (x > r.x1) |
| r.x1 = x; |
| if (y < r.y0) |
| r.y0 = y; |
| else if (y > r.y1) |
| r.y1 = y; |
| } |
| } |
| } |
| qDebug() << "Erode regions. time = " << clock.elapsed(); |
| } |
| |
| |
| |
| |
| void Naa2TlvConverter::findMainInks() |
| { |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| if (region.isBackground() || region.type == RegionInfo::LargePaint || region.boundaries[0] > 0) |
| continue; |
| double ap2 = 100000.0 * (double)region.pixelCount / pow((double)region.perimeter, 2); |
| if (ap2 > 100) |
| continue; |
| foreach (int c, region.links.keys()) { |
| if (c >= 0 && (m_regions[c].isBackground() || m_regions[c].type == RegionInfo::LargePaint)) { |
| int strength = region.links[c]; |
| if (strength > 50) { |
| m_regions[i].type = RegionInfo::MainInk; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::computeMainInkThickness() |
| { |
| m_inkThickness = 0.0; |
| int largestArea = 0; |
| for (int i = 0; i < m_regions.count(); i++) { |
| if (m_regions[i].type != RegionInfo::MainInk) |
| continue; |
| if (m_regions[i].pixelCount < largestArea) |
| continue; |
| largestArea = m_regions[i].pixelCount; |
| if (i == 55) { |
| int x = 123; |
| } |
| QList<int> &bs = m_regions[i].boundaries; |
| Q_ASSERT(bs[1] > 0); |
| int perimeter = m_regions[i].perimeter; |
| int area = bs[1]; |
| for (int j = 2; j < bs.count() && bs[j] * 2 > bs[1]; j++) |
| area += bs[j]; |
| if (perimeter > 0) { |
| m_inkThickness = 2 * (double)area / (double)perimeter; |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::findLargePaints() |
| { |
| if (!m_regionRas || !m_borderRas || m_regions.empty()) |
| return; |
| QSet<int> largePaintColors; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| QList<int> &bs = region.boundaries; |
| if (bs[0] > 0) { |
| region.type = RegionInfo::LargePaint; |
| largePaintColors.insert(region.colorIndex); |
| } |
| } |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| if (largePaintColors.contains(region.colorIndex)) |
| region.type = RegionInfo::LargePaint; |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::findThinInks() |
| { |
| if (!m_regionRas || !m_borderRas || m_regions.empty()) |
| return; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| QList<int> &bs = region.boundaries; |
| if (bs.count() == 2) |
| region.type = RegionInfo::ThinInk; |
| else if (bs.count() == 3) { |
| continue; |
| if (bs[2] * 5 < bs[1]) |
| region.type = RegionInfo::ThinInk; |
| } else { |
| continue; |
| int k = 1; |
| int s = 0; |
| while (k < bs.count() && s * 100 < region.pixelCount * 90) |
| s += bs[k++]; |
| if (region.pixelCount > 100 && k <= 3) |
| region.type = RegionInfo::ThinInk; |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::findPaints() |
| { |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| foreach (int c, m_regions[i].links.keys()) { |
| if (c >= 0 && m_regions[c].isInk()) { |
| m_regions[i].type = RegionInfo::Paint; |
| break; |
| } |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::findPaints2() |
| { |
| double avThickness = 0.0; |
| int m = 0; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type == RegionInfo::MainInk) { |
| avThickness += region.thickness * region.pixelCount; |
| m += region.pixelCount; |
| } |
| } |
| if (m > 0) { |
| avThickness = avThickness / m; |
| } else { |
| avThickness = 1.5; |
| } |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| |
| if (region.thickness > 0.0) { |
| if (region.thickness < 1.2 * avThickness) |
| region.type = RegionInfo::Ink; |
| else |
| region.type = RegionInfo::Paint; |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::findThinPaints() |
| { |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| QList<int> regions; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (!region.isInk()) |
| continue; |
| if (region.type == RegionInfo::MainInk) |
| continue; |
| |
| int inkBoundary = 0; |
| QMap<int, int>::ConstIterator it; |
| for (it = region.links.begin(); it != region.links.end(); ++it) { |
| int c = it.key(); |
| int linkStrength = it.value(); |
| if (c >= 0 && m_regions[c].isInk()) |
| inkBoundary += linkStrength; |
| } |
| region.inkBoundary = inkBoundary; |
| if (inkBoundary * 100 > region.perimeter * 80) |
| regions.append(i); |
| } |
| |
| foreach (int c, regions) |
| m_regions[c].type = RegionInfo::SmallPaint; |
| } |
| |
| |
| |
| void Naa2TlvConverter::findSuspectInks() |
| { |
| QMultiMap<TUINT32, int> paintColorTable; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (0 == (region.type & RegionInfo::Paint)) |
| continue; |
| TPixel32 color = m_colors[region.colorIndex]; |
| if (color == TPixel32(0, 0, 0)) { |
| int x = 1234; |
| continue; |
| } |
| TUINT32 rawColor = *(TUINT32 *)&color; |
| paintColorTable.insert(rawColor, i); |
| } |
| |
| int count = 0; |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.isInk() && region.links.size() == 2) { |
| int ra = region.links.keys().at(0), rb = region.links.keys().at(1); |
| if (ra >= 0 && rb >= 0) { |
| if (!m_regions[ra].isInk()) |
| qSwap(ra, rb); |
| if (m_regions[ra].isInk() && !m_regions[rb].isInk()) { |
| int sa = region.links[ra]; |
| int sb = region.links[rb]; |
| if (sa > sb) { |
| region.type = RegionInfo::Paint; |
| count++; |
| continue; |
| } |
| } |
| } |
| } |
| |
| if (region.type != RegionInfo::ThinInk) |
| continue; |
| TUINT32 rawColor = *(TUINT32 *)&m_colors[region.colorIndex]; |
| if (paintColorTable.contains(rawColor)) { |
| region.type = RegionInfo::Unknown; |
| count++; |
| } |
| } |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.isInk() && 10 <= region.pixelCount && region.pixelCount < 100) { |
| int lx = region.x1 - region.x0 + 1; |
| int ly = region.y1 - region.y0 + 1; |
| int d = qMax(lx, ly); |
| if (qMin(lx, ly) * 2 > qMax(lx, ly) && region.pixelCount > d * d / 2) { |
| region.type = RegionInfo::Paint; |
| } |
| } |
| |
| if (region.type == RegionInfo::Paint || region.type == RegionInfo::Unknown) { |
| bool isInk = false; |
| if (region.boundaries.at(0) == 0) { |
| if (region.boundaries.count() == 2) |
| isInk = true; |
| else if (region.boundaries.count() == 3) { |
| int b1 = region.boundaries.at(1); |
| int b2 = region.boundaries.at(2); |
| if (b1 * 2 < b2) |
| isInk = true; |
| } |
| } |
| if (isInk) |
| region.type = RegionInfo::Ink; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| void Naa2TlvConverter::assignColorTypes() |
| { |
| |
| if (!m_regionRas || !m_borderRas || m_regions.empty()) |
| return; |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| if (region.type != RegionInfo::Unknown) |
| continue; |
| QList<int> &bs = region.boundaries; |
| if (bs[0] > 0) { |
| region.type = RegionInfo::LargePaint; |
| } else { |
| int b = 0; |
| for (int j = 1; j <= 2 && j < bs.count(); j++) |
| b += bs[j]; |
| if (region.pixelCount > 200 && (region.pixelCount - b) * 10 < region.pixelCount) { |
| region.type = RegionInfo::ThinInk; |
| } |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::addBorderInks() |
| { |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| static const int dd[][2] = {{0, -1}, {-1, 0}, {1, 0}, {0, 1}, {1, 1}, {-1, -1}, {-1, 1}, {1, -1}}; |
| |
| m_syntheticInkRas = new WorkRaster<unsigned char>(lx, ly); |
| for (int i = 0; i < lx * ly; i++) |
| m_syntheticInkRas->pixels(0)[i] = 0; |
| |
| int borderInkColorIndex = m_colors.count(); |
| m_colors.append(TPixel32(255, 0, 0)); |
| |
| RegionInfo borderInkRegion; |
| borderInkRegion.type = RegionInfo::SyntheticInk; |
| borderInkRegion.colorIndex = borderInkColorIndex; |
| int borderInkRegionIndex = m_regions.count(); |
| m_regions.append(borderInkRegion); |
| |
| for (int y = 0; y < ly; y++) { |
| unsigned short *workScanLine = m_regionRas->pixels(y); |
| unsigned char *borderScanLine = m_borderRas->pixels(y); |
| for (int x = 0; x < lx; x++) { |
| int c = workScanLine[x]; |
| if (borderScanLine[x] != 1) |
| continue; |
| if (0 == (m_regions[c].type & RegionInfo::Paint) && m_regions[c].type != RegionInfo::Unknown) |
| continue; |
| |
| |
| bool touchesOtherRegion = false; |
| for (int j = 0; j < 8; j++) { |
| int x1 = x + dd[j][0], y1 = y + dd[j][1]; |
| if (0 <= x1 && x1 < lx && 0 <= y1 && y1 < ly) { |
| int c1 = m_regionRas->pixels(y1)[x1]; |
| |
| if (m_regions[c1].type == RegionInfo::Background) { |
| touchesOtherRegion = true; |
| break; |
| } else if (m_regions[c1].type == RegionInfo::Unknown || (m_regions[c1].type & RegionInfo::Paint) != 0) { |
| |
| |
| if (m_regions[c1].pixelCount < m_regions[c].pixelCount) { |
| touchesOtherRegion = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (touchesOtherRegion) { |
| m_syntheticInkRas->pixels(y)[x] = 1; |
| } |
| } |
| } |
| } |
| |
| |
| |
| void Naa2TlvConverter::measureThickness() |
| { |
| QTime timer; |
| timer.start(); |
| if (!m_regionRas || !m_borderRas) |
| return; |
| unsigned short *regionBuffer = m_regionRas->pixels(); |
| unsigned char *borderBuffer = m_borderRas->pixels(); |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| if (!m_dotRas || m_dotRas->getLx() != lx || m_dotRas->getLy() != ly) { |
| delete m_dotRas; |
| m_dotRas = new WorkRaster<unsigned char>(lx, ly); |
| } |
| memset(m_dotRas->pixels(), 0, lx * ly); |
| |
| |
| unsigned char *dotBuffer = m_dotRas->pixels(); |
| |
| for (int y = 0; y < ly; y++) { |
| for (int x = 0; x < lx; x++) { |
| if (borderBuffer[y * lx + x] != 1) |
| continue; |
| if (dotBuffer[y * lx + x] != 0) |
| continue; |
| int regionId = regionBuffer[y * lx + x]; |
| RegionInfo ®ion = m_regions[regionId]; |
| int type = region.type; |
| if (type == RegionInfo::Background || type == RegionInfo::LargePaint || type == RegionInfo::ThinInk) |
| continue; |
| |
| int thickness = measureThickness(x, y); |
| if (thickness < 1) |
| continue; |
| QMap<int, int>::Iterator it = region.thicknessHistogram.find(thickness); |
| if (it != region.thicknessHistogram.end()) |
| it.value() += 1; |
| else |
| region.thicknessHistogram.insert(thickness, 1); |
| } |
| } |
| |
| for (int i = 0; i < m_regions.count(); i++) { |
| RegionInfo ®ion = m_regions[i]; |
| int type = region.type; |
| if (type == RegionInfo::Background || type == RegionInfo::LargePaint || type == RegionInfo::ThinInk) |
| continue; |
| int thicknessCount = 0; |
| double thickness = 0.0; |
| for (QMap<int, int>::Iterator it = region.thicknessHistogram.begin(); it != region.thicknessHistogram.end(); ++it) { |
| thicknessCount += it.value(); |
| thickness += it.key() * it.value(); |
| } |
| if (thicknessCount > 0) { |
| thickness *= 1.0 / thicknessCount; |
| region.thickness = thickness; |
| } |
| } |
| qDebug() << "measure thickness. time=" << timer.elapsed(); |
| } |
| |
| |
| |
| int Naa2TlvConverter::measureThickness(int x0, int y0) |
| { |
| |
| if (!m_regionRas || !m_borderRas || !m_dotRas) |
| return -1; |
| unsigned short *regionBuffer = m_regionRas->pixels(); |
| unsigned char *borderBuffer = m_borderRas->pixels(); |
| unsigned char *dotBuffer = m_dotRas->pixels(); |
| |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| |
| int k0 = lx * y0 + x0; |
| |
| if (x0 - 1 < 0 || x0 + 1 >= lx || y0 - 1 < 0 || y0 + 1 >= ly) |
| return -1; |
| |
| if (borderBuffer[k0] != 1) |
| return -1; |
| |
| |
| if (dotBuffer[k0] != 0) |
| return -1; |
| |
| int regionId = regionBuffer[k0]; |
| RegionInfo &info = m_regions[regionId]; |
| |
| |
| const int dd[8] = {1, lx + 1, lx, lx - 1, -1, -1 - lx, -lx, 1 - lx}; |
| |
| |
| int a = 0; |
| while (a < 8 && !(regionBuffer[k0 + dd[a]] == regionId && regionBuffer[k0 + dd[(a + 1) % 8]] != regionId)) |
| a++; |
| if (a == 8) { |
| |
| qDebug() << "Isolated point or intern point"; |
| return -1; |
| } |
| int ka = k0 + dd[a]; |
| |
| int b = (a + 2) % 8; |
| while (regionBuffer[k0 + dd[b]] != regionId) |
| b = (b + 1) % 8; |
| |
| int kb = k0 + dd[b]; |
| if (a == b || ((b + 1) % 8) == a) |
| return -1; |
| |
| dotBuffer[k0] = 1; |
| dotBuffer[ka] = 2; |
| dotBuffer[kb] = 3; |
| |
| int c = (b + 1) % 8; |
| bool isThin = false; |
| while (c != a) { |
| if (regionBuffer[k0 + dd[c]] != regionId) { |
| isThin = true; |
| break; |
| } |
| c = (c + 1) % 8; |
| } |
| if (isThin) { |
| |
| return 1; |
| } |
| |
| int baseLength = 3; |
| |
| int d = b; |
| int oldk = k0; |
| int k = kb; |
| int lastd2 = 0; |
| for (int i = 0; i < baseLength; i++) { |
| int x = k % lx; |
| int y = k / lx; |
| if (x <= 1 || x >= lx - 1 || y <= 1 || y >= ly - 1) |
| break; |
| int d2 = (x - x0) * (x - x0) + (y - y0) * (y - y0); |
| if (d2 <= lastd2) |
| break; |
| lastd2 = d2; |
| int d1 = (d + 4) % 8; |
| d1 = (d1 + 1) % 8; |
| while (regionBuffer[k + dd[d1]] != regionId) |
| d1 = (d1 + 1) % 8; |
| Q_ASSERT(regionBuffer[k + dd[d1]] == regionId); |
| oldk = k; |
| k = k + dd[d1]; |
| d = d1; |
| dotBuffer[k] = 4; |
| } |
| |
| |
| int x1 = k % lx; |
| int y1 = k / lx; |
| |
| d = a; |
| oldk = k0; |
| k = ka; |
| lastd2 = 0; |
| for (int i = 0; i < baseLength; i++) { |
| int x = k % lx; |
| int y = k / lx; |
| if (x <= 1 || x >= lx - 1 || y <= 1 || y >= ly - 1) |
| break; |
| int d2 = (x - x0) * (x - x0) + (y - y0) * (y - y0); |
| if (d2 <= lastd2) |
| break; |
| lastd2 = d2; |
| int d1 = (d + 4) % 8; |
| d1 = (d1 + 7) % 8; |
| while (regionBuffer[k + dd[d1]] != regionId) |
| d1 = (d1 + 7) % 8; |
| Q_ASSERT(regionBuffer[k + dd[d1]] == regionId); |
| oldk = k; |
| k = k + dd[d1]; |
| d = d1; |
| dotBuffer[k] = 5; |
| } |
| |
| |
| int x2 = k % lx; |
| int y2 = k / lx; |
| |
| int dx = y2 - y1, dy = x1 - x2; |
| |
| int delta2 = dx * dx + dy * dy; |
| if (delta2 < baseLength * baseLength * 3) |
| return -1; |
| |
| int adx = abs(dx), ady = abs(dy); |
| int sgx = dx > 0 ? 1 : -1, sgy = dy > 0 ? 1 : -1; |
| int thickness = 1; |
| const int maxThickness = 64; |
| for (; thickness < maxThickness; thickness++) { |
| int x, y; |
| if (adx > ady) { |
| x = x0 + thickness * sgx; |
| y = y0 + sgy * (thickness * ady + adx / 2) / adx; |
| } else { |
| y = y0 + thickness * sgy; |
| x = x0 + sgx * (thickness * adx + ady / 2) / ady; |
| } |
| k = y * lx + x; |
| if (0 <= x && x < lx && 0 <= y && y < ly && regionBuffer[k] == regionId) |
| dotBuffer[k] = 6; |
| else |
| break; |
| } |
| return thickness; |
| } |
| |
| |
| |
| TToonzImageP Naa2TlvConverter::makeTlv(bool transparentSyntheticInks) |
| { |
| |
| if (!m_valid || m_colors.empty() || m_regions.empty() || !m_regionRas) |
| return TToonzImageP(); |
| int lx = m_regionRas->getLx(); |
| int ly = m_regionRas->getLy(); |
| TPalette *palette = m_palette; |
| if (!palette) |
| palette = new TPalette(); |
| |
| TRasterCM32P ras(lx, ly); |
| |
| QList<int> styleIds; |
| |
| for (int i = 0; i < m_colors.count() - 1; i++) { |
| TPixel32 color = m_colors.at(i); |
| int styleId = palette->getClosestStyle(color); |
| TColorStyle *cs = styleId < 0 ? 0 : palette->getStyle(styleId); |
| if (cs) { |
| if (cs->getMainColor() != color) |
| cs = 0; |
| } |
| if (cs == 0) { |
| styleId = palette->getPage(0)->addStyle(color); |
| cs = palette->getStyle(styleId); |
| } |
| styleIds.append(styleId); |
| } |
| styleIds.append(0); |
| |
| |
| |
| |
| for (int y = 0; y < ly; y++) { |
| unsigned short *workScanLine = m_regionRas->pixels(y); |
| TPixelCM32 *outScanLine = ras->pixels(y); |
| for (int x = 0; x < lx; x++) { |
| int c = workScanLine[x]; |
| Q_ASSERT(0 <= c && c < m_regions.count()); |
| |
| int color = m_regions[c].colorIndex; |
| Q_ASSERT(0 <= color && color < styleIds.count()); |
| |
| int styleId = styleIds.at(color); |
| |
| RegionInfo::Type type = m_regions.at(c).type; |
| if (type == RegionInfo::Background) |
| outScanLine[x] = TPixelCM32(); |
| else if (type & RegionInfo::Ink) |
| outScanLine[x] = TPixelCM32(styleId, 0, 0); |
| else if (m_syntheticInkRas->pixels(y)[x] == 1) |
| outScanLine[x] = TPixelCM32(transparentSyntheticInks ? 0 : styleId, styleId, 0); |
| else |
| outScanLine[x] = TPixelCM32(0, styleId, 255); |
| } |
| } |
| |
| TToonzImageP ti = new TToonzImage(ras, ras->getBounds()); |
| ti->setPalette(palette); |
| ti->setDpi(72, 72); |
| |
| return ti; |
| } |
| |
| |
| |
| TVectorImageP Naa2TlvConverter::vectorize(const TToonzImageP &ti) |
| { |
| CenterlineConfiguration conf; |
| if (!ti) |
| return TVectorImageP(); |
| TPalette *palette = ti->getPalette(); |
| |
| VectorizerCore vc; |
| |
| TAffine dpiAff; |
| double factor = Stage::inch; |
| double dpix = factor / 72, dpiy = factor / 72; |
| |
| ti->getDpi(dpix, dpiy); |
| TPointD center = ti->getRaster()->getCenterD(); |
| |
| if (dpix != 0.0 && dpiy != 0.0) |
| dpiAff = TScale(factor / dpix, factor / dpiy); |
| factor = norm(dpiAff * TPointD(1, 0)); |
| |
| conf.m_affine = dpiAff * TTranslation(-center); |
| conf.m_thickScale = factor; |
| conf.m_leaveUnpainted = false; |
| conf.m_makeFrame = true; |
| conf.m_penalty = 0.0; |
| conf.m_despeckling = 0; |
| |
| TImageP img(ti.getPointer()); |
| TVectorImageP vi = vc.vectorize(img, conf, palette); |
| vi->setPalette(palette); |
| return vi; |
| } |
| |