1
2
3 #include "toonz/tcenterlinevectorizer.h"
4
5 // TnzCore includes
6 #include "tsystem.h"
7 #include "tstopwatch.h"
8 #include "tpalette.h"
9 #include "trastercm.h"
10 #include "ttoonzimage.h"
11 #include "tregion.h"
12 #include "tstroke.h"
13 #include "trasterimage.h"
14 #include "tmathutil.h"
15
16 // tcg includes
17 #include "tcg/tcg_numeric_ops.h"
18
19 // STD includes
20 #include <cmath>
21 #include <functional>
22
23 #undef DEBUG
24
25 //---------------------------------------------------------
26
27 struct ControlPoint {
28 TStroke *m_stroke;
29 int m_index;
ControlPointControlPoint30 ControlPoint(TStroke *stroke, int index) : m_stroke(stroke), m_index(index) {}
getPointControlPoint31 TPointD getPoint() const { return m_stroke->getControlPoint(m_index); }
setPointControlPoint32 void setPoint(const TPointD &p) {
33 TThickPoint point = m_stroke->getControlPoint(m_index);
34 point.x = p.x;
35 point.y = p.y;
36 m_stroke->setControlPoint(m_index, point);
37 }
38 };
39
40 //---------------------------------------------------------
41
42 class Node;
43
44 class DataPixel {
45 public:
46 TPoint m_pos;
47 int m_value;
48 bool m_ink;
49 Node *m_node;
DataPixel()50 DataPixel() : m_value(0), m_ink(false), m_node(0) {}
51 };
52
53 //---------------------------------------------------------
54 #ifdef _WIN32
55 template class DV_EXPORT_API TSmartPointerT<TRasterT<DataPixel>>;
56 #endif
57 typedef TRasterPT<DataPixel> DataRasterP;
58 //---------------------------------------------------------
59
60 class Junction;
61
62 class Node {
63 public:
64 Node *m_other;
65 DataPixel *m_pixel;
66 Node *m_prev, *m_next;
67 Junction *m_junction;
68 #ifdef DEBUG
69 bool m_flag;
70 #endif
71 bool m_visited;
Node()72 Node()
73 : m_pixel(0)
74 , m_prev(0)
75 , m_next(0)
76 , m_junction(0)
77 ,
78 #ifdef DEBUG
79 m_flag(false)
80 ,
81 #endif
82 m_visited(false) {
83 }
84 };
85
86 //---------------------------------------------------------
87
88 class ProtoStroke;
89
90 class Junction {
91 public:
92 TThickPoint m_center;
93 std::deque<Node *> m_nodes;
94 int m_junctionOrder;
95 std::vector<ProtoStroke *> m_protoStrokes;
96 bool m_locked;
Junction()97 Junction()
98 : m_center()
99 , m_nodes()
100 , m_junctionOrder(0)
101 , m_protoStrokes()
102 , m_locked(false) {}
103 bool isConvex();
104 };
105
106 //---------------------------------------------------------
107
108 class ProtoStroke {
109 public:
110 TPointD m_startDirection, m_endDirection;
111 Junction *m_startJunction, *m_endJunction;
112 std::deque<TThickPoint> m_points;
ProtoStroke()113 ProtoStroke()
114 : m_points()
115 , m_startDirection()
116 , m_endDirection()
117 , m_startJunction(0)
118 , m_endJunction(0) {}
ProtoStroke(std::deque<TThickPoint>::iterator it_b,std::deque<TThickPoint>::iterator it_e)119 ProtoStroke(std::deque<TThickPoint>::iterator it_b,
120 std::deque<TThickPoint>::iterator it_e)
121 : m_points(it_b, it_e)
122 , m_startDirection()
123 , m_endDirection()
124 , m_startJunction(0)
125 , m_endJunction(0) {}
126 };
127
128 //---------------------------------------------------------
129
computeDistance2(Node * na,Node * nb)130 static double computeDistance2(Node *na, Node *nb) {
131 assert(na->m_pixel);
132 assert(nb->m_pixel);
133 TPointD d = convert(na->m_pixel->m_pos - nb->m_pixel->m_pos);
134 return d * d;
135 }
136
137 //---------------------------------------------------------
138
renormalizeImage(TVectorImage * vi)139 static void renormalizeImage(TVectorImage *vi) {
140 int i, j;
141 int n = vi->getStrokeCount();
142 std::vector<ControlPoint> points;
143 points.reserve(n * 2);
144 for (i = 0; i < n; i++) {
145 TStroke *stroke = vi->getStroke(i);
146 int m = stroke->getControlPointCount();
147 if (m > 0) {
148 if (m == 1)
149 points.push_back(ControlPoint(stroke, 0));
150 else {
151 points.push_back(ControlPoint(stroke, 0));
152 points.push_back(ControlPoint(stroke, m - 1));
153 }
154 }
155 }
156 int count = points.size();
157 for (i = 0; i < count; i++) {
158 ControlPoint &pi = points[i];
159 TPointD posi = pi.getPoint();
160 TPointD center = posi;
161 std::vector<int> neighbours;
162 neighbours.push_back(i);
163 for (j = i + 1; j < count; j++) {
164 TPointD posj = points[j].getPoint();
165 double d = tdistance(posj, posi);
166 if (d < 0.01) {
167 neighbours.push_back(j);
168 center += posj;
169 }
170 }
171 int m = neighbours.size();
172 if (m == 1) continue;
173 center = center * (1.0 / m);
174 for (j = 0; j < m; j++) points[neighbours[j]].setPoint(center);
175 }
176 }
177
178 //---------------------------------------------------------
179
180 class OutlineVectorizer {
181 TPalette *m_palette;
182
183 public:
184 TRasterP m_src;
185
186 OutlineConfiguration m_configuration;
187 DataRasterP m_dataRaster;
188 std::vector<std::pair<int, DataRasterP>> m_dataRasterArray;
189 TVectorImageP m_vimage;
190 std::vector<Node *> m_nodes;
191 std::list<std::vector<TThickPoint>> m_protoOutlines;
192
193 std::vector<Junction *> m_junctions;
194
OutlineVectorizer(const OutlineConfiguration & configuration,TPalette * palette)195 OutlineVectorizer(const OutlineConfiguration &configuration,
196 TPalette *palette)
197 : m_configuration(configuration), m_palette(palette) {}
198
199 ~OutlineVectorizer();
200
201 void traceOutline(Node *initialNode);
202 void createOutlineStrokes();
203 void makeDataRaster(const TRasterP &src);
204
205 Node *findOtherSide(Node *node);
206
207 void clearNodes();
208 Node *createNode(DataPixel *pix);
209
210 void clearJunctions();
211
212 void init();
213
214 void link(DataPixel *pix, DataPixel *from, DataPixel *to);
215
computeGradient(DataPixel * pix)216 TPoint computeGradient(DataPixel *pix) {
217 assert(m_dataRaster);
218 const int wrap = m_dataRaster->getWrap();
219
220 TPoint g(0, 0);
221 int n, s, w, e, nw, sw, ne, se;
222
223 w = pix[-1].m_value;
224 nw = pix[-1 + wrap].m_value;
225 sw = pix[-1 - wrap].m_value;
226
227 e = pix[+1].m_value;
228 ne = pix[+1 + wrap].m_value;
229 se = pix[+1 - wrap].m_value;
230
231 n = pix[+wrap].m_value;
232 s = pix[-wrap].m_value;
233
234 g.y = -sw + ne - se + nw + 2 * (n - s);
235 g.x = -sw + ne + se - nw + 2 * (e - w);
236 return g;
237 }
238
239 private:
240 // not implemented
241 OutlineVectorizer(const OutlineVectorizer &);
242 OutlineVectorizer &operator=(const OutlineVectorizer &);
243 };
244
245 //---------------------------------------------------------
246
~OutlineVectorizer()247 OutlineVectorizer::~OutlineVectorizer() {
248 m_protoOutlines.clear();
249 clearNodes();
250 clearJunctions();
251 }
252
253 //---------------------------------------------------------
254
init()255 void OutlineVectorizer::init() {
256 int y;
257
258 DataRasterP dataRaster = m_dataRaster;
259 const int wrap = dataRaster->getWrap();
260 const int delta[] = {-wrap - 1, -wrap, -wrap + 1, 1,
261 wrap + 1, wrap, wrap - 1, -1};
262
263 for (y = 1; y < dataRaster->getLy() - 1; y++) {
264 DataPixel *pix = dataRaster->pixels(y);
265 DataPixel *endPix = pix + dataRaster->getLx() - 1;
266 pix++;
267 for (pix++; pix < endPix; ++pix) {
268 if ((pix->m_ink == false) || (pix[-wrap].m_ink && pix[wrap].m_ink &&
269 pix[-1].m_ink && pix[1].m_ink))
270 continue;
271 int i;
272 for (i = 0; i < 8; i++)
273 if (pix[delta[i]].m_ink && pix[delta[(i + 1) & 0x7]].m_ink == false)
274 break;
275 int start = i;
276 if (i == 8) continue; // punto isolato
277 for (;;) {
278 int j = (i + 1) & 0x7;
279 assert(i < 8 && pix[delta[i]].m_ink);
280 assert(j < 8 && pix[delta[j]].m_ink == false);
281 do
282 j = (j + 1) & 0x7;
283 while (pix[delta[j]].m_ink == false);
284 assert(j < 8 && pix[delta[j]].m_ink);
285 if (((i + 2) & 0x7) != j || (i & 1) == 0) {
286 // il bianco comprende anche un fianco
287 link(pix, pix + delta[i], pix + delta[j]);
288 }
289 i = j;
290 assert(i < 8);
291 while (pix[delta[(i + 1) & 0x7]].m_ink) i = (i + 1) & 0x7;
292 assert(i < 8 && pix[delta[i]].m_ink);
293 assert(pix[delta[(i + 1) & 0x7]].m_ink == false);
294 if (i == start) break;
295 }
296 }
297 }
298 }
299
300 //---------------------------------------------------------
301
createNode(DataPixel * pix)302 Node *OutlineVectorizer::createNode(DataPixel *pix) {
303 Node *node = new Node();
304 node->m_pixel = pix;
305 node->m_other = pix->m_node;
306 pix->m_node = node;
307 m_nodes.push_back(node);
308 return node;
309 }
310
311 //---------------------------------------------------------
312
clearNodes()313 void OutlineVectorizer::clearNodes() {
314 int i;
315 for (i = 0; i < (int)m_nodes.size(); i++) delete m_nodes[i];
316 m_nodes.clear();
317 }
318
319 //---------------------------------------------------------
320
clearJunctions()321 void OutlineVectorizer::clearJunctions() {
322 int i;
323 for (i = 0; i < (int)m_junctions.size(); i++) delete m_junctions[i];
324 m_junctions.clear();
325 }
326
327 //---------------------------------------------------------
328
link(DataPixel * pix,DataPixel * srcPix,DataPixel * dstPix)329 void OutlineVectorizer::link(DataPixel *pix, DataPixel *srcPix,
330 DataPixel *dstPix) {
331 Node *srcNode = 0, *dstNode = 0, *node = 0;
332 Node *tmp;
333 for (tmp = pix->m_node; tmp; tmp = tmp->m_other) {
334 if (tmp->m_pixel == 0) continue;
335 if (tmp->m_prev && tmp->m_prev->m_pixel == srcPix) {
336 assert(srcNode == 0);
337 if (node) {
338 assert(node->m_next->m_pixel == dstPix);
339 assert(node->m_prev == 0);
340 node->m_prev = tmp->m_prev;
341 tmp->m_prev->m_next = node;
342 tmp->m_next = tmp->m_prev = 0;
343 tmp->m_pixel = 0;
344 return;
345 }
346 assert(tmp->m_next == 0);
347 srcNode = tmp->m_prev;
348 node = tmp;
349 }
350 if (tmp->m_next && tmp->m_next->m_pixel == dstPix) {
351 assert(dstNode == 0);
352 if (node) {
353 assert(node->m_prev->m_pixel == srcPix);
354 assert(node->m_next == 0);
355 node->m_next = tmp->m_next;
356 tmp->m_next->m_prev = node;
357 tmp->m_next = tmp->m_prev = 0;
358 tmp->m_pixel = 0;
359 return;
360 }
361 assert(tmp->m_prev == 0);
362 dstNode = tmp->m_next;
363 node = tmp;
364 }
365 }
366 if (!node) node = createNode(pix);
367 if (!srcNode) srcNode = createNode(srcPix);
368 if (!dstNode) dstNode = createNode(dstPix);
369
370 if (!node->m_next) {
371 node->m_next = dstNode;
372 assert(dstNode->m_prev == 0);
373 dstNode->m_prev = node;
374 }
375 if (!node->m_prev) {
376 node->m_prev = srcNode;
377 assert(srcNode->m_next == 0);
378 srcNode->m_next = node;
379 }
380
381 assert(node->m_next == dstNode);
382 assert(node->m_prev == srcNode);
383 assert(dstNode->m_prev == node);
384 assert(srcNode->m_next == node);
385 }
386
387 //---------------------------------------------------------
388
traceOutline(Node * initialNode)389 void OutlineVectorizer::traceOutline(Node *initialNode) {
390 Node *startNode = initialNode;
391 Node *node;
392 do {
393 if (!startNode) break;
394 node = findOtherSide(startNode);
395 if (!node) break;
396
397 double startDist2 = computeDistance2(startNode, node);
398 if (startDist2 > 0.1) break;
399
400 startNode = startNode->m_next;
401 } while (startNode != initialNode);
402
403 if (!startNode) return;
404 node = startNode;
405 std::vector<TThickPoint> points;
406 do {
407 node = node->m_next;
408 if (!node) break;
409 node->m_visited = true;
410 points.push_back(TThickPoint(convert(node->m_pixel->m_pos), 0));
411 } while (node != startNode);
412 m_protoOutlines.push_back(points);
413 }
414
415 //---------------------------------------------------------
416
findOtherSide(Node * node)417 Node *OutlineVectorizer::findOtherSide(Node *node) {
418 DataPixel *pix = node->m_pixel;
419
420 TPoint dir = -computeGradient(pix);
421 if (dir == TPoint(0, 0)) return 0;
422 TPoint d1(tsign(dir.x), 0), d2(0, tsign(dir.y));
423 int num = abs(dir.y), den = abs(dir.x);
424 if (num > den) {
425 std::swap(d1, d2);
426 std::swap(num, den);
427 }
428 TPoint pos = pix->m_pos;
429 int i;
430 for (i = 0;; i++) {
431 TPoint q(pos.x + d1.x * i + d2.x * num * i / den,
432 pos.y + d1.y * i + d2.y * num * i / den);
433 DataPixel *nextPix = m_dataRaster->pixels(q.y) + q.x;
434 if (nextPix->m_ink == false) break;
435 pix = nextPix;
436 }
437 assert(pix);
438 if (!pix->m_node) {
439 const int wrap = m_dataRaster->getWrap();
440 if (pix[-1].m_node)
441 pix--;
442 else if (pix[1].m_node)
443 pix++;
444 else if (pix[wrap].m_node)
445 pix += wrap;
446 else if (pix[-wrap].m_node)
447 pix -= wrap;
448 else {
449 assert(0);
450 }
451 }
452 if (!pix->m_node) return 0;
453 Node *q = pix->m_node;
454 while (q->m_pixel == 0 && q->m_other) q = q->m_other;
455 assert(q && q->m_pixel == pix);
456
457 for (i = 0; i < 5; i++) {
458 if (!q->m_prev) break;
459 q = q->m_prev;
460 }
461
462 Node *best = q;
463 double bestDist2 = computeDistance2(q, node);
464 for (i = 0; i < 10; i++) {
465 q = q->m_next;
466 if (!q) break;
467 double dist2 = computeDistance2(q, node);
468 if (dist2 < bestDist2) {
469 bestDist2 = dist2;
470 best = q;
471 }
472 }
473
474 return best;
475 }
476
477 //---------------------------------------------------------
478
createOutlineStrokes()479 void OutlineVectorizer::createOutlineStrokes() {
480 m_vimage->enableRegionComputing(true, false);
481 int j;
482
483 for (j = 0; j < (int)m_nodes.size(); j++) {
484 Node *node = m_nodes[j];
485 if (node->m_pixel == 0 || node->m_visited) continue;
486 traceOutline(node);
487 }
488
489 #ifdef DEBUG
490 for (j = 0; j < (int)m_nodes.size(); j++) {
491 Node *node = m_nodes[j];
492 if (node->m_pixel == 0 || node->m_flag) continue;
493 outputNodes(node);
494 }
495 #endif
496
497 std::list<std::vector<TThickPoint>>::iterator it_outlines =
498 m_protoOutlines.begin();
499 for (; it_outlines != m_protoOutlines.end(); it_outlines++) {
500 if (it_outlines->size() > 3) {
501 std::vector<TThickPoint> points;
502 std::vector<TThickPoint>::iterator it;
503
504 if (it_outlines->size() > 10) {
505 it = it_outlines->begin() + 1;
506 for (;;) {
507 // Baco: Ricontrolla l'if seguente - in alcuni casi va fuori bounds...
508 if ((int)it_outlines->size() <= m_configuration.m_smoothness + 1)
509 break;
510 if (it >= it_outlines->end() - (m_configuration.m_smoothness + 1))
511 break;
512 for (j = 0; j < m_configuration.m_smoothness; j++)
513 it = it_outlines->erase(it);
514 ++it;
515 }
516 }
517
518 points.push_back(it_outlines->front());
519 it = it_outlines->begin();
520 TThickPoint old = *it;
521 ++it;
522 for (; it != it_outlines->end(); ++it) {
523 TThickPoint point((1 / 2.0) * (*it + old));
524 points.push_back(point);
525 old = *it;
526 }
527
528 points.push_back(it_outlines->back());
529 points.push_back(it_outlines->front());
530
531 TStroke *stroke =
532 TStroke::interpolate(points, m_configuration.m_interpolationError);
533 stroke->setStyle(m_configuration.m_strokeStyleId);
534 stroke->setSelfLoop();
535 m_vimage->addStroke(stroke);
536 }
537 }
538 }
539
540 //---------------------------------------------------------
541
colorDistance2(const TPixel32 & c0,const TPixel32 & c1)542 inline int colorDistance2(const TPixel32 &c0, const TPixel32 &c1) {
543 return ((c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) +
544 (c0.b - c1.b) * (c0.b - c1.b));
545 }
546
547 //---------------------------------------------------------
548 #define MAX_TOLERANCE 20
549
550 #include "tcolorstyles.h"
551
makeDataRaster(const TRasterP & src)552 void OutlineVectorizer::makeDataRaster(const TRasterP &src) {
553 m_vimage = new TVectorImage();
554 if (!src) return;
555 m_src = src;
556
557 clearNodes();
558 clearJunctions();
559
560 int x, y, ii = 0;
561 TRaster32P srcRGBM = (TRaster32P)m_src;
562 TRasterCM32P srcCM = (TRasterCM32P)m_src;
563 TRasterGR8P srcGR = (TRasterGR8P)m_src;
564
565 // Inizializzo DataRasterP per i casi in cui si ha un TRaster32P, un
566 // TRasterGR8P o un TRasterCM32P molto grande
567 DataRasterP dataRaster(m_src->getSize().lx + 2, m_src->getSize().ly + 2);
568 if (srcRGBM || srcGR ||
569 (srcCM && srcCM->getLx() * srcCM->getLy() > 5000000)) {
570 int ly = dataRaster->getLy();
571 int lx = dataRaster->getLx();
572 int wrap = dataRaster->getWrap();
573 DataPixel *dataPix0 = dataRaster->pixels(0);
574 DataPixel *dataPix1 = dataRaster->pixels(0) + m_src->getLx() + 1;
575 for (y = 0; y < ly; y++, dataPix0 += wrap, dataPix1 += wrap) {
576 dataPix0->m_pos.x = 0;
577 dataPix1->m_pos.x = lx - 1;
578 dataPix0->m_pos.y = dataPix1->m_pos.y = y;
579 dataPix0->m_value = dataPix1->m_value = 0;
580 dataPix0->m_ink = dataPix1->m_ink = false;
581 dataPix0->m_node = dataPix1->m_node = 0;
582 }
583 dataPix0 = dataRaster->pixels(0);
584 dataPix1 = dataRaster->pixels(ly - 1);
585 for (x = 0; x < lx; x++, dataPix0++, dataPix1++) {
586 dataPix0->m_pos.x = dataPix1->m_pos.x = x;
587 dataPix0->m_pos.y = 0;
588 dataPix1->m_pos.y = ly - 1;
589 dataPix0->m_value = dataPix1->m_value = 0;
590 dataPix0->m_ink = dataPix1->m_ink = false;
591 dataPix0->m_node = dataPix1->m_node = 0;
592 }
593 }
594
595 if (srcRGBM) {
596 assert(m_palette);
597 int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
598 if (!inkId || m_configuration.m_inkColor !=
599 m_palette->getStyle(inkId)->getMainColor()) {
600 inkId = m_palette->getStyleCount();
601 m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
602 m_palette->setStyle(inkId, m_configuration.m_inkColor);
603 }
604 assert(inkId);
605
606 m_dataRasterArray.push_back(std::pair<int, DataRasterP>(inkId, dataRaster));
607 int maxDistance2 =
608 m_configuration.m_threshold * m_configuration.m_threshold;
609
610 for (y = 0; y < m_src->getLy(); y++) {
611 TPixel32 *inPix = srcRGBM->pixels(y);
612 TPixel32 *inEndPix = inPix + srcRGBM->getLx();
613 DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
614 x = 0;
615 while (inPix < inEndPix) {
616 *dataPix = DataPixel();
617 int distance2 = colorDistance2(m_configuration.m_inkColor, *inPix);
618
619 if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
620 x == m_src->getLx() - 1 || inPix->m == 0) {
621 dataPix->m_value = 255;
622 dataPix->m_ink = false;
623 } else {
624 dataPix->m_value = (inPix->r + 2 * inPix->g + inPix->b) >> 2;
625 dataPix->m_ink = (distance2 < maxDistance2);
626 }
627 dataPix->m_pos.x = x++;
628 dataPix->m_pos.y = y;
629 dataPix->m_node = 0;
630 inPix++;
631 dataPix++;
632 }
633 }
634 } else if (srcGR) {
635 assert(m_palette);
636 int inkId = m_palette->getClosestStyle(m_configuration.m_inkColor);
637 if (!inkId || m_configuration.m_inkColor !=
638 m_palette->getStyle(inkId)->getMainColor()) {
639 inkId = m_palette->getStyleCount();
640 m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
641 m_palette->setStyle(inkId, m_configuration.m_inkColor);
642 }
643 assert(inkId);
644
645 m_dataRasterArray.push_back(std::pair<int, DataRasterP>(inkId, dataRaster));
646 int threshold = m_configuration.m_threshold;
647
648 for (y = 0; y < m_src->getLy(); y++) {
649 TPixelGR8 *inPix = srcGR->pixels(y);
650 TPixelGR8 *inEndPix = inPix + srcGR->getLx();
651 DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
652 x = 0;
653 while (inPix < inEndPix) {
654 *dataPix = DataPixel();
655 if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
656 x == m_src->getLx() - 1) {
657 dataPix->m_value = 255;
658 dataPix->m_ink = false;
659 } else {
660 dataPix->m_value = inPix->value;
661 dataPix->m_ink = (inPix->value < threshold);
662 }
663 dataPix->m_pos.x = x++;
664 dataPix->m_pos.y = y;
665 dataPix->m_node = 0;
666 inPix++;
667 dataPix++;
668 }
669 }
670 }
671
672 else if (srcCM) {
673 int currInk, nextInk = 0;
674
675 if (srcCM->getLx() * srcCM->getLy() > 5000000) {
676 int threshold = m_configuration.m_threshold;
677 int inkId = m_palette->getClosestStyle(TPixel::Black);
678
679 if (TPixel::Black != m_palette->getStyle(inkId)->getMainColor()) {
680 inkId = m_palette->getStyleCount();
681 m_palette->getStylePage(1)->insertStyle(1, m_configuration.m_inkColor);
682 m_palette->setStyle(inkId, m_configuration.m_inkColor);
683 }
684 assert(inkId);
685
686 m_dataRasterArray.push_back(
687 std::pair<int, DataRasterP>(inkId, dataRaster));
688
689 // inizializza la parte centrale
690 for (y = 0; y < m_src->getLy(); y++) {
691 TPixelCM32 *inPix = srcCM->pixels(y);
692 TPixelCM32 *inEndPix = inPix + m_src->getLx();
693 DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
694 x = 0;
695 while (inPix < inEndPix) {
696 *dataPix = DataPixel();
697 int value = inPix->getTone();
698 if (m_configuration.m_ignoreInkColors) inkId = 1;
699 if (y == 0 || y == m_src->getLy() - 1 || x == 0 ||
700 x == m_src->getLx() - 1) {
701 dataPix->m_value = 255;
702 dataPix->m_ink = false;
703 } else {
704 dataPix->m_value = value;
705 dataPix->m_ink = (value < threshold);
706 }
707 dataPix->m_pos.x = x++;
708 dataPix->m_pos.y = y;
709 dataPix->m_node = 0;
710 inPix++;
711 dataPix++;
712 }
713 }
714 } else {
715 do {
716 // Inizializzo DataRasterP
717 DataRasterP dataRaster(m_src->getSize().lx + 2,
718 m_src->getSize().ly + 2);
719 int ly = dataRaster->getLy();
720 int lx = dataRaster->getLx();
721 int wrap = dataRaster->getWrap();
722 DataPixel *dataPix0 = dataRaster->pixels(0);
723 DataPixel *dataPix1 = dataRaster->pixels(0) + m_src->getLx() + 1;
724 for (y = 0; y < ly; y++, dataPix0 += wrap, dataPix1 += wrap) {
725 dataPix0->m_pos.x = 0;
726 dataPix1->m_pos.x = lx - 1;
727 dataPix0->m_pos.y = dataPix1->m_pos.y = y;
728 dataPix0->m_value = dataPix1->m_value = 0;
729 dataPix0->m_ink = dataPix1->m_ink = false;
730 dataPix0->m_node = dataPix1->m_node = 0;
731 }
732 dataPix0 = dataRaster->pixels(0);
733 dataPix1 = dataRaster->pixels(ly - 1);
734 for (x = 0; x < lx; x++, dataPix0++, dataPix1++) {
735 dataPix0->m_pos.x = dataPix1->m_pos.x = x;
736 dataPix0->m_pos.y = 0;
737 dataPix1->m_pos.y = ly - 1;
738 dataPix0->m_value = dataPix1->m_value = 0;
739 dataPix0->m_ink = dataPix1->m_ink = false;
740 dataPix0->m_node = dataPix1->m_node = 0;
741 }
742
743 int threshold =
744 m_configuration.m_threshold; // tolerance: 1->MAX thresh: 1-255
745 currInk = nextInk;
746 nextInk = 0;
747 m_dataRasterArray.push_back(
748 std::pair<int, DataRasterP>(currInk, dataRaster));
749
750 // inizializza la parte centrale
751 for (y = 0; y < m_src->getLy(); y++) {
752 TPixelCM32 *inPix = srcCM->pixels(y);
753 TPixelCM32 *inEndPix = inPix + m_src->getLx();
754 DataPixel *dataPix = dataRaster->pixels(y + 1) + 1;
755 x = 0;
756 while (inPix < inEndPix) {
757 *dataPix = DataPixel();
758 int value = inPix->getTone();
759 if (value < 255 && !m_configuration.m_ignoreInkColors) {
760 int ink = inPix->getInk();
761 if (currInk == 0) {
762 currInk = ink;
763 m_dataRasterArray.back().first = ink;
764 } else if (ink != currInk) {
765 value = 255;
766 if (nextInk == 0) {
767 for (ii = 0; ii < (int)m_dataRasterArray.size() - 1; ii++)
768 if (m_dataRasterArray[ii].first == ink) break;
769 if (ii == (int)m_dataRasterArray.size() - 1) nextInk = ink;
770 }
771 }
772 }
773 dataPix->m_pos.x = x++;
774 dataPix->m_pos.y = y;
775 dataPix->m_value = value;
776 dataPix->m_ink = (value < threshold);
777 dataPix->m_node = 0;
778 inPix++;
779 dataPix++;
780 }
781 }
782 } while (nextInk != 0);
783 }
784 if (m_configuration.m_ignoreInkColors) {
785 assert(m_dataRasterArray.size() == 1);
786 m_dataRasterArray.back().first = 1;
787 }
788 } else
789 assert(false);
790 }
791
792 //---------------------------------------------------------
793
outlineVectorize(const TImageP & image,const OutlineConfiguration & configuration,TPalette * palette)794 TVectorImageP VectorizerCore::outlineVectorize(
795 const TImageP &image, const OutlineConfiguration &configuration,
796 TPalette *palette) {
797 TVectorImageP out;
798
799 OutlineVectorizer vectorizer(configuration, palette);
800
801 TRasterImageP ri = image;
802 TToonzImageP vi = image;
803 if (ri)
804 vectorizer.makeDataRaster(ri->getRaster());
805 else
806 vectorizer.makeDataRaster(vi->getRaster());
807 int layersCount = vectorizer.m_dataRasterArray.size();
808 if (layersCount > 1) {
809 out = new TVectorImage();
810 out->setPalette(palette);
811 }
812 int i;
813 for (i = 0; i < (int)layersCount; i++) {
814 vectorizer.m_dataRaster = vectorizer.m_dataRasterArray[i].second;
815 vectorizer.m_configuration.m_strokeStyleId =
816 vectorizer.m_dataRasterArray[i].first;
817 vectorizer.m_protoOutlines.clear();
818 vectorizer.init();
819 vectorizer.createOutlineStrokes();
820 renormalizeImage(vectorizer.m_vimage.getPointer());
821 vectorizer.m_vimage->setPalette(palette);
822 if (layersCount > 1) out->mergeImage(vectorizer.m_vimage, TAffine());
823
824 if (i != (int)layersCount - 1) vectorizer.m_vimage = new TVectorImage();
825 }
826
827 return (layersCount == 1) ? vectorizer.m_vimage : out;
828 }
829
830 //=========================================================
831
isPointInRegion(TPointD p,TRegion * r)832 static bool isPointInRegion(TPointD p, TRegion *r) {
833 int i;
834 for (i = 0; i < 5; i++) {
835 double stepX = i * 0.2;
836 int j;
837 for (j = 0; j < 5; j++) {
838 double stepY = j * 0.2;
839 if (r->contains(TPointD(p.x + stepX, p.y + stepY))) return true;
840 }
841 }
842 return false;
843 }
844
845 //------------------------------------------------------
846
847 // Se findInk == true :
848 // trova il punto piu' vicino a p con ink puro e restituisce true se e'
849 // contenuto nella regione
850 // Se findInk == false :
851 // Trova il punto piu' vicino a p con paint puro e restituisce true se e'
852 // contenuto nella regione
853
854 //(Daniele) Aggiunti controlli per evitare uscite dai bounds
855
isNearestInkOrPaintInRegion(bool findInk,const TRasterCM32P & ras,TRegion * r,const TAffine & aff,const TPoint & p)856 static bool isNearestInkOrPaintInRegion(bool findInk, const TRasterCM32P &ras,
857 TRegion *r, const TAffine &aff,
858 const TPoint &p) {
859 bool isTheLastSquare = false;
860 int mx, my, Mx, My;
861 int i;
862
863 for (i = 1; i <= 100; i++) {
864 int j, t, s, e;
865 if (p.x - i >= 0) {
866 my = std::max(p.y - i, 0);
867 My = std::min(p.y + i, ras->getLy() - 1);
868 for (j = my; j <= My; j++) {
869 TPixelCM32 col = ras->pixels(j)[p.x - i];
870 int tone = col.getTone();
871 if ((findInk && tone == 0) || (!findInk && tone == 255)) {
872 if (isPointInRegion(aff * TPointD(double(p.x - i), double(j)), r))
873 return true;
874 else
875 isTheLastSquare = true;
876 }
877 }
878 }
879 if (p.y + i < ras->getLy()) {
880 mx = std::max(p.x - i + 1, 0);
881 Mx = std::min(p.x + i, ras->getLx() - 1);
882 for (t = mx; t <= Mx; t++) {
883 TPixelCM32 col = ras->pixels(p.y + i)[t];
884 int tone = col.getTone();
885 if ((findInk && tone == 0) || (!findInk && tone == 255)) {
886 if (isPointInRegion(aff * TPointD(double(t), double(p.y + i)), r))
887 return true;
888 else
889 isTheLastSquare = true;
890 }
891 }
892 }
893 if (p.x + i < ras->getLx()) {
894 my = std::max(p.y - i, 0);
895 My = std::min(p.y + i - 1, ras->getLy() - 1);
896 for (s = my; s <= My; s++) {
897 TPixelCM32 col = ras->pixels(s)[p.x + i];
898 int tone = col.getTone();
899 if ((findInk && tone == 0) || (!findInk && tone == 255)) {
900 if (isPointInRegion(aff * TPointD(double(p.x + i), double(s)), r))
901 return true;
902 else
903 isTheLastSquare = true;
904 }
905 }
906 }
907 if (p.y - i >= 0) {
908 mx = std::max(p.x - i + 1, 0);
909 Mx = std::min(p.x + i - 1, ras->getLx() - 1);
910 for (e = mx; e <= Mx; e++) {
911 TPixelCM32 col = ras->pixels(p.y - i)[e];
912 int tone = col.getTone();
913 if ((findInk && tone == 0) || (!findInk && tone == 255)) {
914 if (isPointInRegion(aff * TPointD(double(e), double(p.y - i)), r))
915 return true;
916 else
917 isTheLastSquare = true;
918 }
919 }
920 }
921
922 if (isTheLastSquare) return false;
923 }
924
925 return false;
926 }
927
928 //======================================================
929
isBright(const TPixelCM32 & pix,int threshold)930 inline bool isBright(const TPixelCM32 &pix, int threshold) {
931 return pix.getTone() >= threshold;
932 }
933
isBright(const TPixelGR8 & pix,int threshold)934 inline bool isBright(const TPixelGR8 &pix, int threshold) {
935 return pix.value >= threshold;
936 }
937
isBright(const TPixel32 & pix,int threshold)938 inline bool isBright(const TPixel32 &pix, int threshold) {
939 // Using Value in HSV color model
940 return std::max(pix.r, std::max(pix.g, pix.b)) >= threshold * (pix.m / 255.0);
941
942 // Using Lightness in HSL color model
943 // return (max(pix.r,max(pix.g,pix.b)) + min(pix.r,min(pix.g,pix.b))) / 2.0
944 // >= threshold * (pix.m / 255.0);
945
946 // Using (relative) Luminance
947 // return 0.2126 * pix.r + 0.7152 * pix.g + 0.0722 * pix.b >= threshold *
948 // (pix.m / 255.0);
949 }
950
951 //------------------------------------------------------
952
isDark(const TPixelCM32 & pix,int threshold)953 inline bool isDark(const TPixelCM32 &pix, int threshold) {
954 return !isBright(pix, threshold);
955 }
956
isDark(const TPixelGR8 & pix,int threshold)957 inline bool isDark(const TPixelGR8 &pix, int threshold) {
958 return !isBright(pix, threshold);
959 }
960
isDark(const TPixelRGBM32 & pix,int threshold)961 inline bool isDark(const TPixelRGBM32 &pix, int threshold) {
962 return !isBright(pix, threshold);
963 }
964
965 //------------------------------------------------------
966
967 template <typename Pix, typename Selector>
getInternalPoint(const TRasterPT<Pix> & ras,const Selector & sel,const TAffine & inverse,const VectorizerConfiguration & c,const TRegion * region,TPointD & p)968 bool getInternalPoint(const TRasterPT<Pix> &ras, const Selector &sel,
969 const TAffine &inverse, const VectorizerConfiguration &c,
970 const TRegion *region, TPointD &p) {
971 struct Locals {
972 const TRasterPT<Pix> &m_ras;
973 const Selector &m_sel;
974 const TAffine &m_inverse;
975 double m_pixelSize;
976 const TRegion &m_region;
977
978 static bool contains(const TRegion ®ion, const TPointD &p) {
979 return region.getBBox().contains(p) &&
980 (region.leftScanlineIntersections(p.x, p.y) % 2);
981 }
982
983 bool contains(const TPointD &p) {
984 if (!contains(m_region, p)) return false;
985
986 UINT sr, srCount = m_region.getSubregionCount();
987 for (sr = 0; sr != srCount; ++sr) {
988 if (contains(*m_region.getSubregion(sr), p)) return false;
989 }
990
991 return true;
992 }
993
994 // Subdivide the output scanline in even intervals, and sample each's
995 // midpoint
996 bool sampleMidpoints(TPointD &p, double x0, double x1, double y,
997 int intervalsCount) {
998 const double iCountD = intervalsCount;
999
1000 for (int i = 0; i != intervalsCount; ++i) {
1001 double i_x0 = tcg::numeric_ops::lerp(x0, x1, i / iCountD),
1002 i_x1 = tcg::numeric_ops::lerp(x0, x1, (i + 1) / iCountD);
1003
1004 if (sample(p = TPointD(0.5 * (i_x0 + i_x1), y))) return true;
1005 }
1006
1007 return false;
1008 }
1009
1010 // Sample the output scanline's midpoint
1011 bool sample(TPointD &point) {
1012 return (contains(point) &&
1013 adjustPoint(point) // Ensures that point is inRaster()
1014 && selected(point));
1015 }
1016
1017 TPoint toRaster(const TPointD &p) {
1018 const TPointD &pRasD = m_inverse * p;
1019 return TPoint(pRasD.x, pRasD.y);
1020 }
1021
1022 bool inRaster(const TPointD &point) {
1023 const TPoint &pRas = toRaster(point);
1024 return (pRas.x >= 0 && pRas.x < m_ras->getLx() && pRas.y >= 0 &&
1025 pRas.y < m_ras->getLy());
1026 }
1027
1028 bool selected(const TPointD &point) {
1029 assert(inRaster(point));
1030
1031 const TPoint &pRas = toRaster(point);
1032 return m_sel(m_ras->pixels(pRas.y)[pRas.x]);
1033 }
1034
1035 bool adjustPoint(TPointD &p) {
1036 const TRectD &bbox = m_region.getBBox();
1037 const double tol = std::max(1e-1 * m_pixelSize, 1e-4);
1038
1039 TPointD newP = p;
1040 {
1041 // Adjust along x axis
1042 int iCount = scanlineIntersectionsBefore(newP.x, newP.y, true);
1043
1044 double in0 = newP.x, out0 = bbox.x0, in1 = newP.x, out1 = bbox.x1;
1045
1046 isolateBorderX(in0, out0, newP.y, iCount, tol);
1047 isolateBorderX(in1, out1, newP.y, iCount, tol);
1048
1049 newP = TPointD(0.5 * (in0 + in1), newP.y);
1050 assert(scanlineIntersectionsBefore(newP.x, newP.y, true) == iCount);
1051 }
1052 {
1053 // Adjust along y axis
1054 int iCount = scanlineIntersectionsBefore(newP.x, newP.y, false);
1055
1056 double in0 = newP.y, out0 = bbox.y0, in1 = newP.y, out1 = bbox.y1;
1057
1058 isolateBorderY(newP.x, in0, out0, iCount, tol);
1059 isolateBorderY(newP.x, in1, out1, iCount, tol);
1060
1061 newP = TPointD(newP.x, 0.5 * (in0 + in1));
1062 assert(scanlineIntersectionsBefore(newP.x, newP.y, false) == iCount);
1063 }
1064
1065 return inRaster(newP) ? (p = newP, true) : false;
1066 }
1067
1068 void isolateBorderX(double &xIn, double &xOut, double y, int iCount,
1069 const double tol) {
1070 assert(scanlineIntersectionsBefore(xIn, y, true) == iCount);
1071
1072 while (true) {
1073 // Subdivide current interval
1074 double mid = 0.5 * (xIn + xOut);
1075
1076 if (scanlineIntersectionsBefore(mid, y, true) == iCount)
1077 xIn = mid;
1078 else
1079 xOut = mid;
1080
1081 if (std::abs(xOut - xIn) < tol) break;
1082 }
1083 }
1084
1085 void isolateBorderY(double x, double &yIn, double &yOut, int iCount,
1086 const double tol) {
1087 assert(scanlineIntersectionsBefore(x, yIn, false) == iCount);
1088
1089 while (true) {
1090 // Subdivide current interval
1091 double mid = 0.5 * (yIn + yOut);
1092
1093 if (scanlineIntersectionsBefore(x, mid, false) == iCount)
1094 yIn = mid;
1095 else
1096 yOut = mid;
1097
1098 if (std::abs(yOut - yIn) < tol) break;
1099 }
1100 }
1101
1102 int scanlineIntersectionsBefore(double x, double y, bool hor) {
1103 int result = m_region.scanlineIntersectionsBefore(x, y, hor);
1104
1105 UINT sr, srCount = m_region.getSubregionCount();
1106 for (sr = 0; sr != srCount; ++sr)
1107 result +=
1108 m_region.getSubregion(sr)->scanlineIntersectionsBefore(x, y, hor);
1109
1110 return result;
1111 }
1112
1113 } locals = {ras, sel, inverse, c.m_thickScale, *region};
1114
1115 assert(region);
1116
1117 const TRectD ®ionBBox = region->getBBox();
1118 double regionMidY = 0.5 * (regionBBox.y0 + regionBBox.y1);
1119
1120 int ic, icEnd = tceil((regionBBox.x1 - regionBBox.x0) / c.m_thickScale) +
1121 1; // Say you have 4 pixels, in [0, 4]. We want to
1122 // have at least 4 intervals where midpoints are
1123 // taken - so end intervals count is 5.
1124 for (ic = 1; ic < icEnd; ic *= 2) {
1125 if (locals.sampleMidpoints(p, regionBBox.x0, regionBBox.x1, regionMidY, ic))
1126 return true;
1127 }
1128
1129 return false;
1130 }
1131
1132 //=========================================================
1133
1134 //(Daniele)
1135
1136 // Taking lone, unchecked points is dangerous - they could lie inside
1137 // region r and still have a wrong color (for example, if they lie
1138 //*on* a boundary stroke).
1139 // Plus, over-threshold regions should always be considered black.
1140
1141 // In order to improve this, we search a 4way-local-brightest
1142 // neighbour of p. Observe that, however, it may still lie outside r;
1143 // would that happen, p was not significative in the first place.
1144 //---------------------------------------------------------------
1145
takeLocalBrightest(const TRaster32P rr,TRegion * r,const VectorizerConfiguration & c,TPoint & p)1146 inline TPixel32 takeLocalBrightest(const TRaster32P rr, TRegion *r,
1147 const VectorizerConfiguration &c,
1148 TPoint &p) {
1149 TPoint pMax;
1150
1151 while (r->contains(c.m_affine * convert(p))) {
1152 pMax = p;
1153 if (p.x > 0 && rr->pixels(p.y)[p.x - 1] > rr->pixels(pMax.y)[pMax.x])
1154 pMax = TPoint(p.x - 1, p.y);
1155 if (p.x < rr->getLx() - 1 &&
1156 rr->pixels(p.y)[p.x + 1] > rr->pixels(pMax.y)[pMax.x])
1157 pMax = TPoint(p.x + 1, p.y);
1158 if (p.y > 0 && rr->pixels(p.y - 1)[p.x] > rr->pixels(pMax.y)[pMax.x])
1159 pMax = TPoint(p.x, p.y - 1);
1160 if (p.y < rr->getLy() - 1 &&
1161 rr->pixels(p.y + 1)[p.x] > rr->pixels(pMax.y)[pMax.x])
1162 pMax = TPoint(p.x, p.y + 1);
1163
1164 if (p == pMax) break;
1165
1166 p = pMax;
1167 }
1168
1169 if (!isBright(rr->pixels(p.y)[p.x], c.m_threshold))
1170 return TPixel32::Black;
1171 else
1172 return rr->pixels(p.y)[p.x];
1173 }
1174
1175 //------------------------------------------------------
1176
takeLocalBrightest(const TRasterGR8P rgr,TRegion * r,const VectorizerConfiguration & c,TPoint & p)1177 inline TPixel32 takeLocalBrightest(const TRasterGR8P rgr, TRegion *r,
1178 const VectorizerConfiguration &c,
1179 TPoint &p) {
1180 TPoint pMax;
1181
1182 while (r->contains(c.m_affine * convert(p))) {
1183 pMax = p;
1184 if (p.x > 0 && rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y)[p.x - 1])
1185 pMax = TPoint(p.x - 1, p.y);
1186 if (p.x < rgr->getLx() - 1 &&
1187 rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y)[p.x + 1])
1188 pMax = TPoint(p.x + 1, p.y);
1189 if (p.y > 0 && rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y - 1)[p.x])
1190 pMax = TPoint(p.x, p.y - 1);
1191 if (p.y < rgr->getLy() - 1 &&
1192 rgr->pixels(pMax.y)[pMax.x] < rgr->pixels(p.y + 1)[p.x])
1193 pMax = TPoint(p.x, p.y + 1);
1194
1195 if (p == pMax) break;
1196
1197 p = pMax;
1198 }
1199
1200 if (!isBright(rgr->pixels(p.y)[p.x], c.m_threshold))
1201 return TPixel32::Black;
1202 else {
1203 int val = rgr->pixels(p.y)[p.x].value;
1204 return TPixel32(val, val, val, 255);
1205 }
1206 }
1207
1208 //---------------------------------------------------------------
1209
takeLocalDarkest(const TRaster32P rr,TRegion * r,const VectorizerConfiguration & c,TPoint & p)1210 inline TPixel32 takeLocalDarkest(const TRaster32P rr, TRegion *r,
1211 const VectorizerConfiguration &c, TPoint &p) {
1212 TPoint pMax;
1213
1214 while (r->contains(c.m_affine * convert(p))) // 1
1215 {
1216 pMax = p;
1217 if (p.x > 0 && rr->pixels(p.y)[p.x - 1] < rr->pixels(pMax.y)[pMax.x])
1218 pMax = TPoint(p.x - 1, p.y);
1219 if (p.x < rr->getLx() - 1 &&
1220 rr->pixels(p.y)[p.x + 1] < rr->pixels(pMax.y)[pMax.x])
1221 pMax = TPoint(p.x + 1, p.y);
1222 if (p.y > 0 && rr->pixels(p.y - 1)[p.x] < rr->pixels(pMax.y)[pMax.x])
1223 pMax = TPoint(p.x, p.y - 1);
1224 if (p.y < rr->getLy() - 1 &&
1225 rr->pixels(p.y + 1)[p.x] < rr->pixels(pMax.y)[pMax.x])
1226 pMax = TPoint(p.x, p.y + 1);
1227
1228 if (p == pMax) break;
1229
1230 p = pMax;
1231 }
1232
1233 return rr->pixels(p.y)[p.x];
1234 }
1235
1236 //------------------------------------------------------
1237
takeLocalDarkest(const TRasterGR8P rgr,TRegion * r,const VectorizerConfiguration & c,TPoint & p)1238 inline TPixel32 takeLocalDarkest(const TRasterGR8P rgr, TRegion *r,
1239 const VectorizerConfiguration &c, TPoint &p) {
1240 TPoint pMax;
1241
1242 while (r->contains(c.m_affine * convert(p))) {
1243 pMax = p;
1244 if (p.x > 0 && rgr->pixels(p.y)[p.x - 1] < rgr->pixels(pMax.y)[pMax.x])
1245 pMax = TPoint(p.x - 1, p.y);
1246 if (p.x < rgr->getLx() - 1 &&
1247 rgr->pixels(p.y)[p.x + 1] < rgr->pixels(pMax.y)[pMax.x])
1248 pMax = TPoint(p.x + 1, p.y);
1249 if (p.y > 0 && rgr->pixels(p.y - 1)[p.x] < rgr->pixels(pMax.y)[pMax.x])
1250 pMax = TPoint(p.x, p.y - 1);
1251 if (p.y < rgr->getLy() - 1 &&
1252 rgr->pixels(p.y + 1)[p.x] < rgr->pixels(pMax.y)[pMax.x])
1253 pMax = TPoint(p.x, p.y + 1);
1254
1255 if (p == pMax) break;
1256
1257 p = pMax;
1258 }
1259
1260 int val = rgr->pixels(p.y)[p.x].value;
1261 return TPixel32(val, val, val, 255);
1262 }
1263
1264 //=================================================================
1265 // Vectorizer Core
1266 //-----------------------------------------------------------------
1267
applyFillColors(TRegion * r,const TRasterP & ras,TPalette * palette,const CenterlineConfiguration & c,int regionCount)1268 void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
1269 TPalette *palette,
1270 const CenterlineConfiguration &c,
1271 int regionCount) {
1272 auto const alwaysTrue = [](const TPixelCM32 &) { return true; };
1273
1274 TRasterCM32P rt = ras;
1275 TRaster32P rr = ras;
1276 TRasterGR8P rgr = ras;
1277
1278 assert(rt || rr || rgr);
1279
1280 bool isBrightRegion = true;
1281 {
1282 unsigned int e, edgesCount = r->getEdgeCount();
1283 for (e = 0; e < edgesCount; ++e) {
1284 if (isInkRegionEdge(r->getEdge(e)->m_s)) {
1285 if (r->getEdge(e)->m_w0 > r->getEdge(e)->m_w1) isBrightRegion = false;
1286 break;
1287 }
1288 if (isInkRegionEdgeReversed(r->getEdge(e)->m_s)) {
1289 if (r->getEdge(e)->m_w0 < r->getEdge(e)->m_w1) isBrightRegion = false;
1290 break;
1291 }
1292 }
1293 }
1294
1295 TAffine inverse = c.m_affine.inv();
1296 TPointD pd;
1297
1298 typedef bool (*cm_func)(const TPixelCM32 &, int);
1299 typedef bool (*rgbm_func)(const TPixelRGBM32 &, int);
1300 typedef bool (*gr_func)(const TPixelGR8 &, int);
1301
1302 bool tookPoint =
1303 isBrightRegion
1304 ? rt ? getInternalPoint(
1305 rt,
1306 std::bind(cm_func(isBright), std::placeholders::_1,
1307 c.m_threshold),
1308 inverse, c, r, pd) ||
1309 // If no bright pixel could be found,
1310 getInternalPoint(rt, alwaysTrue, inverse, c, r,
1311 pd)
1312 : // then any pixel inside the region
1313 rr ? getInternalPoint(
1314 rr,
1315 std::bind(rgbm_func(isBright), std::placeholders::_1,
1316 c.m_threshold),
1317 inverse, c, r, pd)
1318 : // must suffice.
1319 getInternalPoint(
1320 rgr,
1321 std::bind(gr_func(isBright), std::placeholders::_1,
1322 c.m_threshold),
1323 inverse, c, r, pd)
1324 : rt ? getInternalPoint(
1325 rt,
1326 std::bind(cm_func(isDark), std::placeholders::_1,
1327 c.m_threshold),
1328 inverse, c, r, pd)
1329 : rr ? getInternalPoint(
1330 rr,
1331 std::bind(rgbm_func(isDark), std::placeholders::_1,
1332 c.m_threshold),
1333 inverse, c, r, pd)
1334 : getInternalPoint(
1335 rgr,
1336 std::bind(gr_func(isDark), std::placeholders::_1,
1337 c.m_threshold),
1338 inverse, c, r, pd);
1339
1340 if (tookPoint) {
1341 pd = inverse * pd;
1342 TPoint p(pd.x, pd.y); // The same thing that happened inside
1343 // getInternalPoint()
1344 if (ras->getBounds().contains(p)) {
1345 int styleId = 0;
1346
1347 if (rt) {
1348 TPixelCM32 col = rt->pixels(p.y)[p.x];
1349 styleId = isBrightRegion
1350 ? col.getPaint()
1351 : col.getInk(); // Only paint colors with centerline
1352 } // vectorization
1353 else {
1354 TPixel32 color;
1355
1356 // Update color found to local brightness-extremals
1357 if (rr) {
1358 color = isBrightRegion ? takeLocalBrightest(rr, r, c, p)
1359 : takeLocalDarkest(rr, r, c, p);
1360 } else {
1361 color = isBrightRegion ? takeLocalBrightest(rgr, r, c, p)
1362 : takeLocalDarkest(rgr, r, c, p);
1363 }
1364
1365 if (color.m != 0) {
1366 styleId = palette->getClosestStyle(color);
1367 TPixel32 oldColor = palette->getStyle(styleId)->getMainColor();
1368 if (!(isAlmostZero(double(oldColor.r - color.r), 15.0) &&
1369 isAlmostZero(double(oldColor.g - color.g), 15.0) &&
1370 isAlmostZero(double(oldColor.b - color.b), 15.0))) {
1371 styleId = palette->getStyleCount();
1372 palette->getStylePage(1)->insertStyle(1, color);
1373 palette->setStyle(styleId, color);
1374 }
1375 }
1376 }
1377
1378 ++regionCount;
1379 r->setStyle(styleId);
1380 }
1381 }
1382
1383 for (int i = 0; i < (int)r->getSubregionCount(); ++i)
1384 applyFillColors(r->getSubregion(i), ras, palette, c, regionCount);
1385 }
1386
1387 //-----------------------------------------------------------------
1388 /*
1389 void VectorizerCore::applyFillColors(TRegion *r, const TRasterP &ras,
1390 TPalette *palette,
1391 const OutlineConfiguration &c,
1392 int regionCount) {
1393 TRasterCM32P rt = ras;
1394 TRaster32P rr = ras;
1395 TRasterGR8P rgr = ras;
1396
1397 assert(rt || rr || rgr);
1398
1399 TAffine inverse = c.m_affine.inv();
1400 bool doInks = !c.m_ignoreInkColors, doPaints = !c.m_leaveUnpainted;
1401
1402 // Retrieve a point inside the specified region
1403 TPointD pd;
1404 if (r->getInternalPoint(pd)) {
1405 pd = inverse * pd; // Convert point to raster coordinates
1406 TPoint p(pd.x, pd.y); //
1407
1408 // Retrieve the corresponding pixel in the raster image
1409
1410 if (ras->getBounds().contains(p)) {
1411 int styleId = 0;
1412
1413 if (rt) {
1414 // Toonz colormap case
1415 TPixelCM32 col =
1416 rt->pixels(p.y)[p.x]; // In the outline vectorization case, color
1417 int tone = col.getTone(); // can be either ink or paint
1418
1419 if (tone == 0) // Full ink case
1420 styleId = doInks ? col.getInk() : 1;
1421 else if (tone == 255 && doPaints) // Full paint case
1422 styleId = col.getPaint();
1423 else if (tone != 255) {
1424 if (regionCount % 2 == 1) {
1425 // Whenever regionCount is odd, ink is checked first
1426
1427 if (isNearestInkOrPaintInRegion(true, rt, r, c.m_affine, p))
1428 styleId = doInks ? col.getInk() : 1;
1429 else if (doPaints &&
1430 isNearestInkOrPaintInRegion(false, rt, r, c.m_affine, p))
1431 styleId = col.getPaint();
1432 } else {
1433 // Whenever regionCount is even, paint is checked first
1434
1435 if (doPaints &&
1436 isNearestInkOrPaintInRegion(false, rt, r, c.m_affine, p))
1437 styleId = col.getPaint();
1438 else if (isNearestInkOrPaintInRegion(true, rt, r, c.m_affine, p))
1439 styleId = doInks ? col.getInk() : 1;
1440 }
1441 }
1442 } else {
1443 TPixel32 color;
1444 if (rr)
1445 color = rr->pixels(p.y)[p.x];
1446 else {
1447 int val = rgr->pixels(p.y)[p.x].value;
1448 color = (val < 80) ? TPixel32::Black : TPixel32::White;
1449 }
1450
1451 if ((color.m != 0) && ((!c.m_leaveUnpainted) ||
1452 (c.m_leaveUnpainted && color == c.m_inkColor))) {
1453 styleId = palette->getClosestStyle(color);
1454 TPixel32 oldColor = palette->getStyle(styleId)->getMainColor();
1455
1456 if (!(isAlmostZero(double(oldColor.r - color.r), 15.0) &&
1457 isAlmostZero(double(oldColor.g - color.g), 15.0) &&
1458 isAlmostZero(double(oldColor.b - color.b), 15.0))) {
1459 styleId = palette->getStyleCount();
1460 palette->getStylePage(1)->insertStyle(1, color);
1461 palette->setStyle(styleId, color);
1462 }
1463 }
1464 }
1465
1466 ++regionCount;
1467 r->setStyle(styleId);
1468 }
1469 }
1470
1471 for (int i = 0; i < (int)r->getSubregionCount(); ++i)
1472 applyFillColors(r->getSubregion(i), ras, palette, c, regionCount);
1473 }
1474 */
1475 //-----------------------------------------------------------------
1476
applyFillColors(TVectorImageP vi,const TImageP & img,TPalette * palette,const VectorizerConfiguration & c)1477 void VectorizerCore::applyFillColors(TVectorImageP vi, const TImageP &img,
1478 TPalette *palette,
1479 const VectorizerConfiguration &c) {
1480 const CenterlineConfiguration ¢Conf =
1481 static_cast<const CenterlineConfiguration &>(c);
1482 // const OutlineConfiguration &outConf =
1483 // static_cast<const OutlineConfiguration &>(c);
1484
1485 // If configuration is not set for color fill at all, quit.
1486 if (c.m_leaveUnpainted && !c.m_outline && !c.m_alignBoundaryStrokesDirection)
1487 return;
1488 // if (c.m_leaveUnpainted && (!c.m_outline || outConf.m_ignoreInkColors))
1489 // return;
1490
1491 TToonzImageP ti = img;
1492 TRasterImageP ri = img;
1493
1494 assert(ti || ri);
1495 TRasterP ras = ti ? TRasterP(ti->getRaster()) : TRasterP(ri->getRaster());
1496
1497 vi->findRegions();
1498
1499 int r, regionsCount = vi->getRegionCount();
1500 // filling colors in outline mode is done in tnewoutlinevectorize.cpp
1501 // if (c.m_outline) {
1502 // for (r = 0; r < regionsCount; ++r)
1503 // applyFillColors(vi->getRegion(r), ras, palette, outConf, 1);
1504 //} else {
1505 for (r = 0; r < regionsCount; ++r)
1506 applyFillColors(vi->getRegion(r), ras, palette, centConf,
1507 1); // 1 - c.m_makeFrame;
1508
1509 clearInkRegionFlags(vi);
1510 //}
1511 }
1512
1513 namespace {
1514 struct StrokeData {
1515 UCHAR m_hasColor, m_hasRegion;
1516 };
1517
1518 // found the shape boundaries and recognize their stroke direction.
1519 // based on the function getBoundaries() in levelselection.cpp
alignBoundariesDirection(TVectorImageP vi)1520 void alignBoundariesDirection(TVectorImageP vi) {
1521 enum { FORWARD = 0x1, BACKWARD = 0x2, INTERNAL = FORWARD | BACKWARD };
1522
1523 struct locals {
1524 static void markEdges(const TRegion ®ion, std::vector<StrokeData> &sData,
1525 bool parentRegionHasColor) {
1526 bool regionHasColor = (region.getStyle() != 0);
1527
1528 // Traverse region edges, marking associated strokes accordingly
1529 UINT e, eCount = region.getEdgeCount();
1530 for (e = 0; e != eCount; ++e) {
1531 const TEdge &ed = *region.getEdge(e);
1532 assert(ed.m_s);
1533
1534 int strokeIdx = ed.m_index;
1535 if (strokeIdx >= 0) // Could be <0 in case the corresponding
1536 { // stroke is a region 'closure' (autoclose)
1537 assert(0 <= strokeIdx && strokeIdx < sData.size());
1538
1539 StrokeData &sd = sData[strokeIdx];
1540
1541 UCHAR side = (ed.m_w1 > ed.m_w0) ? FORWARD : BACKWARD;
1542
1543 sd.m_hasRegion |= side;
1544 if (regionHasColor) sd.m_hasColor |= side;
1545 }
1546 }
1547
1548 if (parentRegionHasColor) {
1549 // Mark non-region edge sides with color
1550 for (e = 0; e != eCount; ++e) {
1551 const TEdge &ed = *region.getEdge(e);
1552 assert(ed.m_s);
1553
1554 int strokeIdx = ed.m_index;
1555 if (strokeIdx >= 0) {
1556 StrokeData &sd = sData[strokeIdx];
1557 sd.m_hasColor |= (INTERNAL & ~sd.m_hasRegion);
1558 }
1559 }
1560 }
1561
1562 // Mark recursively on sub-regions
1563 UINT sr, srCount = region.getSubregionCount();
1564 for (sr = 0; sr != srCount; ++sr)
1565 markEdges(*region.getSubregion(sr), sData, regionHasColor);
1566 }
1567 }; // locals
1568
1569 std::vector<StrokeData> sData(vi->getStrokeCount());
1570
1571 // Traverse regions, mark each stroke edge with the side a COLORED region is
1572 // on
1573 UINT r, rCount = vi->getRegionCount();
1574 for (r = 0; r != rCount; ++r)
1575 locals::markEdges(*vi->getRegion(r), sData, false);
1576
1577 UINT s, sCount = vi->getStrokeCount();
1578 for (s = 0; s != sCount; ++s) {
1579 // Strokes not appearing as region edges must be checked for region
1580 // inclusion separately
1581 if (!sData[s].m_hasRegion) {
1582 TRegion *parentRegion = vi->getRegion(vi->getStroke(s)->getPoint(0.5));
1583
1584 if (parentRegion && parentRegion->getStyle())
1585 sData[s].m_hasColor = INTERNAL;
1586 }
1587
1588 // flip the stroke here
1589 if (sData[s].m_hasColor == FORWARD) vi->getStroke(s)->changeDirection();
1590 }
1591 }
removeFillColors(TRegion * r)1592 void removeFillColors(TRegion *r) {
1593 UINT i, edgeCount = r->getEdgeCount();
1594 for (i = 0; i < edgeCount; ++i) {
1595 r->getEdge(i)->setStyle(0);
1596 }
1597 // Build the color for its sub-regions
1598 int j, rCount = r->getSubregionCount();
1599 for (j = 0; j < rCount; ++j) removeFillColors(r->getSubregion(j));
1600 }
1601
removeFillColors(TVectorImageP vi)1602 void removeFillColors(TVectorImageP vi) {
1603 int i, rCount = vi->getRegionCount();
1604 for (i = 0; i < rCount; ++i) {
1605 removeFillColors(vi->getRegion(i));
1606 }
1607 }
1608 } // namespace
1609
1610 //=================================================================
1611
vectorize(const TImageP & img,const VectorizerConfiguration & c,TPalette * plt)1612 TVectorImageP VectorizerCore::vectorize(const TImageP &img,
1613 const VectorizerConfiguration &c,
1614 TPalette *plt) {
1615 TVectorImageP vi;
1616
1617 if (c.m_outline)
1618 vi = newOutlineVectorize(
1619 img, static_cast<const NewOutlineConfiguration &>(c), plt);
1620 else {
1621 TImageP img2(img);
1622 vi = centerlineVectorize(
1623 img2, static_cast<const CenterlineConfiguration &>(c), plt);
1624
1625 if (vi) {
1626 for (int i = 0; i < (int)vi->getStrokeCount(); ++i) {
1627 TStroke *stroke = vi->getStroke(i);
1628
1629 for (int j = 0; j < stroke->getControlPointCount(); ++j) {
1630 TThickPoint p = stroke->getControlPoint(j);
1631 p = TThickPoint(c.m_affine * p, c.m_thickScale * p.thick);
1632
1633 stroke->setControlPoint(j, p);
1634 }
1635 }
1636
1637 applyFillColors(vi, img2, plt, c);
1638 }
1639 }
1640 // align boundary strokes direction
1641 if (c.m_alignBoundaryStrokesDirection) {
1642 alignBoundariesDirection(vi);
1643 vi->validateRegions(false);
1644 vi->findRegions();
1645 if (c.m_leaveUnpainted) removeFillColors(vi);
1646 }
1647
1648 return vi;
1649 }
1650
1651 //-----------------------------------------------------------------
1652
emitPartialDone(void)1653 void VectorizerCore::emitPartialDone(void) {
1654 emit partialDone(m_currPartial++, m_totalPartials);
1655 }
1656
1657 //-----------------------------------------------------------------
1658 /*
1659 void VectorizerCore::emitPartialDone(int current)
1660 {
1661 m_currPartial= current;
1662 emit partialDone(current, m_totalPartials);
1663 }
1664 */
1665