2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
|
|
|
|
#include "toonz/tcenterlinevectorizer.h"
|
|
|
|
|
|
|
|
// TnzCore includes
|
|
|
|
#include "tsystem.h"
|
|
|
|
#include "tstopwatch.h"
|
|
|
|
#include "tpalette.h"
|
|
|
|
#include "trastercm.h"
|
|
|
|
#include "ttoonzimage.h"
|
|
|
|
#include "tregion.h"
|
|
|
|
#include "tstroke.h"
|
|
|
|
#include "trasterimage.h"
|
|
|
|
#include "tmathutil.h"
|
|
|
|
|
|
|
|
// tcg includes
|
|
|
|
#include "tcg/tcg_numeric_ops.h"
|
|
|
|
|
|
|
|
// STD includes
|
2016-04-18 21:52:53 +12:00
|
|
|
#include <cmath>
|
2016-03-19 06:57:51 +13:00
|
|
|
#include <functional>
|
|
|
|
|
|
|
|
#undef DEBUG
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
struct ControlPoint {
|
2016-06-15 18:43:10 +12:00
|
|
|
TStroke *m_stroke;
|
|
|
|
int m_index;
|
|
|
|
ControlPoint(TStroke *stroke, int index) : m_stroke(stroke), m_index(index) {}
|
|
|
|
TPointD getPoint() const { return m_stroke->getControlPoint(m_index); }
|
|
|
|
void setPoint(const TPointD &p) {
|
|
|
|
TThickPoint point = m_stroke->getControlPoint(m_index);
|
|
|
|
point.x = p.x;
|
|
|
|
point.y = p.y;
|
|
|
|
m_stroke->setControlPoint(m_index, point);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
class Node;
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
class DataPixel {
|
2016-03-19 06:57:51 +13:00
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
TPoint m_pos;
|
|
|
|
int m_value;
|
|
|
|
bool m_ink;
|
|
|
|
Node *m_node;
|
|
|
|
DataPixel() : m_value(0), m_ink(false), m_node(0) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
2016-04-15 17:11:23 +12:00
|
|
|
#ifdef _WIN32
|
2016-03-19 06:57:51 +13:00
|
|
|
template class DV_EXPORT_API TSmartPointerT<TRasterT<DataPixel>>;
|
|
|
|
#endif
|
|
|
|
typedef TRasterPT<DataPixel> DataRasterP;
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
class Junction;
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
class Node {
|
2016-03-19 06:57:51 +13:00
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
Node *m_other;
|
|
|
|
DataPixel *m_pixel;
|
|
|
|
Node *m_prev, *m_next;
|
|
|
|
Junction *m_junction;
|
2016-03-19 06:57:51 +13:00
|
|
|
#ifdef DEBUG
|
2016-06-15 18:43:10 +12:00
|
|
|
bool m_flag;
|
2016-03-19 06:57:51 +13:00
|
|
|
#endif
|
2016-06-15 18:43:10 +12:00
|
|
|
bool m_visited;
|
|
|
|
Node()
|
|
|
|
: m_pixel(0)
|
|
|
|
, m_prev(0)
|
|
|
|
, m_next(0)
|
|
|
|
, m_junction(0)
|
|
|
|
,
|
2016-03-19 06:57:51 +13:00
|
|
|
#ifdef DEBUG
|
2016-06-15 18:43:10 +12:00
|
|
|
m_flag(false)
|
|
|
|
,
|
2016-03-19 06:57:51 +13:00
|
|
|
#endif
|
2016-06-15 18:43:10 +12:00
|
|
|
m_visited(false) {
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
|
|
|
class ProtoStroke;
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
class Junction {
|
2016-03-19 06:57:51 +13:00
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
TThickPoint m_center;
|
|
|
|
std::deque<Node *> m_nodes;
|
|
|
|
int m_junctionOrder;
|
|
|
|
std::vector<ProtoStroke *> m_protoStrokes;
|
|
|
|
bool m_locked;
|
|
|
|
Junction()
|
|
|
|
: m_center()
|
|
|
|
, m_nodes()
|
|
|
|
, m_junctionOrder(0)
|
|
|
|
, m_protoStrokes()
|
|
|
|
, m_locked(false) {}
|
|
|
|
bool isConvex();
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
class ProtoStroke {
|
2016-03-19 06:57:51 +13:00
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
TPointD m_startDirection, m_endDirection;
|
|
|
|
Junction *m_startJunction, *m_endJunction;
|
|
|
|
std::deque<TThickPoint> m_points;
|
|
|
|
ProtoStroke()
|
|
|
|
: m_points()
|
|
|
|
, m_startDirection()
|
|
|
|
, m_endDirection()
|
|
|
|
, m_startJunction(0)
|
|
|
|
, m_endJunction(0) {}
|
|
|
|
ProtoStroke(std::deque<TThickPoint>::iterator it_b,
|
|
|
|
std::deque<TThickPoint>::iterator it_e)
|
|
|
|
: m_points(it_b, it_e)
|
|
|
|
, m_startDirection()
|
|
|
|
, m_endDirection()
|
|
|
|
, m_startJunction(0)
|
|
|
|
, m_endJunction(0) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-07-14 00:05:06 +12:00
|
|
|
static double computeDistance2(Node *na, Node *nb) {
|
2016-06-15 18:43:10 +12:00
|
|
|
assert(na->m_pixel);
|
|
|
|
assert(nb->m_pixel);
|
|
|
|
TPointD d = convert(na->m_pixel->m_pos - nb->m_pixel->m_pos);
|
|
|
|
return d * d;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-07-14 00:05:06 +12:00
|
|
|
static void renormalizeImage(TVectorImage *vi) {
|
2016-06-15 18:43:10 +12:00
|
|
|
int i, j;
|
|
|
|
int n = vi->getStrokeCount();
|
|
|
|
std::vector<ControlPoint> points;
|
|
|
|
points.reserve(n * 2);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
TStroke *stroke = vi->getStroke(i);
|
|
|
|
int m = stroke->getControlPointCount();
|
|
|
|
if (m > 0) {
|
|
|
|
if (m == 1)
|
|
|
|
points.push_back(ControlPoint(stroke, 0));
|
|
|
|
else {
|
|
|
|
points.push_back(ControlPoint(stroke, 0));
|
|
|
|
points.push_back(ControlPoint(stroke, m - 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int count = points.size();
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
ControlPoint &pi = points[i];
|
|
|
|
TPointD posi = pi.getPoint();
|
|
|
|
TPointD center = posi;
|
|
|
|
std::vector<int> neighbours;
|
|
|
|
neighbours.push_back(i);
|
|
|
|
for (j = i + 1; j < count; j++) {
|
|
|
|
TPointD posj = points[j].getPoint();
|
|
|
|
double d = tdistance(posj, posi);
|
|
|
|
if (d < 0.01) {
|
|
|
|
neighbours.push_back(j);
|
|
|
|
center += posj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int m = neighbours.size();
|
|
|
|
if (m == 1) continue;
|
|
|
|
center = center * (1.0 / m);
|
|
|
|
for (j = 0; j < m; j++) points[neighbours[j]].setPoint(center);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
class OutlineVectorizer {
|
|
|
|
TPalette *m_palette;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
public:
|
2016-06-15 18:43:10 +12:00
|
|
|
TRasterP m_src;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
OutlineConfiguration m_configuration;
|
|
|
|
DataRasterP m_dataRaster;
|
|
|
|
std::vector<std::pair<int, DataRasterP>> m_dataRasterArray;
|
|
|
|
TVectorImageP m_vimage;
|
|
|
|
std::vector<Node *> m_nodes;
|
|
|
|
std::list<std::vector<TThickPoint>> m_protoOutlines;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
std::vector<Junction *> m_junctions;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
OutlineVectorizer(const OutlineConfiguration &configuration,
|
|
|
|
TPalette *palette)
|
|
|
|
: m_configuration(configuration), m_palette(palette) {}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
~OutlineVectorizer();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void traceOutline(Node *initialNode);
|
|
|
|
void createOutlineStrokes();
|
|
|
|
void makeDataRaster(const TRasterP &src);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
Node *findOtherSide(Node *node);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void clearNodes();
|
|
|
|
Node *createNode(DataPixel *pix);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void clearJunctions();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void init();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void link(DataPixel *pix, DataPixel *from, DataPixel *to);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
TPoint computeGradient(DataPixel *pix) {
|
|
|
|
assert(m_dataRaster);
|
|
|
|
const int wrap = m_dataRaster->getWrap();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
TPoint g(0, 0);
|
|
|
|
int n, s, w, e, nw, sw, ne, se;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
w = pix[-1].m_value;
|
|
|
|
nw = pix[-1 + wrap].m_value;
|
|
|
|
sw = pix[-1 - wrap].m_value;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
e = pix[+1].m_value;
|
|
|
|
ne = pix[+1 + wrap].m_value;
|
|
|
|
se = pix[+1 - wrap].m_value;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
n = pix[+wrap].m_value;
|
|
|
|
s = pix[-wrap].m_value;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
g.y = -sw + ne - se + nw + 2 * (n - s);
|
|
|
|
g.x = -sw + ne + se - nw + 2 * (e - w);
|
|
|
|
return g;
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
private:
|
2016-06-15 18:43:10 +12:00
|
|
|
// not implemented
|
|
|
|
OutlineVectorizer(const OutlineVectorizer &);
|
|
|
|
OutlineVectorizer &operator=(const OutlineVectorizer &);
|
2016-03-19 06:57:51 +13:00
|
|
|
};
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
OutlineVectorizer::~OutlineVectorizer() {
|
|
|
|
m_protoOutlines.clear();
|
|
|
|
clearNodes();
|
|
|
|
clearJunctions();
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::init() {
|
|
|
|
int y;
|
|
|
|
|
|
|
|
DataRasterP dataRaster = m_dataRaster;
|
|
|
|
const int wrap = dataRaster->getWrap();
|
|
|
|
const int delta[] = {-wrap - 1, -wrap, -wrap + 1, 1,
|
|
|
|
wrap + 1, wrap, wrap - 1, -1};
|
|
|
|
|
|
|
|
for (y = 1; y < dataRaster->getLy() - 1; y++) {
|
|
|
|
DataPixel *pix = dataRaster->pixels(y);
|
|
|
|
DataPixel *endPix = pix + dataRaster->getLx() - 1;
|
|
|
|
pix++;
|
|
|
|
for (pix++; pix < endPix; ++pix) {
|
|
|
|
if ((pix->m_ink == false) || (pix[-wrap].m_ink && pix[wrap].m_ink &&
|
|
|
|
pix[-1].m_ink && pix[1].m_ink))
|
|
|
|
continue;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
|
|
if (pix[delta[i]].m_ink && pix[delta[(i + 1) & 0x7]].m_ink == false)
|
|
|
|
break;
|
|
|
|
int start = i;
|
|
|
|
if (i == 8) continue; // punto isolato
|
|
|
|
for (;;) {
|
|
|
|
int j = (i + 1) & 0x7;
|
|
|
|
assert(i < 8 && pix[delta[i]].m_ink);
|
|
|
|
assert(j < 8 && pix[delta[j]].m_ink == false);
|
|
|
|
do
|
|
|
|
j = (j + 1) & 0x7;
|
|
|
|
while (pix[delta[j]].m_ink == false);
|
|
|
|
assert(j < 8 && pix[delta[j]].m_ink);
|
|
|
|
if (((i + 2) & 0x7) != j || (i & 1) == 0) {
|
|
|
|
// il bianco comprende anche un fianco
|
|
|
|
link(pix, pix + delta[i], pix + delta[j]);
|
|
|
|
}
|
|
|
|
i = j;
|
|
|
|
assert(i < 8);
|
|
|
|
while (pix[delta[(i + 1) & 0x7]].m_ink) i = (i + 1) & 0x7;
|
|
|
|
assert(i < 8 && pix[delta[i]].m_ink);
|
|
|
|
assert(pix[delta[(i + 1) & 0x7]].m_ink == false);
|
|
|
|
if (i == start) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
Node *OutlineVectorizer::createNode(DataPixel *pix) {
|
|
|
|
Node *node = new Node();
|
|
|
|
node->m_pixel = pix;
|
|
|
|
node->m_other = pix->m_node;
|
|
|
|
pix->m_node = node;
|
|
|
|
m_nodes.push_back(node);
|
|
|
|
return node;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::clearNodes() {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < (int)m_nodes.size(); i++) delete m_nodes[i];
|
|
|
|
m_nodes.clear();
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::clearJunctions() {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < (int)m_junctions.size(); i++) delete m_junctions[i];
|
|
|
|
m_junctions.clear();
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::link(DataPixel *pix, DataPixel *srcPix,
|
|
|
|
DataPixel *dstPix) {
|
|
|
|
Node *srcNode = 0, *dstNode = 0, *node = 0;
|
|
|
|
Node *tmp;
|
|
|
|
for (tmp = pix->m_node; tmp; tmp = tmp->m_other) {
|
|
|
|
if (tmp->m_pixel == 0) continue;
|
|
|
|
if (tmp->m_prev && tmp->m_prev->m_pixel == srcPix) {
|
|
|
|
assert(srcNode == 0);
|
|
|
|
if (node) {
|
|
|
|
assert(node->m_next->m_pixel == dstPix);
|
|
|
|
assert(node->m_prev == 0);
|
|
|
|
node->m_prev = tmp->m_prev;
|
|
|
|
tmp->m_prev->m_next = node;
|
|
|
|
tmp->m_next = tmp->m_prev = 0;
|
|
|
|
tmp->m_pixel = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(tmp->m_next == 0);
|
|
|
|
srcNode = tmp->m_prev;
|
|
|
|
node = tmp;
|
|
|
|
}
|
|
|
|
if (tmp->m_next && tmp->m_next->m_pixel == dstPix) {
|
|
|
|
assert(dstNode == 0);
|
|
|
|
if (node) {
|
|
|
|
assert(node->m_prev->m_pixel == srcPix);
|
|
|
|
assert(node->m_next == 0);
|
|
|
|
node->m_next = tmp->m_next;
|
|
|
|
tmp->m_next->m_prev = node;
|
|
|
|
tmp->m_next = tmp->m_prev = 0;
|
|
|
|
tmp->m_pixel = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
assert(tmp->m_prev == 0);
|
|
|
|
dstNode = tmp->m_next;
|
|
|
|
node = tmp;
|
|
|
|
}
|
|
|
|
}
|
2020-11-20 19:17:37 +13:00
|
|
|
if (!node) node = createNode(pix);
|
2016-06-15 18:43:10 +12:00
|
|
|
if (!srcNode) srcNode = createNode(srcPix);
|
|
|
|
if (!dstNode) dstNode = createNode(dstPix);
|
|
|
|
|
|
|
|
if (!node->m_next) {
|
|
|
|
node->m_next = dstNode;
|
|
|
|
assert(dstNode->m_prev == 0);
|
|
|
|
dstNode->m_prev = node;
|
|
|
|
}
|
|
|
|
if (!node->m_prev) {
|
|
|
|
node->m_prev = srcNode;
|
|
|
|
assert(srcNode->m_next == 0);
|
|
|
|
srcNode->m_next = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(node->m_next == dstNode);
|
|
|
|
assert(node->m_prev == srcNode);
|
|
|
|
assert(dstNode->m_prev == node);
|
|
|
|
assert(srcNode->m_next == node);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::traceOutline(Node *initialNode) {
|
|
|
|
Node *startNode = initialNode;
|
|
|
|
Node *node;
|
|
|
|
do {
|
|
|
|
if (!startNode) break;
|
|
|
|
node = findOtherSide(startNode);
|
|
|
|
if (!node) break;
|
|
|
|
|
|
|
|
double startDist2 = computeDistance2(startNode, node);
|
|
|
|
if (startDist2 > 0.1) break;
|
|
|
|
|
|
|
|
startNode = startNode->m_next;
|
|
|
|
} while (startNode != initialNode);
|
|
|
|
|
|
|
|
if (!startNode) return;
|
|
|
|
node = startNode;
|
|
|
|
std::vector<TThickPoint> points;
|
|
|
|
do {
|
|
|
|
node = node->m_next;
|
|
|
|
if (!node) break;
|
|
|
|
node->m_visited = true;
|
|
|
|
points.push_back(TThickPoint(convert(node->m_pixel->m_pos), 0));
|
|
|
|
} while (node != startNode);
|
|
|
|
m_protoOutlines.push_back(points);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
Node *OutlineVectorizer::findOtherSide(Node *node) {
|
|
|
|
DataPixel *pix = node->m_pixel;
|
|
|
|
|
|
|
|
TPoint dir = -computeGradient(pix);
|
|
|
|
if (dir == TPoint(0, 0)) return 0;
|
|
|
|
TPoint d1(tsign(dir.x), 0), d2(0, tsign(dir.y));
|
|
|
|
int num = abs(dir.y), den = abs(dir.x);
|
|
|
|
if (num > den) {
|
2018-08-30 20:18:43 +12:00
|
|
|
std::swap(d1, d2);
|
|
|
|
std::swap(num, den);
|
2016-06-15 18:43:10 +12:00
|
|
|
}
|
|
|
|
TPoint pos = pix->m_pos;
|
|
|
|
int i;
|
|
|
|
for (i = 0;; i++) {
|
|
|
|
TPoint q(pos.x + d1.x * i + d2.x * num * i / den,
|
|
|
|
pos.y + d1.y * i + d2.y * num * i / den);
|
|
|
|
DataPixel *nextPix = m_dataRaster->pixels(q.y) + q.x;
|
|
|
|
if (nextPix->m_ink == false) break;
|
|
|
|
pix = nextPix;
|
|
|
|
}
|
|
|
|
assert(pix);
|
|
|
|
if (!pix->m_node) {
|
|
|
|
const int wrap = m_dataRaster->getWrap();
|
|
|
|
if (pix[-1].m_node)
|
|
|
|
pix--;
|
|
|
|
else if (pix[1].m_node)
|
|
|
|
pix++;
|
|
|
|
else if (pix[wrap].m_node)
|
|
|
|
pix += wrap;
|
|
|
|
else if (pix[-wrap].m_node)
|
|
|
|
pix -= wrap;
|
|
|
|
else {
|
|
|
|
assert(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!pix->m_node) return 0;
|
2020-11-20 19:17:37 +13:00
|
|
|
Node *q = pix->m_node;
|
2016-06-15 18:43:10 +12:00
|
|
|
while (q->m_pixel == 0 && q->m_other) q = q->m_other;
|
|
|
|
assert(q && q->m_pixel == pix);
|
|
|
|
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
if (!q->m_prev) break;
|
|
|
|
q = q->m_prev;
|
|
|
|
}
|
|
|
|
|
|
|
|
Node *best = q;
|
|
|
|
double bestDist2 = computeDistance2(q, node);
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
|
|
q = q->m_next;
|
|
|
|
if (!q) break;
|
|
|
|
double dist2 = computeDistance2(q, node);
|
|
|
|
if (dist2 < bestDist2) {
|
|
|
|
bestDist2 = dist2;
|
|
|
|
best = q;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return best;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::createOutlineStrokes() {
|
|
|
|
m_vimage->enableRegionComputing(true, false);
|
|
|
|
int j;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
for (j = 0; j < (int)m_nodes.size(); j++) {
|
|
|
|
Node *node = m_nodes[j];
|
|
|
|
if (node->m_pixel == 0 || node->m_visited) continue;
|
|
|
|
traceOutline(node);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
#ifdef DEBUG
|
2016-06-15 18:43:10 +12:00
|
|
|
for (j = 0; j < (int)m_nodes.size(); j++) {
|
|
|
|
Node *node = m_nodes[j];
|
|
|
|
if (node->m_pixel == 0 || node->m_flag) continue;
|
|
|
|
outputNodes(node);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
#endif
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
std::list<std::vector<TThickPoint>>::iterator it_outlines =
|
|
|
|
m_protoOutlines.begin();
|
2020-04-12 17:19:20 +12:00
|
|
|
for (; it_outlines != m_protoOutlines.end(); it_outlines++) {
|
2016-06-15 18:43:10 +12:00
|
|
|
if (it_outlines->size() > 3) {
|
|
|
|
std::vector<TThickPoint> points;
|
|
|
|
std::vector<TThickPoint>::iterator it;
|
|
|
|
|
|
|
|
if (it_outlines->size() > 10) {
|
|
|
|
it = it_outlines->begin() + 1;
|
|
|
|
for (;;) {
|
|
|
|
// Baco: Ricontrolla l'if seguente - in alcuni casi va fuori bounds...
|
|
|
|
if ((int)it_outlines->size() <= m_configuration.m_smoothness + 1)
|
|
|
|
break;
|
|
|
|
if (it >= it_outlines->end() - (m_configuration.m_smoothness + 1))
|
|
|
|
break;
|
|
|
|
for (j = 0; j < m_configuration.m_smoothness; j++)
|
|
|
|
it = it_outlines->erase(it);
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
points.push_back(it_outlines->front());
|
|
|
|
it = it_outlines->begin();
|
|
|
|
TThickPoint old = *it;
|
|
|
|
++it;
|
|
|
|
for (; it != it_outlines->end(); ++it) {
|
|
|
|
TThickPoint point((1 / 2.0) * (*it + old));
|
|
|
|
points.push_back(point);
|
|
|
|
old = *it;
|
|
|
|
}
|
|
|
|
|
|
|
|
points.push_back(it_outlines->back());
|
|
|
|
points.push_back(it_outlines->front());
|
|
|
|
|
|
|
|
TStroke *stroke =
|
|
|
|
TStroke::interpolate(points, m_configuration.m_interpolationError);
|
|
|
|
stroke->setStyle(m_configuration.m_strokeStyleId);
|
|
|
|
stroke->setSelfLoop();
|
|
|
|
m_vimage->addStroke(stroke);
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline int colorDistance2(const TPixel32 &c0, const TPixel32 &c1) {
|
|
|
|
return ((c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) +
|
|
|
|
(c0.b - c1.b) * (c0.b - c1.b));
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
#define MAX_TOLERANCE 20
|
|
|
|
|
|
|
|
#include "tcolorstyles.h"
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void OutlineVectorizer::makeDataRaster(const TRasterP &src) {
|
|
|
|
m_vimage = new TVectorImage();
|
|
|
|
if (!src) return;
|
|
|
|
m_src = src;
|
|
|
|
|
|
|
|
clearNodes();
|
|
|
|
clearJunctions();
|
|
|
|
|
|
|
|
int x, y, ii = 0;
|
|
|
|
TRaster32P srcRGBM = (TRaster32P)m_src;
|
|
|
|
TRasterCM32P srcCM = (TRasterCM32P)m_src;
|
|
|
|
TRasterGR8P srcGR = (TRasterGR8P)m_src;
|
|
|
|
|
|
|
|
// Inizializzo DataRasterP per i casi in cui si ha un TRaster32P, un
|
|
|
|
// TRasterGR8P o un TRasterCM32P molto grande
|
|
|
|
DataRasterP dataRaster(m_src->getSize().lx + 2, m_src->getSize().ly + 2);
|
|
|
|
if (srcRGBM || srcGR ||
|
|
|
|
(srcCM && srcCM->getLx() * srcCM->getLy() > 5000000)) {
|
|
|
|
int ly = dataRaster->getLy();
|
|
|
|
int lx = dataRaster->getLx();
|
|
|
|
int wrap = dataRaster->getWrap();
|
|
|
|
DataPixel *dataPix0 = dataRaster->pixels(0);
|
|
|
|
DataPixel *dataPix1 = dataRaster->pixels(0) + m_src->getLx() + 1;
|
|
|
|
for (y = 0; y < ly; y++, dataPix0 += wrap, dataPix1 += wrap) {
|
|
|
|
dataPix0->m_pos.x = 0;
|
|
|
|
dataPix1->m_pos.x = lx - 1;
|
|
|
|
dataPix0->m_pos.y = dataPix1->m_pos.y = y;
|
|
|
|
dataPix0->m_value = dataPix1->m_value = 0;
|
|
|
|
dataPix0->m_ink = dataPix1->m_ink = false;
|
|
|
|
dataPix0->m_node = dataPix1->m_node = 0;
|
|
|
|
}
|
|
|
|
dataPix0 = dataRaster->pixels(0);
|
|
|
|
dataPix1 = dataRaster->pixels(ly - 1);
|
|
|
|
for (x = 0; x < lx; x++, dataPix0++, dataPix1++) {
|
|
|
|
dataPix0->m_pos.x = dataPix1->m_pos.x = x;
|
|
|
|
dataPix0->m_pos.y = 0;
|
|
|
|
dataPix1->m_pos.y = ly - 1;
|
|
|
|
dataPix0->m_value = dataPix1->m_value = 0;
|
|
|
|
dataPix0->m_ink = dataPix1->m_ink = false;
|
|
|
|
dataPix0->m_node = dataPix1->m_node = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (srcRGBM) {
|
|
|
|
assert(m_palette);
|
|
|
|
int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
|
2020-11-20 19:17:37 +13:00
|
|
|
if (!inkId || m_configuration.m_inkColor !=
|
|
|
|
m_palette->getStyle(inkId)->getMainColor()) {
|
2016-06-15 18:43:10 +12:00
|
|
|
inkId = m_palette->getStyleCount();
|
|
|
|
m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
|
|
|
|
m_palette->setStyle(inkId, m_configuration.m_inkColor);
|
|
|
|
}
|
|
|
|
assert(inkId);
|
|
|
|
|
|
|
|
m_dataRasterArray.push_back(std::pair<int, DataRasterP>(inkId, dataRaster));
|
|
|
|
int maxDistance2 =
|
|
|
|
m_configuration.m_threshold * m_configuration.m_threshold;
|
|
|
|
|
|
|
|
for (y = 0; y < m_src->getLy(); y++) {
|
|
|
|
TPixel32 *inPix = srcRGBM->pixels(y);
|
|
|
|
TPixel32 *inEndPix = inPix + srcRGBM->getLx();
|
|
|
|
DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
|
|
|
|
x = 0;
|
|
|
|
while (inPix < inEndPix) {
|
|
|
|
*dataPix = DataPixel();
|
|
|
|
int distance2 = colorDistance2(m_configuration.m_inkColor, *inPix);
|
|
|
|
|
|
|
|
if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
|
|
|
|
x == m_src->getLx() - 1 || inPix->m == 0) {
|
|
|
|
dataPix->m_value = 255;
|
|
|
|
dataPix->m_ink = false;
|
|
|
|
} else {
|
|
|
|
dataPix->m_value = (inPix->r + 2 * inPix->g + inPix->b) >> 2;
|
|
|
|
dataPix->m_ink = (distance2 < maxDistance2);
|
|
|
|
}
|
|
|
|
dataPix->m_pos.x = x++;
|
|
|
|
dataPix->m_pos.y = y;
|
|
|
|
dataPix->m_node = 0;
|
|
|
|
inPix++;
|
|
|
|
dataPix++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (srcGR) {
|
|
|
|
assert(m_palette);
|
|
|
|
int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
|
2020-11-20 19:17:37 +13:00
|
|
|
if (!inkId || m_configuration.m_inkColor !=
|
|
|
|
m_palette->getStyle(inkId)->getMainColor()) {
|
2016-06-15 18:43:10 +12:00
|
|
|
inkId = m_palette->getStyleCount();
|
|
|
|
m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
|
|
|
|
m_palette->setStyle(inkId, m_configuration.m_inkColor);
|
|
|
|
}
|
|
|
|
assert(inkId);
|
|
|
|
|
|
|
|
m_dataRasterArray.push_back(std::pair<int, DataRasterP>(inkId, dataRaster));
|
|
|
|
int threshold = m_configuration.m_threshold;
|
|
|
|
|
|
|
|
for (y = 0; y < m_src->getLy(); y++) {
|
|
|
|
TPixelGR8 *inPix = srcGR->pixels(y);
|
|
|
|
TPixelGR8 *inEndPix = inPix + srcGR->getLx();
|
|
|
|
DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
|
|
|
|
x = 0;
|
|
|
|
while (inPix < inEndPix) {
|
|
|
|
*dataPix = DataPixel();
|
|
|
|
if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
|
|
|
|
x == m_src->getLx() - 1) {
|
|
|
|
dataPix->m_value = 255;
|
|
|
|
dataPix->m_ink = false;
|
|
|
|
} else {
|
|
|
|
dataPix->m_value = inPix->value;
|
|
|
|
dataPix->m_ink = (inPix->value < threshold);
|
|
|
|
}
|
|
|
|
dataPix->m_pos.x = x++;
|
|
|
|
dataPix->m_pos.y = y;
|
|
|
|
dataPix->m_node = 0;
|
|
|
|
inPix++;
|
|
|
|
dataPix++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (srcCM) {
|
|
|
|
int currInk, nextInk = 0;
|
|
|
|
|
|
|
|
if (srcCM->getLx() * srcCM->getLy() > 5000000) {
|
|
|
|
int threshold = m_configuration.m_threshold;
|
|
|
|
int inkId = m_palette->getClosestStyle(TPixel::Black);
|
|
|
|
|
|
|
|
if (TPixel::Black != m_palette->getStyle(inkId)->getMainColor()) {
|
|
|
|
inkId = m_palette->getStyleCount();
|
|
|
|
m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
|
|
|
|
m_palette->setStyle(inkId, m_configuration.m_inkColor);
|
|
|
|
}
|
|
|
|
assert(inkId);
|
|
|
|
|
|
|
|
m_dataRasterArray.push_back(
|
|
|
|
std::pair<int, DataRasterP>(inkId, dataRaster));
|
|
|
|
|
|
|
|
// inizializza la parte centrale
|
|
|
|
for (y = 0; y < m_src->getLy(); y++) {
|
|
|
|
TPixelCM32 *inPix = srcCM->pixels(y);
|
|
|
|
TPixelCM32 *inEndPix = inPix + m_src->getLx();
|
|
|
|
DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
|
|
|
|
x = 0;
|
|
|
|
while (inPix < inEndPix) {
|
2020-11-20 19:17:37 +13:00
|
|
|
*dataPix = DataPixel();
|
|
|
|
int value = inPix->getTone();
|
2016-06-15 18:43:10 +12:00
|
|
|
if (m_configuration.m_ignoreInkColors) inkId = 1;
|
|
|
|
if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
|
|
|
|
x == m_src->getLx() - 1) {
|
|
|
|
dataPix->m_value = 255;
|
|
|
|
dataPix->m_ink = false;
|
|
|
|
} else {
|
|
|
|
dataPix->m_value = value;
|
|
|
|
dataPix->m_ink = (value < threshold);
|
|
|
|
}
|
|
|
|
dataPix->m_pos.x = x++;
|
|
|
|
dataPix->m_pos.y = y;
|
|
|
|
dataPix->m_node = 0;
|
|
|
|
inPix++;
|
|
|
|
dataPix++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
do {
|
|
|
|
// Inizializzo DataRasterP
|
|
|
|
DataRasterP dataRaster(m_src->getSize().lx + 2,
|
|
|
|
m_src->getSize().ly + 2);
|
|
|
|
int ly = dataRaster->getLy();
|
|
|
|
int lx = dataRaster->getLx();
|
|
|
|
int wrap = dataRaster->getWrap();
|
|
|
|
DataPixel *dataPix0 = dataRaster->pixels(0);
|
|
|
|
DataPixel *dataPix1 = dataRaster->pixels(0) + m_src->getLx() + 1;
|
|
|
|
for (y = 0; y < ly; y++, dataPix0 += wrap, dataPix1 += wrap) {
|
|
|
|
dataPix0->m_pos.x = 0;
|
|
|
|
dataPix1->m_pos.x = lx - 1;
|
|
|
|
dataPix0->m_pos.y = dataPix1->m_pos.y = y;
|
|
|
|
dataPix0->m_value = dataPix1->m_value = 0;
|
|
|
|
dataPix0->m_ink = dataPix1->m_ink = false;
|
|
|
|
dataPix0->m_node = dataPix1->m_node = 0;
|
|
|
|
}
|
|
|
|
dataPix0 = dataRaster->pixels(0);
|
|
|
|
dataPix1 = dataRaster->pixels(ly - 1);
|
|
|
|
for (x = 0; x < lx; x++, dataPix0++, dataPix1++) {
|
|
|
|
dataPix0->m_pos.x = dataPix1->m_pos.x = x;
|
|
|
|
dataPix0->m_pos.y = 0;
|
|
|
|
dataPix1->m_pos.y = ly - 1;
|
|
|
|
dataPix0->m_value = dataPix1->m_value = 0;
|
|
|
|
dataPix0->m_ink = dataPix1->m_ink = false;
|
|
|
|
dataPix0->m_node = dataPix1->m_node = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int threshold =
|
|
|
|
m_configuration.m_threshold; // tolerance: 1->MAX thresh: 1-255
|
|
|
|
currInk = nextInk;
|
|
|
|
nextInk = 0;
|
|
|
|
m_dataRasterArray.push_back(
|
|
|
|
std::pair<int, DataRasterP>(currInk, dataRaster));
|
|
|
|
|
|
|
|
// inizializza la parte centrale
|
|
|
|
for (y = 0; y < m_src->getLy(); y++) {
|
|
|
|
TPixelCM32 *inPix = srcCM->pixels(y);
|
|
|
|
TPixelCM32 *inEndPix = inPix + m_src->getLx();
|
|
|
|
DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
|
|
|
|
x = 0;
|
|
|
|
while (inPix < inEndPix) {
|
|
|
|
*dataPix = DataPixel();
|
|
|
|
int value = inPix->getTone();
|
|
|
|
if (value < 255 && !m_configuration.m_ignoreInkColors) {
|
|
|
|
int ink = inPix->getInk();
|
|
|
|
if (currInk == 0) {
|
|
|
|
currInk = ink;
|
|
|
|
m_dataRasterArray.back().first = ink;
|
|
|
|
} else if (ink != currInk) {
|
|
|
|
value = 255;
|
|
|
|
if (nextInk == 0) {
|
|
|
|
for (ii = 0; ii < (int)m_dataRasterArray.size() - 1; ii++)
|
|
|
|
if (m_dataRasterArray[ii].first == ink) break;
|
|
|
|
if (ii == (int)m_dataRasterArray.size() - 1) nextInk = ink;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dataPix->m_pos.x = x++;
|
|
|
|
dataPix->m_pos.y = y;
|
|
|
|
dataPix->m_value = value;
|
|
|
|
dataPix->m_ink = (value < threshold);
|
|
|
|
dataPix->m_node = 0;
|
|
|
|
inPix++;
|
|
|
|
dataPix++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (nextInk != 0);
|
|
|
|
}
|
|
|
|
if (m_configuration.m_ignoreInkColors) {
|
|
|
|
assert(m_dataRasterArray.size() == 1);
|
|
|
|
m_dataRasterArray.back().first = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
assert(false);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
TVectorImageP VectorizerCore::outlineVectorize(
|
|
|
|
const TImageP &image, const OutlineConfiguration &configuration,
|
|
|
|
TPalette *palette) {
|
|
|
|
TVectorImageP out;
|
|
|
|
|
|
|
|
OutlineVectorizer vectorizer(configuration, palette);
|
|
|
|
|
|
|
|
TRasterImageP ri = image;
|
|
|
|
TToonzImageP vi = image;
|
|
|
|
if (ri)
|
|
|
|
vectorizer.makeDataRaster(ri->getRaster());
|
|
|
|
else
|
|
|
|
vectorizer.makeDataRaster(vi->getRaster());
|
|
|
|
int layersCount = vectorizer.m_dataRasterArray.size();
|
|
|
|
if (layersCount > 1) {
|
|
|
|
out = new TVectorImage();
|
|
|
|
out->setPalette(palette);
|
|
|
|
}
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < (int)layersCount; i++) {
|
|
|
|
vectorizer.m_dataRaster = vectorizer.m_dataRasterArray[i].second;
|
|
|
|
vectorizer.m_configuration.m_strokeStyleId =
|
|
|
|
vectorizer.m_dataRasterArray[i].first;
|
|
|
|
vectorizer.m_protoOutlines.clear();
|
|
|
|
vectorizer.init();
|
|
|
|
vectorizer.createOutlineStrokes();
|
|
|
|
renormalizeImage(vectorizer.m_vimage.getPointer());
|
|
|
|
vectorizer.m_vimage->setPalette(palette);
|
|
|
|
if (layersCount > 1) out->mergeImage(vectorizer.m_vimage, TAffine());
|
|
|
|
|
|
|
|
if (i != (int)layersCount - 1) vectorizer.m_vimage = new TVectorImage();
|
|
|
|
}
|
|
|
|
|
|
|
|
return (layersCount == 1) ? vectorizer.m_vimage : out;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
|
2016-07-14 00:05:06 +12:00
|
|
|
static bool isPointInRegion(TPointD p, TRegion *r) {
|
2016-06-15 18:43:10 +12:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
double stepX = i * 0.2;
|
|
|
|
int j;
|
|
|
|
for (j = 0; j < 5; j++) {
|
|
|
|
double stepY = j * 0.2;
|
|
|
|
if (r->contains(TPointD(p.x + stepX, p.y + stepY))) return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------
|
|
|
|
|
|
|
|
// Se findInk == true :
|
2016-06-15 18:43:10 +12:00
|
|
|
// trova il punto piu' vicino a p con ink puro e restituisce true se e'
|
|
|
|
// contenuto nella regione
|
2016-03-19 06:57:51 +13:00
|
|
|
// Se findInk == false :
|
2016-06-15 18:43:10 +12:00
|
|
|
// Trova il punto piu' vicino a p con paint puro e restituisce true se e'
|
|
|
|
// contenuto nella regione
|
2016-03-19 06:57:51 +13:00
|
|
|
|
|
|
|
//(Daniele) Aggiunti controlli per evitare uscite dai bounds
|
|
|
|
|
2016-07-14 00:05:06 +12:00
|
|
|
static bool isNearestInkOrPaintInRegion(bool findInk, const TRasterCM32P &ras,
|
|
|
|
TRegion *r, const TAffine &aff,
|
|
|
|
const TPoint &p) {
|
2016-06-15 18:43:10 +12:00
|
|
|
bool isTheLastSquare = false;
|
|
|
|
int mx, my, Mx, My;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 1; i <= 100; i++) {
|
|
|
|
int j, t, s, e;
|
|
|
|
if (p.x - i >= 0) {
|
|
|
|
my = std::max(p.y - i, 0);
|
|
|
|
My = std::min(p.y + i, ras->getLy() - 1);
|
|
|
|
for (j = my; j <= My; j++) {
|
|
|
|
TPixelCM32 col = ras->pixels(j)[p.x - i];
|
|
|
|
int tone = col.getTone();
|
|
|
|
if ((findInk && tone == 0) || (!findInk && tone == 255)) {
|
|
|
|
if (isPointInRegion(aff * TPointD(double(p.x - i), double(j)), r))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
isTheLastSquare = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p.y + i < ras->getLy()) {
|
|
|
|
mx = std::max(p.x - i + 1, 0);
|
|
|
|
Mx = std::min(p.x + i, ras->getLx() - 1);
|
|
|
|
for (t = mx; t <= Mx; t++) {
|
|
|
|
TPixelCM32 col = ras->pixels(p.y + i)[t];
|
|
|
|
int tone = col.getTone();
|
|
|
|
if ((findInk && tone == 0) || (!findInk && tone == 255)) {
|
|
|
|
if (isPointInRegion(aff * TPointD(double(t), double(p.y + i)), r))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
isTheLastSquare = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p.x + i < ras->getLx()) {
|
|
|
|
my = std::max(p.y - i, 0);
|
|
|
|
My = std::min(p.y + i - 1, ras->getLy() - 1);
|
|
|
|
for (s = my; s <= My; s++) {
|
|
|
|
TPixelCM32 col = ras->pixels(s)[p.x + i];
|
|
|
|
int tone = col.getTone();
|
|
|
|
if ((findInk && tone == 0) || (!findInk && tone == 255)) {
|
|
|
|
if (isPointInRegion(aff * TPointD(double(p.x + i), double(s)), r))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
isTheLastSquare = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p.y - i >= 0) {
|
|
|
|
mx = std::max(p.x - i + 1, 0);
|
|
|
|
Mx = std::min(p.x + i - 1, ras->getLx() - 1);
|
|
|
|
for (e = mx; e <= Mx; e++) {
|
|
|
|
TPixelCM32 col = ras->pixels(p.y - i)[e];
|
|
|
|
int tone = col.getTone();
|
|
|
|
if ((findInk && tone == 0) || (!findInk && tone == 255)) {
|
|
|
|
if (isPointInRegion(aff * TPointD(double(e), double(p.y - i)), r))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
isTheLastSquare = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isTheLastSquare) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//======================================================
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isBright(const TPixelCM32 &pix, int threshold) {
|
|
|
|
return pix.getTone() >= threshold;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isBright(const TPixelGR8 &pix, int threshold) {
|
|
|
|
return pix.value >= threshold;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isBright(const TPixel32 &pix, int threshold) {
|
|
|
|
// Using Value in HSV color model
|
|
|
|
return std::max(pix.r, std::max(pix.g, pix.b)) >= threshold * (pix.m / 255.0);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
// Using Lightness in HSL color model
|
|
|
|
// return (max(pix.r,max(pix.g,pix.b)) + min(pix.r,min(pix.g,pix.b))) / 2.0
|
|
|
|
// >= threshold * (pix.m / 255.0);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
// Using (relative) Luminance
|
|
|
|
// return 0.2126 * pix.r + 0.7152 * pix.g + 0.0722 * pix.b >= threshold *
|
|
|
|
// (pix.m / 255.0);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isDark(const TPixelCM32 &pix, int threshold) {
|
|
|
|
return !isBright(pix, threshold);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isDark(const TPixelGR8 &pix, int threshold) {
|
|
|
|
return !isBright(pix, threshold);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline bool isDark(const TPixelRGBM32 &pix, int threshold) {
|
|
|
|
return !isBright(pix, threshold);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------
|
|
|
|
|
|
|
|
template <typename Pix, typename Selector>
|
|
|
|
bool getInternalPoint(const TRasterPT<Pix> &ras, const Selector &sel,
|
2016-06-15 18:43:10 +12:00
|
|
|
const TAffine &inverse, const VectorizerConfiguration &c,
|
|
|
|
const TRegion *region, TPointD &p) {
|
|
|
|
struct Locals {
|
|
|
|
const TRasterPT<Pix> &m_ras;
|
|
|
|
const Selector &m_sel;
|
|
|
|
const TAffine &m_inverse;
|
|
|
|
double m_pixelSize;
|
|
|
|
const TRegion &m_region;
|
|
|
|
|
|
|
|
static bool contains(const TRegion ®ion, const TPointD &p) {
|
|
|
|
return region.getBBox().contains(p) &&
|
|
|
|
(region.leftScanlineIntersections(p.x, p.y) % 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool contains(const TPointD &p) {
|
|
|
|
if (!contains(m_region, p)) return false;
|
|
|
|
|
|
|
|
UINT sr, srCount = m_region.getSubregionCount();
|
|
|
|
for (sr = 0; sr != srCount; ++sr) {
|
|
|
|
if (contains(*m_region.getSubregion(sr), p)) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subdivide the output scanline in even intervals, and sample each's
|
|
|
|
// midpoint
|
|
|
|
bool sampleMidpoints(TPointD &p, double x0, double x1, double y,
|
|
|
|
int intervalsCount) {
|
|
|
|
const double iCountD = intervalsCount;
|
|
|
|
|
|
|
|
for (int i = 0; i != intervalsCount; ++i) {
|
|
|
|
double i_x0 = tcg::numeric_ops::lerp(x0, x1, i / iCountD),
|
|
|
|
i_x1 = tcg::numeric_ops::lerp(x0, x1, (i + 1) / iCountD);
|
|
|
|
|
|
|
|
if (sample(p = TPointD(0.5 * (i_x0 + i_x1), y))) return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sample the output scanline's midpoint
|
|
|
|
bool sample(TPointD &point) {
|
|
|
|
return (contains(point) &&
|
|
|
|
adjustPoint(point) // Ensures that point is inRaster()
|
|
|
|
&& selected(point));
|
|
|
|
}
|
|
|
|
|
|
|
|
TPoint toRaster(const TPointD &p) {
|
|
|
|
const TPointD &pRasD = m_inverse * p;
|
|
|
|
return TPoint(pRasD.x, pRasD.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool inRaster(const TPointD &point) {
|
|
|
|
const TPoint &pRas = toRaster(point);
|
|
|
|
return (pRas.x >= 0 && pRas.x < m_ras->getLx() && pRas.y >= 0 &&
|
|
|
|
pRas.y < m_ras->getLy());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool selected(const TPointD &point) {
|
|
|
|
assert(inRaster(point));
|
|
|
|
|
|
|
|
const TPoint &pRas = toRaster(point);
|
|
|
|
return m_sel(m_ras->pixels(pRas.y)[pRas.x]);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool adjustPoint(TPointD &p) {
|
|
|
|
const TRectD &bbox = m_region.getBBox();
|
|
|
|
const double tol = std::max(1e-1 * m_pixelSize, 1e-4);
|
|
|
|
|
|
|
|
TPointD newP = p;
|
|
|
|
{
|
|
|
|
// Adjust along x axis
|
|
|
|
int iCount = scanlineIntersectionsBefore(newP.x, newP.y, true);
|
|
|
|
|
|
|
|
double in0 = newP.x, out0 = bbox.x0, in1 = newP.x, out1 = bbox.x1;
|
|
|
|
|
|
|
|
isolateBorderX(in0, out0, newP.y, iCount, tol);
|
|
|
|
isolateBorderX(in1, out1, newP.y, iCount, tol);
|
|
|
|
|
|
|
|
newP = TPointD(0.5 * (in0 + in1), newP.y);
|
|
|
|
assert(scanlineIntersectionsBefore(newP.x, newP.y, true) == iCount);
|
|
|
|
}
|
|
|
|
{
|
|
|
|
// Adjust along y axis
|
|
|
|
int iCount = scanlineIntersectionsBefore(newP.x, newP.y, false);
|
|
|
|
|
|
|
|
double in0 = newP.y, out0 = bbox.y0, in1 = newP.y, out1 = bbox.y1;
|
|
|
|
|
|
|
|
isolateBorderY(newP.x, in0, out0, iCount, tol);
|
|
|
|
isolateBorderY(newP.x, in1, out1, iCount, tol);
|
|
|
|
|
|
|
|
newP = TPointD(newP.x, 0.5 * (in0 + in1));
|
|
|
|
assert(scanlineIntersectionsBefore(newP.x, newP.y, false) == iCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
return inRaster(newP) ? (p = newP, true) : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void isolateBorderX(double &xIn, double &xOut, double y, int iCount,
|
|
|
|
const double tol) {
|
|
|
|
assert(scanlineIntersectionsBefore(xIn, y, true) == iCount);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
// Subdivide current interval
|
|
|
|
double mid = 0.5 * (xIn + xOut);
|
|
|
|
|
|
|
|
if (scanlineIntersectionsBefore(mid, y, true) == iCount)
|
|
|
|
xIn = mid;
|
|
|
|
else
|
|
|
|
xOut = mid;
|
|
|
|
|
|
|
|
if (std::abs(xOut - xIn) < tol) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void isolateBorderY(double x, double &yIn, double &yOut, int iCount,
|
|
|
|
const double tol) {
|
|
|
|
assert(scanlineIntersectionsBefore(x, yIn, false) == iCount);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
// Subdivide current interval
|
|
|
|
double mid = 0.5 * (yIn + yOut);
|
|
|
|
|
|
|
|
if (scanlineIntersectionsBefore(x, mid, false) == iCount)
|
|
|
|
yIn = mid;
|
|
|
|
else
|
|
|
|
yOut = mid;
|
|
|
|
|
|
|
|
if (std::abs(yOut - yIn) < tol) break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int scanlineIntersectionsBefore(double x, double y, bool hor) {
|
|
|
|
int result = m_region.scanlineIntersectionsBefore(x, y, hor);
|
|
|
|
|
|
|
|
UINT sr, srCount = m_region.getSubregionCount();
|
|
|
|
for (sr = 0; sr != srCount; ++sr)
|
|
|
|
result +=
|
|
|
|
m_region.getSubregion(sr)->scanlineIntersectionsBefore(x, y, hor);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
return result;
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
} locals = {ras, sel, inverse, c.m_thickScale, *region};
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
assert(region);
|
|
|
|
|
|
|
|
const TRectD ®ionBBox = region->getBBox();
|
|
|
|
double regionMidY = 0.5 * (regionBBox.y0 + regionBBox.y1);
|
|
|
|
|
|
|
|
int ic, icEnd = tceil((regionBBox.x1 - regionBBox.x0) / c.m_thickScale) +
|
|
|
|
1; // Say you have 4 pixels, in [0, 4]. We want to
|
|
|
|
// have at least 4 intervals where midpoints are
|
|
|
|
// taken - so end intervals count is 5.
|
|
|
|
for (ic = 1; ic < icEnd; ic *= 2) {
|
|
|
|
if (locals.sampleMidpoints(p, regionBBox.x0, regionBBox.x1, regionMidY, ic))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
|
|
|
|
//(Daniele)
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
// Taking lone, unchecked points is dangerous - they could lie inside
|
|
|
|
// region r and still have a wrong color (for example, if they lie
|
2016-03-19 06:57:51 +13:00
|
|
|
//*on* a boundary stroke).
|
2016-06-15 18:43:10 +12:00
|
|
|
// Plus, over-threshold regions should always be considered black.
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
// In order to improve this, we search a 4way-local-brightest
|
|
|
|
// neighbour of p. Observe that, however, it may still lie outside r;
|
|
|
|
// would that happen, p was not significative in the first place.
|
2016-03-19 06:57:51 +13:00
|
|
|
//---------------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline TPixel32 takeLocalBrightest(const TRaster32P rr, TRegion *r,
|
|
|
|
const VectorizerConfiguration &c,
|
|
|
|
TPoint &p) {
|
|
|
|
TPoint pMax;
|
|
|
|
|
|
|
|
while (r->contains(c.m_affine * convert(p))) {
|
|
|
|
pMax = p;
|
|
|
|
if (p.x > 0 && rr->pixels(p.y)[p.x - 1] > rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x - 1, p.y);
|
|
|
|
if (p.x < rr->getLx() - 1 &&
|
|
|
|
rr->pixels(p.y)[p.x + 1] > rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x + 1, p.y);
|
|
|
|
if (p.y > 0 && rr->pixels(p.y - 1)[p.x] > rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y - 1);
|
|
|
|
if (p.y < rr->getLy() - 1 &&
|
|
|
|
rr->pixels(p.y + 1)[p.x] > rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y + 1);
|
|
|
|
|
|
|
|
if (p == pMax) break;
|
|
|
|
|
|
|
|
p = pMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isBright(rr->pixels(p.y)[p.x], c.m_threshold))
|
|
|
|
return TPixel32::Black;
|
|
|
|
else
|
|
|
|
return rr->pixels(p.y)[p.x];
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline TPixel32 takeLocalBrightest(const TRasterGR8P rgr, TRegion *r,
|
|
|
|
const VectorizerConfiguration &c,
|
|
|
|
TPoint &p) {
|
|
|
|
TPoint pMax;
|
|
|
|
|
|
|
|
while (r->contains(c.m_affine * convert(p))) {
|
|
|
|
pMax = p;
|
|
|
|
if (p.x > 0 && rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y)[p.x - 1])
|
|
|
|
pMax = TPoint(p.x - 1, p.y);
|
|
|
|
if (p.x < rgr->getLx() - 1 &&
|
|
|
|
rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y)[p.x + 1])
|
|
|
|
pMax = TPoint(p.x + 1, p.y);
|
|
|
|
if (p.y > 0 && rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y - 1)[p.x])
|
|
|
|
pMax = TPoint(p.x, p.y - 1);
|
|
|
|
if (p.y < rgr->getLy() - 1 &&
|
|
|
|
rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y + 1)[p.x])
|
|
|
|
pMax = TPoint(p.x, p.y + 1);
|
|
|
|
|
|
|
|
if (p == pMax) break;
|
|
|
|
|
|
|
|
p = pMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isBright(rgr->pixels(p.y)[p.x], c.m_threshold))
|
|
|
|
return TPixel32::Black;
|
|
|
|
else {
|
|
|
|
int val = rgr->pixels(p.y)[p.x].value;
|
|
|
|
return TPixel32(val, val, val, 255);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline TPixel32 takeLocalDarkest(const TRaster32P rr, TRegion *r,
|
|
|
|
const VectorizerConfiguration &c, TPoint &p) {
|
|
|
|
TPoint pMax;
|
|
|
|
|
|
|
|
while (r->contains(c.m_affine * convert(p))) // 1
|
|
|
|
{
|
|
|
|
pMax = p;
|
|
|
|
if (p.x > 0 && rr->pixels(p.y)[p.x - 1] < rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x - 1, p.y);
|
|
|
|
if (p.x < rr->getLx() - 1 &&
|
|
|
|
rr->pixels(p.y)[p.x + 1] < rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x + 1, p.y);
|
|
|
|
if (p.y > 0 && rr->pixels(p.y - 1)[p.x] < rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y - 1);
|
|
|
|
if (p.y < rr->getLy() - 1 &&
|
|
|
|
rr->pixels(p.y + 1)[p.x] < rr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y + 1);
|
|
|
|
|
|
|
|
if (p == pMax) break;
|
|
|
|
|
|
|
|
p = pMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rr->pixels(p.y)[p.x];
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
inline TPixel32 takeLocalDarkest(const TRasterGR8P rgr, TRegion *r,
|
|
|
|
const VectorizerConfiguration &c, TPoint &p) {
|
|
|
|
TPoint pMax;
|
|
|
|
|
|
|
|
while (r->contains(c.m_affine * convert(p))) {
|
|
|
|
pMax = p;
|
|
|
|
if (p.x > 0 && rgr->pixels(p.y)[p.x - 1] < rgr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x - 1, p.y);
|
|
|
|
if (p.x < rgr->getLx() - 1 &&
|
|
|
|
rgr->pixels(p.y)[p.x + 1] < rgr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x + 1, p.y);
|
|
|
|
if (p.y > 0 && rgr->pixels(p.y - 1)[p.x] < rgr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y - 1);
|
|
|
|
if (p.y < rgr->getLy() - 1 &&
|
|
|
|
rgr->pixels(p.y + 1)[p.x] < rgr->pixels(pMax.y)[pMax.x])
|
|
|
|
pMax = TPoint(p.x, p.y + 1);
|
|
|
|
|
|
|
|
if (p == pMax) break;
|
|
|
|
|
|
|
|
p = pMax;
|
|
|
|
}
|
|
|
|
|
|
|
|
int val = rgr->pixels(p.y)[p.x].value;
|
|
|
|
return TPixel32(val, val, val, 255);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//=================================================================
|
|
|
|
// Vectorizer Core
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
|
|
|
|
TPalette *palette,
|
|
|
|
const CenterlineConfiguration &c,
|
|
|
|
int regionCount) {
|
2020-01-08 14:49:48 +13:00
|
|
|
auto const alwaysTrue = [](const TPixelCM32 &) { return true; };
|
2016-06-15 18:43:10 +12:00
|
|
|
|
|
|
|
TRasterCM32P rt = ras;
|
|
|
|
TRaster32P rr = ras;
|
|
|
|
TRasterGR8P rgr = ras;
|
|
|
|
|
|
|
|
assert(rt || rr || rgr);
|
|
|
|
|
|
|
|
bool isBrightRegion = true;
|
|
|
|
{
|
|
|
|
unsigned int e, edgesCount = r->getEdgeCount();
|
|
|
|
for (e = 0; e < edgesCount; ++e) {
|
|
|
|
if (isInkRegionEdge(r->getEdge(e)->m_s)) {
|
|
|
|
if (r->getEdge(e)->m_w0 > r->getEdge(e)->m_w1) isBrightRegion = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (isInkRegionEdgeReversed(r->getEdge(e)->m_s)) {
|
|
|
|
if (r->getEdge(e)->m_w0 < r->getEdge(e)->m_w1) isBrightRegion = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TAffine inverse = c.m_affine.inv();
|
|
|
|
TPointD pd;
|
|
|
|
|
|
|
|
typedef bool (*cm_func)(const TPixelCM32 &, int);
|
|
|
|
typedef bool (*rgbm_func)(const TPixelRGBM32 &, int);
|
|
|
|
typedef bool (*gr_func)(const TPixelGR8 &, int);
|
|
|
|
|
|
|
|
bool tookPoint =
|
|
|
|
isBrightRegion
|
2020-01-08 14:49:48 +13:00
|
|
|
? rt ? getInternalPoint(
|
|
|
|
rt,
|
|
|
|
std::bind(cm_func(isBright), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
|
|
|
inverse, c, r, pd) ||
|
|
|
|
// If no bright pixel could be found,
|
|
|
|
getInternalPoint(rt, alwaysTrue, inverse, c, r,
|
|
|
|
pd)
|
|
|
|
: // then any pixel inside the region
|
2016-06-15 18:43:10 +12:00
|
|
|
rr ? getInternalPoint(
|
2020-01-08 14:49:48 +13:00
|
|
|
rr,
|
|
|
|
std::bind(rgbm_func(isBright), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
2016-06-15 18:43:10 +12:00
|
|
|
inverse, c, r, pd)
|
|
|
|
: // must suffice.
|
|
|
|
getInternalPoint(
|
2020-01-08 14:49:48 +13:00
|
|
|
rgr,
|
|
|
|
std::bind(gr_func(isBright), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
2016-06-15 18:43:10 +12:00
|
|
|
inverse, c, r, pd)
|
2020-01-08 14:49:48 +13:00
|
|
|
: rt ? getInternalPoint(
|
|
|
|
rt,
|
|
|
|
std::bind(cm_func(isDark), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
|
|
|
inverse, c, r, pd)
|
2016-06-15 18:43:10 +12:00
|
|
|
: rr ? getInternalPoint(
|
2020-01-08 14:49:48 +13:00
|
|
|
rr,
|
|
|
|
std::bind(rgbm_func(isDark), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
2016-06-15 18:43:10 +12:00
|
|
|
inverse, c, r, pd)
|
|
|
|
: getInternalPoint(
|
2020-01-08 14:49:48 +13:00
|
|
|
rgr,
|
|
|
|
std::bind(gr_func(isDark), std::placeholders::_1,
|
|
|
|
c.m_threshold),
|
2016-06-15 18:43:10 +12:00
|
|
|
inverse, c, r, pd);
|
|
|
|
|
|
|
|
if (tookPoint) {
|
|
|
|
pd = inverse * pd;
|
|
|
|
TPoint p(pd.x, pd.y); // The same thing that happened inside
|
|
|
|
// getInternalPoint()
|
|
|
|
if (ras->getBounds().contains(p)) {
|
|
|
|
int styleId = 0;
|
|
|
|
|
|
|
|
if (rt) {
|
|
|
|
TPixelCM32 col = rt->pixels(p.y)[p.x];
|
|
|
|
styleId = isBrightRegion
|
|
|
|
? col.getPaint()
|
|
|
|
: col.getInk(); // Only paint colors with centerline
|
|
|
|
} // vectorization
|
|
|
|
else {
|
|
|
|
TPixel32 color;
|
|
|
|
|
|
|
|
// Update color found to local brightness-extremals
|
|
|
|
if (rr) {
|
|
|
|
color = isBrightRegion ? takeLocalBrightest(rr, r, c, p)
|
|
|
|
: takeLocalDarkest(rr, r, c, p);
|
|
|
|
} else {
|
|
|
|
color = isBrightRegion ? takeLocalBrightest(rgr, r, c, p)
|
|
|
|
: takeLocalDarkest(rgr, r, c, p);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (color.m != 0) {
|
|
|
|
styleId = palette->getClosestStyle(color);
|
|
|
|
TPixel32 oldColor = palette->getStyle(styleId)->getMainColor();
|
|
|
|
if (!(isAlmostZero(double(oldColor.r - color.r), 15.0) &&
|
|
|
|
isAlmostZero(double(oldColor.g - color.g), 15.0) &&
|
|
|
|
isAlmostZero(double(oldColor.b - color.b), 15.0))) {
|
|
|
|
styleId = palette->getStyleCount();
|
|
|
|
palette->getStylePage(1)->insertStyle(1, color);
|
|
|
|
palette->setStyle(styleId, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++regionCount;
|
|
|
|
r->setStyle(styleId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)r->getSubregionCount(); ++i)
|
|
|
|
applyFillColors(r->getSubregion(i), ras, palette, c, regionCount);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------
|
2020-11-20 19:17:37 +13:00
|
|
|
/*
|
2016-06-15 18:43:10 +12:00
|
|
|
void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
|
|
|
|
TPalette *palette,
|
|
|
|
const OutlineConfiguration &c,
|
|
|
|
int regionCount) {
|
|
|
|
TRasterCM32P rt = ras;
|
|
|
|
TRaster32P rr = ras;
|
|
|
|
TRasterGR8P rgr = ras;
|
|
|
|
|
|
|
|
assert(rt || rr || rgr);
|
|
|
|
|
|
|
|
TAffine inverse = c.m_affine.inv();
|
|
|
|
bool doInks = !c.m_ignoreInkColors, doPaints = !c.m_leaveUnpainted;
|
|
|
|
|
|
|
|
// Retrieve a point inside the specified region
|
|
|
|
TPointD pd;
|
|
|
|
if (r->getInternalPoint(pd)) {
|
|
|
|
pd = inverse * pd; // Convert point to raster coordinates
|
|
|
|
TPoint p(pd.x, pd.y); //
|
|
|
|
|
|
|
|
// Retrieve the corresponding pixel in the raster image
|
|
|
|
|
|
|
|
if (ras->getBounds().contains(p)) {
|
|
|
|
int styleId = 0;
|
|
|
|
|
|
|
|
if (rt) {
|
|
|
|
// Toonz colormap case
|
|
|
|
TPixelCM32 col =
|
|
|
|
rt->pixels(p.y)[p.x]; // In the outline vectorization case, color
|
|
|
|
int tone = col.getTone(); // can be either ink or paint
|
|
|
|
|
|
|
|
if (tone == 0) // Full ink case
|
|
|
|
styleId = doInks ? col.getInk() : 1;
|
|
|
|
else if (tone == 255 && doPaints) // Full paint case
|
|
|
|
styleId = col.getPaint();
|
|
|
|
else if (tone != 255) {
|
|
|
|
if (regionCount % 2 == 1) {
|
|
|
|
// Whenever regionCount is odd, ink is checked first
|
|
|
|
|
|
|
|
if (isNearestInkOrPaintInRegion(true, rt, r, c.m_affine, p))
|
|
|
|
styleId = doInks ? col.getInk() : 1;
|
|
|
|
else if (doPaints &&
|
|
|
|
isNearestInkOrPaintInRegion(false, rt, r, c.m_affine, p))
|
|
|
|
styleId = col.getPaint();
|
|
|
|
} else {
|
|
|
|
// Whenever regionCount is even, paint is checked first
|
|
|
|
|
|
|
|
if (doPaints &&
|
|
|
|
isNearestInkOrPaintInRegion(false, rt, r, c.m_affine, p))
|
|
|
|
styleId = col.getPaint();
|
|
|
|
else if (isNearestInkOrPaintInRegion(true, rt, r, c.m_affine, p))
|
|
|
|
styleId = doInks ? col.getInk() : 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
TPixel32 color;
|
|
|
|
if (rr)
|
|
|
|
color = rr->pixels(p.y)[p.x];
|
|
|
|
else {
|
|
|
|
int val = rgr->pixels(p.y)[p.x].value;
|
|
|
|
color = (val < 80) ? TPixel32::Black : TPixel32::White;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((color.m != 0) && ((!c.m_leaveUnpainted) ||
|
|
|
|
(c.m_leaveUnpainted && color == c.m_inkColor))) {
|
|
|
|
styleId = palette->getClosestStyle(color);
|
|
|
|
TPixel32 oldColor = palette->getStyle(styleId)->getMainColor();
|
|
|
|
|
|
|
|
if (!(isAlmostZero(double(oldColor.r - color.r), 15.0) &&
|
|
|
|
isAlmostZero(double(oldColor.g - color.g), 15.0) &&
|
|
|
|
isAlmostZero(double(oldColor.b - color.b), 15.0))) {
|
|
|
|
styleId = palette->getStyleCount();
|
|
|
|
palette->getStylePage(1)->insertStyle(1, color);
|
|
|
|
palette->setStyle(styleId, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
++regionCount;
|
|
|
|
r->setStyle(styleId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int i = 0; i < (int)r->getSubregionCount(); ++i)
|
|
|
|
applyFillColors(r->getSubregion(i), ras, palette, c, regionCount);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
2020-11-20 19:17:37 +13:00
|
|
|
*/
|
2016-03-19 06:57:51 +13:00
|
|
|
//-----------------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void VectorizerCore::applyFillColors(TVectorImageP vi, const TImageP &img,
|
|
|
|
TPalette *palette,
|
|
|
|
const VectorizerConfiguration &c) {
|
|
|
|
const CenterlineConfiguration ¢Conf =
|
|
|
|
static_cast<const CenterlineConfiguration &>(c);
|
2020-11-20 19:17:37 +13:00
|
|
|
// const OutlineConfiguration &outConf =
|
|
|
|
// static_cast<const OutlineConfiguration &>(c);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
// If configuration is not set for color fill at all, quit.
|
2020-11-20 19:17:37 +13:00
|
|
|
if (c.m_leaveUnpainted && !c.m_outline && !c.m_alignBoundaryStrokesDirection)
|
|
|
|
return;
|
|
|
|
// if (c.m_leaveUnpainted && (!c.m_outline || outConf.m_ignoreInkColors))
|
|
|
|
// return;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
TToonzImageP ti = img;
|
|
|
|
TRasterImageP ri = img;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
assert(ti || ri);
|
|
|
|
TRasterP ras = ti ? TRasterP(ti->getRaster()) : TRasterP(ri->getRaster());
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
vi->findRegions();
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
int r, regionsCount = vi->getRegionCount();
|
2020-11-20 19:17:37 +13:00
|
|
|
// filling colors in outline mode is done in tnewoutlinevectorize.cpp
|
|
|
|
// if (c.m_outline) {
|
|
|
|
// for (r = 0; r < regionsCount; ++r)
|
|
|
|
// applyFillColors(vi->getRegion(r), ras, palette, outConf, 1);
|
|
|
|
//} else {
|
|
|
|
for (r = 0; r < regionsCount; ++r)
|
|
|
|
applyFillColors(vi->getRegion(r), ras, palette, centConf,
|
|
|
|
1); // 1 - c.m_makeFrame;
|
|
|
|
|
|
|
|
clearInkRegionFlags(vi);
|
|
|
|
//}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
struct StrokeData {
|
|
|
|
UCHAR m_hasColor, m_hasRegion;
|
|
|
|
};
|
|
|
|
|
|
|
|
// found the shape boundaries and recognize their stroke direction.
|
|
|
|
// based on the function getBoundaries() in levelselection.cpp
|
|
|
|
void alignBoundariesDirection(TVectorImageP vi) {
|
|
|
|
enum { FORWARD = 0x1, BACKWARD = 0x2, INTERNAL = FORWARD | BACKWARD };
|
|
|
|
|
|
|
|
struct locals {
|
|
|
|
static void markEdges(const TRegion ®ion, std::vector<StrokeData> &sData,
|
|
|
|
bool parentRegionHasColor) {
|
|
|
|
bool regionHasColor = (region.getStyle() != 0);
|
|
|
|
|
|
|
|
// Traverse region edges, marking associated strokes accordingly
|
|
|
|
UINT e, eCount = region.getEdgeCount();
|
|
|
|
for (e = 0; e != eCount; ++e) {
|
|
|
|
const TEdge &ed = *region.getEdge(e);
|
|
|
|
assert(ed.m_s);
|
|
|
|
|
|
|
|
int strokeIdx = ed.m_index;
|
|
|
|
if (strokeIdx >= 0) // Could be <0 in case the corresponding
|
|
|
|
{ // stroke is a region 'closure' (autoclose)
|
|
|
|
assert(0 <= strokeIdx && strokeIdx < sData.size());
|
|
|
|
|
|
|
|
StrokeData &sd = sData[strokeIdx];
|
|
|
|
|
|
|
|
UCHAR side = (ed.m_w1 > ed.m_w0) ? FORWARD : BACKWARD;
|
|
|
|
|
|
|
|
sd.m_hasRegion |= side;
|
|
|
|
if (regionHasColor) sd.m_hasColor |= side;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parentRegionHasColor) {
|
|
|
|
// Mark non-region edge sides with color
|
|
|
|
for (e = 0; e != eCount; ++e) {
|
|
|
|
const TEdge &ed = *region.getEdge(e);
|
|
|
|
assert(ed.m_s);
|
|
|
|
|
|
|
|
int strokeIdx = ed.m_index;
|
|
|
|
if (strokeIdx >= 0) {
|
|
|
|
StrokeData &sd = sData[strokeIdx];
|
|
|
|
sd.m_hasColor |= (INTERNAL & ~sd.m_hasRegion);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark recursively on sub-regions
|
|
|
|
UINT sr, srCount = region.getSubregionCount();
|
|
|
|
for (sr = 0; sr != srCount; ++sr)
|
|
|
|
markEdges(*region.getSubregion(sr), sData, regionHasColor);
|
|
|
|
}
|
|
|
|
}; // locals
|
|
|
|
|
|
|
|
std::vector<StrokeData> sData(vi->getStrokeCount());
|
|
|
|
|
|
|
|
// Traverse regions, mark each stroke edge with the side a COLORED region is
|
|
|
|
// on
|
|
|
|
UINT r, rCount = vi->getRegionCount();
|
|
|
|
for (r = 0; r != rCount; ++r)
|
|
|
|
locals::markEdges(*vi->getRegion(r), sData, false);
|
|
|
|
|
|
|
|
UINT s, sCount = vi->getStrokeCount();
|
|
|
|
for (s = 0; s != sCount; ++s) {
|
|
|
|
// Strokes not appearing as region edges must be checked for region
|
|
|
|
// inclusion separately
|
|
|
|
if (!sData[s].m_hasRegion) {
|
|
|
|
TRegion *parentRegion = vi->getRegion(vi->getStroke(s)->getPoint(0.5));
|
|
|
|
|
|
|
|
if (parentRegion && parentRegion->getStyle())
|
|
|
|
sData[s].m_hasColor = INTERNAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// flip the stroke here
|
|
|
|
if (sData[s].m_hasColor == FORWARD) vi->getStroke(s)->changeDirection();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void removeFillColors(TRegion *r) {
|
|
|
|
UINT i, edgeCount = r->getEdgeCount();
|
|
|
|
for (i = 0; i < edgeCount; ++i) {
|
|
|
|
r->getEdge(i)->setStyle(0);
|
2016-06-15 18:43:10 +12:00
|
|
|
}
|
2020-11-20 19:17:37 +13:00
|
|
|
// Build the color for its sub-regions
|
|
|
|
int j, rCount = r->getSubregionCount();
|
|
|
|
for (j = 0; j < rCount; ++j) removeFillColors(r->getSubregion(j));
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
2020-11-20 19:17:37 +13:00
|
|
|
void removeFillColors(TVectorImageP vi) {
|
|
|
|
int i, rCount = vi->getRegionCount();
|
|
|
|
for (i = 0; i < rCount; ++i) {
|
|
|
|
removeFillColors(vi->getRegion(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
2016-03-19 06:57:51 +13:00
|
|
|
//=================================================================
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
TVectorImageP VectorizerCore::vectorize(const TImageP &img,
|
|
|
|
const VectorizerConfiguration &c,
|
|
|
|
TPalette *plt) {
|
|
|
|
TVectorImageP vi;
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
if (c.m_outline)
|
|
|
|
vi = newOutlineVectorize(
|
|
|
|
img, static_cast<const NewOutlineConfiguration &>(c), plt);
|
|
|
|
else {
|
|
|
|
TImageP img2(img);
|
|
|
|
vi = centerlineVectorize(
|
|
|
|
img2, static_cast<const CenterlineConfiguration &>(c), plt);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
if (vi) {
|
|
|
|
for (int i = 0; i < (int)vi->getStrokeCount(); ++i) {
|
|
|
|
TStroke *stroke = vi->getStroke(i);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
for (int j = 0; j < stroke->getControlPointCount(); ++j) {
|
|
|
|
TThickPoint p = stroke->getControlPoint(j);
|
|
|
|
p = TThickPoint(c.m_affine * p, c.m_thickScale * p.thick);
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
stroke->setControlPoint(j, p);
|
|
|
|
}
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
applyFillColors(vi, img2, plt, c);
|
|
|
|
}
|
|
|
|
}
|
2020-11-20 19:17:37 +13:00
|
|
|
// align boundary strokes direction
|
|
|
|
if (c.m_alignBoundaryStrokesDirection) {
|
|
|
|
alignBoundariesDirection(vi);
|
|
|
|
vi->validateRegions(false);
|
|
|
|
vi->findRegions();
|
|
|
|
if (c.m_leaveUnpainted) removeFillColors(vi);
|
|
|
|
}
|
2016-03-19 06:57:51 +13:00
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
return vi;
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
|
2016-06-15 18:43:10 +12:00
|
|
|
void VectorizerCore::emitPartialDone(void) {
|
|
|
|
emit partialDone(m_currPartial++, m_totalPartials);
|
2016-03-19 06:57:51 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
void VectorizerCore::emitPartialDone(int current)
|
|
|
|
{
|
|
|
|
m_currPartial= current;
|
|
|
|
emit partialDone(current, m_totalPartials);
|
|
|
|
}
|
|
|
|
*/
|