// The following contains classes and prototypes for most code of centerline vectorization.
#include "tcenterlinevectP.h"
// TnzLib includes
#include "toonz/tcenterlinevectorizer.h"
#include "toonz/Naa2TlvConverter.h"
// TnzCore includes
#include "tpalette.h"
#include "tcolorstyles.h"
#include "trastercm.h"
#include "ttoonzimage.h"
#include "trasterimage.h"
#include "tvectorimage.h"
#include "tgeometry.h"
#include "tstroke.h"
#include "tropcm.h"
// STD includes
#include <vector>
#include <list>
#include <queue>
#include <map>
#include <functional>
#include <algorithm>
#include <math.h>
#include <assert.h>
//==========================================================================
//*********************************
//* Further miscellaneous *
//*********************************
//--------------------------------------------------------------------------
//Reduces strokes of image by currConfig->m_reduceThicknessRatio/100
inline void reduceThickness(TVectorImageP image, double thicknessRatio)
{
thicknessRatio *= 0.01;
unsigned int i;
int j;
for (i = 0; i < image->getStrokeCount(); ++i) {
for (j = 0; j < image->getStroke(i)->getControlPointCount(); ++j) {
TThickPoint newCP = image->getStroke(i)->getControlPoint(j);
newCP.thick *= thicknessRatio;
image->getStroke(i)->setControlPoint(j, newCP);
}
}
}
//========================================================================
//Delete Skeleton graphs and list
inline void deleteSkeletonList(SkeletonList *skeleton)
{
unsigned int i;
for (i = 0; i < skeleton->size(); ++i)
delete (*skeleton)[i];
delete skeleton;
}
//========================================================================
TVectorImageP copyStrokes(std::vector<TStroke *> &strokes)
{
unsigned int i;
TVectorImageP result = new TVectorImage;
for (i = 0; i < strokes.size(); ++i)
if (strokes[i]->getStyle() >= 0)
result->addStroke(strokes[i]);
return result;
}
//========================================================================
inline TThickPoint randomized(const TThickPoint &P)
{
TThickPoint Q = P;
Q.x += ((((double)rand()) / RAND_MAX) - 0.5) * 0.1;
Q.y += ((((double)rand()) / RAND_MAX) - 0.5) * 0.1;
return Q;
}
//------------------------------------------------------------------------
//Give stroke extremities a random shake. May help for region computing.
void randomizeExtremities(TVectorImageP vi)
{
unsigned int i;
for (i = 0; i < vi->getStrokeCount(); ++i) {
TThickPoint P = vi->getStroke(i)->getControlPoint(0);
vi->getStroke(i)->setControlPoint(0, randomized(P));
int n = vi->getStroke(i)->getControlPointCount();
P = vi->getStroke(i)->getControlPoint(n - 1);
vi->getStroke(i)->setControlPoint(n - 1, randomized(P));
}
}
//========================================================================
//Looped frame
inline void addFrameStrokes(TVectorImageP vi, TRasterP ras, TPalette *palette)
{
const double epsThick = 1.0;
TStroke *frameStroke;
std::vector<TThickPoint> CPs;
CPs.push_back(TThickPoint(0, 0, epsThick));
CPs.push_back(TThickPoint(ras->getLx() / 2.0, 0, epsThick));
CPs.push_back(TThickPoint(ras->getLx(), 0, epsThick));
CPs.push_back(TThickPoint(ras->getLx(), 0, epsThick));
CPs.push_back(TThickPoint(ras->getLx(), 0, epsThick));
CPs.push_back(TThickPoint(ras->getLx(), ras->getLy() / 2.0, epsThick));
CPs.push_back(TThickPoint(ras->getLx(), ras->getLy(), epsThick));
CPs.push_back(TThickPoint(ras->getLx(), ras->getLy(), epsThick));
CPs.push_back(TThickPoint(ras->getLx(), ras->getLy(), epsThick));
CPs.push_back(TThickPoint(ras->getLx() / 2.0, ras->getLy(), epsThick));
CPs.push_back(TThickPoint(0, ras->getLy(), epsThick));
CPs.push_back(TThickPoint(0, ras->getLy(), epsThick));
CPs.push_back(TThickPoint(0, ras->getLy(), epsThick));
CPs.push_back(TThickPoint(0, ras->getLy() / 2.0, epsThick));
CPs.push_back(TThickPoint(0, 0, epsThick));
frameStroke = new TStroke(CPs);
frameStroke->setStyle(0);
frameStroke->setSelfLoop(true);
vi->addStroke(frameStroke);
}
//========================================================================
//Returns if input stroke is edge boundary of an ink-filled region
bool VectorizerCore::isInkRegionEdge(TStroke *stroke)
{
return stroke->getFlag(SkeletonArc::SS_OUTLINE);
}
//Similar as above
bool VectorizerCore::isInkRegionEdgeReversed(TStroke *stroke)
{
return stroke->getFlag(SkeletonArc::SS_OUTLINE_REVERSED);
}
void VectorizerCore::clearInkRegionFlags(TVectorImageP vi)
{
int flag = SkeletonArc::SS_OUTLINE | SkeletonArc::SS_OUTLINE_REVERSED;
for (int i = 0; i < (int)vi->getStrokeCount(); i++)
vi->getStroke(i)->setFlag(flag, false);
}
//==========================================================================
//************************
//* Vectorizer Main *
//************************
//--------------------------------------------------------------------------
TVectorImageP VectorizerCore::centerlineVectorize(
TImageP &image, const CenterlineConfiguration &configuration, TPalette *palette)
{
TRasterImageP ri = image;
TToonzImageP ti = image;
TRasterP ras;
if (ri)
ras = ri->getRaster();
else {
ras = ti->getRaster()->clone();
TRop::expandPaint(ras);
}
if (configuration.m_naaSource) {
if (TRaster32P ras32 = ras) {
Naa2TlvConverter converter;
converter.process(ras32);
converter.setPalette(palette);
if (ti = converter.makeTlv(true)) // Transparent synthetic inks
{
image = ti;
ras = ti->getRaster();
TRop::expandPaint(ras);
}
}
}
VectorizerCoreGlobals globals;
globals.currConfig = &configuration;
Contours polygons;
polygonize(ras, polygons, globals);
//Most time-consuming part of vectorization, 'this' is passed to inform of partial progresses
SkeletonList *skeletons = skeletonize(polygons, this, globals);
if (isCanceled()) {
//Clean and return 0 at cancel command
deleteSkeletonList(skeletons);
return TVectorImageP();
}
organizeGraphs(skeletons, globals);
//junctionRecovery(polygons); //Da' problemi per maxThickness<inf... sarebbe da rendere compatibile
std::vector<TStroke *> sortibleResult;
TVectorImageP result;
calculateSequenceColors(ras, globals); //Extract stroke colors here
conversionToStrokes(sortibleResult, globals);
applyStrokeColors(sortibleResult, ras, palette, globals); //Strokes get sorted here
result = copyStrokes(sortibleResult);
//Further misc adjustments
if (globals.currConfig->m_thicknessRatio < 100)
reduceThickness(result, configuration.m_thicknessRatio);
if (globals.currConfig->m_maxThickness == 0.0)
for (unsigned int i = 0; i < result->getStrokeCount(); ++i)
result->getStroke(i)->setSelfLoop(true);
if (globals.currConfig->m_makeFrame)
addFrameStrokes(result, ras, palette);
//randomizeExtremities(result); //Cuccio random - non serve...
deleteSkeletonList(skeletons);
return result;
}