1
2
3 // Toonz includes
4 #include "tpixelutils.h"
5 #include "tpalette.h"
6 #include "tcolorstyles.h"
7 #include "timage_io.h"
8 #include "tropcm.h"
9 #include "ttile.h"
10 #include "toonz/toonzscene.h"
11 #include "toonz/tcamera.h"
12 #include "autoadjust.h"
13 #include "autopos.h"
14 #include "cleanuppalette.h"
15 #include "cleanupcommon.h"
16 #include "tmsgcore.h"
17 #include "toonz/cleanupparameters.h"
18
19 #include "toonz/tcleanupper.h"
20
21 using namespace CleanupTypes;
22
23 /* The Cleanup Process Reworked - EXPLANATION (by Daniele)
24
25 INTRODUCTION:
26
27 The purpose of a Cleanup Process is hereby intended as the task of
28 transforming
29 a fullcolor image (any bpp, matte supported*) into a TRasterCM32 -
30 that is, a colormap image - given an externally specified palette of ink
31 colors to
32 be recognized. No paint color is assumed at this stage.
33
34 Typically, artists draw outlines using a black or dark color, whereas
35 different hues are
36 used to mark lines that denote shadows or lights on characters.
37
38 Additional processing steps include the ability to recognize and counter any
39 linear
40 transformation in the image which is 'signaled' by the presence of (black)
41 pegbar holes,
42 so that the countering linear transformation maps those peg holes in the usual
43 centered
44 horizontal fashion.
45
46 Post-processing include despeckling (ie removal or recoloration of little
47 blots with
48 uniform color), and tones' brightness/contrast manipulation.
49
50 (*) The image is first overed on top of a white background
51
52
53 CONSTRAINTS:
54
55 We assume the following constraints throughout the process:
56
57 - Palette colors:
58
59 * Color 0 represents the PAPER color, and will just substitute it in the
60 colormap.
61
62 * Colors with index >= 2 are MATCH-LINE colors, and their HUE ONLY (the H in
63 HSV coordinates)
64 is essential to line recognition. The hue of image pixels is compared to
65 that of each
66 matchline color - and the nearest matchline color is associated to that
67 pixel.
68 If that associated matchline color is still too 'hue-distant' from the pixel
69 color
70 (beyond a user-specified parameter), the pixel is ignored (ie associated to
71 paper).
72 Furthermore, each matchline color also has a parameter corresponding to a
73 saturation
74 threshold; pixels whose color's saturation is below the threshold specified
75 by the
76 associated color are reassociated to the PAPER color.
77
78 * Color 1 represents the OUTLINE color, and its VALUE (the V in HSV
79 coordinates) is
80 assumed to be the image's lowest. Its H and S components are unused.
81 Pixels whose value is below this value + a user-defined threshold parameter
82 are
83 'outline-PRONE' pixels (even matchline-associated pixels can be).
84 They are assumed to be full outline pixels if their CHROMA (S*V) is above a
85 'Color threshold'. This condition lets the user settle how outline/matchline
86 disputed pixels should be considered.
87
88 - The Colormap tone for a pixel is extracted according to these rules:
89
90 * Paper pixels are completely transparent (tone = 255).
91
92 * Undisputed matchline colors build the tone upon the pixel's Saturation,
93 scaled
94 so that 1.0 maps to 0, and the saturation threshold for that matchline
95 color maps to 255.
96
97 * Undisputed Outline colors do similarly, with the Value.
98
99 * Disputed outline/matchline colors result in the blend PRODUCT of the above
100 tones.
101 This makes the tone smoother on outline/matchline intersections.
102 */
103
104 //**************************************************************************************
105 // Local namespace stuff
106 //**************************************************************************************
107
108 namespace {
109
110 // some useful functions for doing math
111
affMV1(const TAffine & aff,double v1,double v2)112 inline double affMV1(const TAffine &aff, double v1, double v2) {
113 return aff.a11 * v1 + aff.a12 * v2 + aff.a13;
114 }
115
116 //-------------------------------------------------------------------------
117
affMV2(const TAffine & aff,double v1,double v2)118 inline double affMV2(const TAffine &aff, double v1, double v2) {
119 return aff.a21 * v1 + aff.a22 * v2 + aff.a23;
120 }
121
122 //=========================================================================
123
124 //! Auxiliary class for HSV (toonz 4.x style)
125 struct HSVColor {
126 double m_h;
127 double m_s;
128 double m_v;
129
130 public:
HSVColor__anonb5261a090111::HSVColor131 HSVColor(double h = 0, double s = 0, double v = 0) : m_h(h), m_s(s), m_v(v) {}
132
133 static HSVColor fromRGB(double r, double g, double b);
134 };
135
136 //-------------------------------------------------------------------------
137
fromRGB(double r,double g,double b)138 HSVColor HSVColor::fromRGB(double r, double g, double b) {
139 double h, s, v;
140 double max, min, delta;
141
142 max = std::max({r, g, b});
143 min = std::min({r, g, b});
144
145 v = max;
146
147 if (max != 0)
148 s = (max - min) / max;
149 else
150 s = 0;
151
152 if (s == 0)
153 h = 0;
154 else {
155 delta = max - min;
156
157 if (r == max)
158 h = (g - b) / delta;
159 else if (g == max)
160 h = 2.0 + (b - r) / delta;
161 else if (b == max)
162 h = 4.0 + (r - g) / delta;
163 h = h * 60.0;
164 if (h < 0) h += 360.0;
165 }
166
167 return HSVColor(h, s, v);
168 }
169
170 //=========================================================================
171
172 //! Precomputation data about target colors
173 struct TargetColorData {
174 int m_idx; //!< Palette color index
175 HSVColor m_hsv; //!< HSV coordinates of the color
176 double m_saturationLower; //!< Pixel colors associated with this color must
177 //! be above this
178 double m_hueLower, m_hueUpper; //!< Pixel colors associated with this color
179 //! must in this range
180
181 public:
TargetColorData__anonb5261a090111::TargetColorData182 TargetColorData(const TargetColor &color)
183 : m_idx(-1)
184 , m_hsv(HSVColor::fromRGB(color.m_color.r / 255.0,
185 color.m_color.g / 255.0,
186 color.m_color.b / 255.0))
187 , m_saturationLower(1.0 - color.m_threshold / 100.0)
188 , m_hueLower(m_hsv.m_h - color.m_hRange * 0.5)
189 , m_hueUpper(m_hsv.m_h + color.m_hRange * 0.5) {
190 if (m_hueLower < 0.0) m_hueLower += 360.0;
191 if (m_hueUpper > 360.0) m_hueUpper -= 360.0;
192 }
193 };
194
195 //=========================================================================
196
197 //! Brightness/Contrast color transform data
198
199 #define MAX_N_PENCILS 8
200
201 /* the following must be updated at every change in palette content */
202 int N_pencils = 4; /* not counting autoclose */
203 TPixelRGBM32 Pencil[MAX_N_PENCILS + 1]; /* last is autoclose pencil */
204 int Pencil_index[MAX_N_PENCILS + 1]; /* "" */
205 int Pencil_id[MAX_N_PENCILS + 1]; /* "" */
206 TPixelRGBM32 Paper = TPixel32::White;
207
208 //=========================================================================
209
210 //! Brightness/Contrast color transform structure
211 class TransfFunction {
212 USHORT TransfFun[(MAX_N_PENCILS + 1) << 8];
213
setTransfFun(int pencil,int b1,int c1)214 void setTransfFun(int pencil, int b1, int c1) {
215 int i, p1, p2, brig, cont, max;
216
217 cont = 255 - c1;
218 brig = 255 - b1;
219 max = 255;
220 notLessThan(1, cont);
221 p2 = brig;
222 p1 = p2 - cont;
223
224 for (i = 0; i <= p1; i++) TransfFun[pencil << 8 | i] = 0;
225 for (; i < p2; i++)
226 TransfFun[pencil << 8 | i] = std::min(max, max * (i - p1) / cont);
227 for (; i < 256; i++) TransfFun[pencil << 8 | i] = max;
228 }
229
230 public:
TransfFunction(const TargetColors & colors)231 TransfFunction(const TargetColors &colors) {
232 memset(TransfFun, 0, sizeof TransfFun);
233 int count = std::min(colors.getColorCount(), MAX_N_PENCILS);
234 for (int p = 0; p < count; p++) {
235 int brightness = troundp(2.55 * colors.getColor(p).m_brightness);
236 int contrast = troundp(2.55 * colors.getColor(p).m_contrast);
237 setTransfFun(p, brightness, contrast);
238 }
239 }
240
getTransfFun()241 USHORT *getTransfFun() { return TransfFun; }
242 };
243
244 //=========================================================================
245
246 //! Brightness/Contrast functions
247
brightnessContrast(const TRasterCM32P & cm,const TargetColors & colors)248 void brightnessContrast(const TRasterCM32P &cm, const TargetColors &colors) {
249 TransfFunction transform(colors);
250 USHORT *transf_fun = transform.getTransfFun();
251
252 int ink, tone;
253 int newTone, newInk;
254
255 for (int y = 0; y < cm->getLy(); ++y) {
256 TPixelCM32 *pix = cm->pixels(y);
257 TPixelCM32 *endPix = pix + cm->getLx();
258
259 for (; pix < endPix; ++pix) {
260 tone = pix->getTone();
261 if (tone < 255) {
262 ink = pix->getInk();
263 newTone = transf_fun[ink << 8 | tone];
264 newInk = (newTone == 255) ? 0 : colors.getColor(ink).m_index;
265
266 *pix = TPixelCM32(newInk, 0, newTone);
267 }
268 }
269 }
270 }
271
272 //------------------------------------------------------------------------------------
273
brightnessContrastGR8(const TRasterCM32P & cm,const TargetColors & colors)274 void brightnessContrastGR8(const TRasterCM32P &cm, const TargetColors &colors) {
275 TransfFunction transform(colors);
276 USHORT *transf_fun = transform.getTransfFun();
277
278 int val, black = colors.getColor(1).m_index;
279
280 for (int y = 0; y < cm->getLy(); ++y) {
281 TPixelCM32 *pix = cm->pixels(y);
282 TPixelCM32 *endPix = pix + cm->getLx();
283
284 for (; pix < endPix; ++pix) {
285 val = transf_fun[pix->getValue() + 256];
286 *pix = (val < 255) ? TPixelCM32(black, 0, val) : TPixelCM32();
287 }
288 }
289 }
290
291 //=========================================================================
292
293 //! Transparency check
294
transparencyCheck(const TRasterCM32P & cmin,const TRaster32P & rasout)295 void transparencyCheck(const TRasterCM32P &cmin, const TRaster32P &rasout) {
296 for (int y = 0; y < cmin->getLy(); ++y) {
297 TPixelCM32 *pix = cmin->pixels(y);
298 TPixelCM32 *endPix = pix + cmin->getLx();
299
300 TPixel32 *outPix = rasout->pixels(y);
301 for (; pix < endPix; ++pix, ++outPix) {
302 int ink = pix->getInk();
303 int tone = pix->getTone();
304
305 if (ink == 4095)
306 *outPix = TPixel32::Green;
307 else
308 *outPix = (tone == 0) ? TPixel32::Black
309 : (tone == 255) ? TPixel32::White : TPixel32::Red;
310 }
311 }
312 }
313
314 } // namespace
315
316 //**************************************************************************************
317 // TCleanupper implementation - elementary functions
318 //**************************************************************************************
319
instance()320 TCleanupper *TCleanupper::instance() {
321 static TCleanupper theCleanupper;
322 return &theCleanupper;
323 }
324
325 //------------------------------------------------------------------------------------
326
setParameters(CleanupParameters * parameters)327 void TCleanupper::setParameters(CleanupParameters *parameters) {
328 m_parameters = parameters;
329 }
330
331 //------------------------------------------------------------------------------------
332
createToonzPaletteFromCleanupPalette()333 TPalette *TCleanupper::createToonzPaletteFromCleanupPalette() {
334 TPalette *cleanupPalette = m_parameters->m_cleanupPalette.getPointer();
335 return createToonzPalette(cleanupPalette, 1);
336 }
337
338 //**************************************************************************************
339 // CleanupProcessedImage implementation
340 //**************************************************************************************
341
getImg() const342 TToonzImageP CleanupPreprocessedImage::getImg() const {
343 return (TToonzImageP)(TImageCache::instance()->get(m_imgId, true));
344 }
345
346 //-----------------------------------------------------------------------------------
347
CleanupPreprocessedImage(CleanupParameters * parameters,TToonzImageP processed,bool fromGr8)348 CleanupPreprocessedImage::CleanupPreprocessedImage(
349 CleanupParameters *parameters, TToonzImageP processed, bool fromGr8)
350 : m_wasFromGR8(fromGr8)
351 , m_autocentered(false)
352 , m_size(processed->getSize()) {
353 if (!processed)
354 m_imgId = "";
355 else {
356 m_imgId = TImageCache::instance()->getUniqueId();
357 assert(!processed->getRaster()->getParent());
358 TImageCache::instance()->add(m_imgId, (TImageP)processed);
359 }
360
361 if (!m_wasFromGR8) {
362 const TPixel32 white(255, 255, 255, 0);
363 for (int i = 0; i < parameters->m_colors.getColorCount(); ++i) {
364 TPixel32 cc = parameters->m_colors.getColor(i).m_color;
365 for (int tone = 0; tone < 256; tone++) {
366 m_pixelsLut.push_back(blend(parameters->m_colors.getColor(i).m_color,
367 white, tone, TPixelCM32::getMaxTone()));
368 }
369 }
370 }
371 }
372
373 //-----------------------------------------------------------------------------------
374
~CleanupPreprocessedImage()375 CleanupPreprocessedImage::~CleanupPreprocessedImage() {
376 TImageCache::instance()->remove(m_imgId);
377 }
378
379 //-----------------------------------------------------------------------------------
380
getPreviewImage() const381 TRasterImageP CleanupPreprocessedImage::getPreviewImage() const {
382 TRaster32P ras(getSize());
383 TRasterImageP ri(ras);
384 double xdpi = 0, ydpi = 0;
385 getImg()->getDpi(xdpi, ydpi);
386 ri->setDpi(xdpi, ydpi);
387 return ri;
388 }
389
390 //**************************************************************************************
391 // TCleanupper implementation - Process functions
392 //**************************************************************************************
393
getResampleValues(const TRasterImageP & image,TAffine & aff,double & blur,TDimension & outDim,TPointD & outDpi,bool isCameraTest,bool & isSameDpi)394 bool TCleanupper::getResampleValues(const TRasterImageP &image, TAffine &aff,
395 double &blur, TDimension &outDim,
396 TPointD &outDpi, bool isCameraTest,
397 bool &isSameDpi) {
398 double outlp, outlq;
399 double scalex, scaley;
400 double cxin, cyin, cpout, cqout;
401 double max_blur;
402 TPointD dpi;
403
404 // Locking the input image to be cleanupped
405 image->getRaster()->lock();
406
407 // Retrieve image infos
408 int rasterLx = image->getRaster()->getLx();
409 int rasterLy = image->getRaster()->getLy();
410
411 /*---入力画像サイズとSaveBoxのサイズが一致しているか?の判定---*/
412 TRect saveBox = image->getSavebox();
413 bool raster_is_savebox = true;
414 if (saveBox == TRect() &&
415 ((saveBox.getLx() > 0 && saveBox.getLx() < rasterLx) ||
416 (saveBox.getLy() > 0 && saveBox.getLy() < rasterLy)))
417 raster_is_savebox = false;
418
419 // Use the same source dpi throughout the level
420 dpi = getSourceDpi();
421 if (dpi == TPointD())
422 dpi.x = dpi.y = 65.0; // using 65.0 as default DPI //??????WHY
423 else if (!dpi.x)
424 dpi.x = dpi.y;
425 else if (!dpi.y)
426 dpi.y = dpi.x;
427
428 // Retrieve some cleanup parameters
429 int rotate = m_parameters->m_rotate;
430
431 // Build scaling/dpi data
432 {
433 m_parameters->getOutputImageInfo(outDim, outDpi.x, outDpi.y);
434
435 // input -> output scale factor
436 scalex = outDpi.x / dpi.x;
437 scaley = outDpi.y / dpi.y;
438
439 outlp = outDim.lx;
440 outlq = outDim.ly;
441 }
442
443 /*---
444 * 拡大/縮小をしていない場合(DPIが変わらない場合)、NearestNeighborでリサンプリングする。---*/
445 isSameDpi = areAlmostEqual(outDpi.x, dpi.x, 0.1) &&
446 areAlmostEqual(outDpi.y, dpi.y, 0.1);
447
448 // Retrieve input center
449 if (raster_is_savebox) {
450 cxin = -saveBox.getP00().x + (saveBox.getLx() - 1) / 2.0;
451 cyin = -saveBox.getP00().y + (saveBox.getLy() - 1) / 2.0;
452 } else {
453 cxin = (rasterLx - 1) / 2.0;
454 cyin = (rasterLy - 1) / 2.0;
455 }
456
457 // Retrieve output center
458 cpout = (outlp - 1) / 2.0;
459 cqout = (outlq - 1) / 2.0;
460 // Perform autocenter if any is found
461 double angle = 0.0;
462 double skew = 0.0;
463 TAffine pre_aff;
464 image->getRaster()->lock();
465
466 bool autocentered =
467 doAutocenter(angle, skew, cxin, cyin, cqout, cpout, dpi.x, dpi.y,
468 raster_is_savebox, saveBox, image, scalex);
469 image->getRaster()->unlock();
470
471 // Build the image transform as deduced by the autocenter
472 if (m_parameters->m_autocenterType == AUTOCENTER_CTR && skew) {
473 pre_aff.a11 = cos(skew * M_PI_180);
474 pre_aff.a21 = sin(skew * M_PI_180);
475 }
476
477 aff = (TScale(scalex, scaley) * pre_aff) * TRotation(angle);
478 aff = aff.place(cxin, cyin, cpout, cqout);
479
480 // Apply eventual additional user-defined transforms
481 TPointD pout = TPointD((outlp - 1) / 2.0, (outlq - 1) / 2.0);
482
483 if (m_parameters->m_rotate != 0)
484 aff = TRotation(-(double)m_parameters->m_rotate).place(pout, pout) * aff;
485
486 if (m_parameters->m_flipx || m_parameters->m_flipy)
487 aff = TScale(m_parameters->m_flipx ? -1 : 1, m_parameters->m_flipy ? -1 : 1)
488 .place(pout, pout) *
489 aff;
490
491 if (!isCameraTest)
492 aff = TTranslation(m_parameters->m_offx * outDpi.x / 2,
493 m_parameters->m_offy * outDpi.y / 2) *
494 aff;
495
496 max_blur = 20.0 * sqrt(fabs(scalex /*** * oversample_factor ***/));
497 blur = pow(max_blur, (100 - m_parameters->m_sharpness) / (100 - 1));
498 return autocentered;
499 }
500
501 //------------------------------------------------------------------------------------
502
503 // this one incorporate the preprocessColors and the finalize function; used for
504 // swatch.(typically on very small rasters)
processColors(const TRasterP & rin)505 TRasterP TCleanupper::processColors(const TRasterP &rin) {
506 if (m_parameters->m_lineProcessingMode == lpNone) return rin;
507
508 TRasterCM32P rcm = TRasterCM32P(rin->getSize());
509 if (!rcm) {
510 assert(!"failed finalRas allocation!");
511 return TRasterCM32P();
512 }
513
514 // Copy current cleanup palette to parameters' colors
515 m_parameters->m_colors.update(m_parameters->m_cleanupPalette.getPointer(),
516 m_parameters->m_noAntialias);
517
518 bool toGr8 = (m_parameters->m_lineProcessingMode == lpGrey);
519 if (toGr8) {
520 // No (color) processing. Not even thresholding. This just means that all
521 // the important
522 // stuff here is made in the brightness/contrast stage...
523
524 // NOTE: Most of the color processing should be DISABLED in this case!!
525
526 // finalRas->clear();
527 rin->lock();
528 rcm->lock();
529
530 if (TRasterGR8P(rin)) {
531 UCHAR *rowin = rin->getRawData();
532 TUINT32 *rowout = reinterpret_cast<TUINT32 *>(rcm->getRawData());
533 for (int i = 0; i < rin->getLy(); i++) {
534 for (int j = 0; j < rin->getLx(); j++)
535 *rowout++ = *rowin++; // Direct copy for now... :(
536 rowin += rin->getWrap() - rin->getLx();
537 rowout += rcm->getWrap() - rcm->getLx();
538 }
539 } else {
540 TPixel32 *rowin = reinterpret_cast<TPixel32 *>(rin->getRawData());
541 TUINT32 *rowout = reinterpret_cast<TUINT32 *>(rcm->getRawData());
542 for (int i = 0; i < rin->getLy(); i++) {
543 for (int j = 0; j < rin->getLx(); j++)
544 *rowout++ = TPixelGR8::from(*rowin++).value;
545 rowin += rin->getWrap() - rin->getLx();
546 rowout += rcm->getWrap() - rcm->getLx();
547 }
548 }
549
550 rin->unlock();
551 rcm->unlock();
552 } else {
553 assert(TRaster32P(rin));
554 preprocessColors(rcm, rin, m_parameters->m_colors);
555 }
556
557 // outImg->setDpi(outDpi.x, outDpi.y);
558 CleanupPreprocessedImage cpi(m_parameters,
559 TToonzImageP(rcm, rcm->getBounds()), toGr8);
560 cpi.m_autocentered = true;
561
562 TRaster32P rout = TRaster32P(rin->getSize());
563 finalize(rout, &cpi);
564 return rout;
565 }
566
567 //------------------------------------------------------------------------------------
568
process(TRasterImageP & image,bool first_image,TRasterImageP & onlyResampledImage,bool isCameraTest,bool returnResampled,bool onlyForSwatch,TAffine * resampleAff)569 CleanupPreprocessedImage *TCleanupper::process(
570 TRasterImageP &image, bool first_image, TRasterImageP &onlyResampledImage,
571 bool isCameraTest, bool returnResampled, bool onlyForSwatch,
572 TAffine *resampleAff) {
573 TAffine aff;
574 double blur;
575 TDimension outDim(0, 0);
576 TPointD outDpi;
577
578 bool isSameDpi = false;
579 bool autocentered = getResampleValues(image, aff, blur, outDim, outDpi,
580 isCameraTest, isSameDpi);
581 if (m_parameters->m_autocenterType != AUTOCENTER_NONE && !autocentered)
582 DVGui::warning(
583 QObject::tr("The autocentering failed on the current drawing."));
584
585 bool fromGr8 = (bool)TRasterGR8P(image->getRaster());
586 bool toGr8 = (m_parameters->m_lineProcessingMode == lpGrey);
587
588 // If necessary, perform auto-adjust
589 if (!isCameraTest && m_parameters->m_lineProcessingMode != lpNone && toGr8 &&
590 m_parameters->m_autoAdjustMode != AUTO_ADJ_NONE && !onlyForSwatch) {
591 static int ref_cum[256];
592 UCHAR lut[256];
593 int cum[256];
594 double x0_src_f, y0_src_f, x1_src_f, y1_src_f;
595 int x0_src, y0_src, x1_src, y1_src;
596
597 // cleanup_message("Autoadjusting... \n");
598
599 TAffine inv = aff.inv();
600
601 x0_src_f = affMV1(inv, 0, 0);
602 y0_src_f = affMV2(inv, 0, 0);
603 x1_src_f = affMV1(inv, outDim.lx - 1, outDim.ly - 1);
604 y1_src_f = affMV2(inv, outDim.lx - 1, outDim.ly - 1);
605
606 x0_src = tround(x0_src_f);
607 y0_src = tround(y0_src_f);
608 x1_src = tround(x1_src_f);
609 y1_src = tround(y1_src_f);
610
611 set_autoadjust_window(x0_src, y0_src, x1_src, y1_src);
612
613 if (!TRasterGR8P(image->getRaster())) {
614 // Auto-adjusting a 32-bit image. This means that a white background must
615 // be introduced first.
616 TRaster32P ras32(image->getRaster()->clone());
617 TRop::addBackground(ras32, TPixel32::White);
618 image = TRasterImageP(ras32); // old image is released here
619 ras32 = TRaster32P();
620
621 TRasterGR8P rgr(image->getRaster()->getSize());
622 TRop::copy(rgr, image->getRaster());
623
624 // This is now legit. It was NOT before the clone, since the original
625 // could be cached.
626 image->setRaster(rgr);
627 }
628 switch (m_parameters->m_autoAdjustMode) {
629 case AUTO_ADJ_HISTOGRAM:
630 if (first_image) {
631 build_gr_cum(image, ref_cum);
632 } else {
633 build_gr_cum(image, cum);
634 build_gr_lut(ref_cum, cum, lut);
635 apply_lut(image, lut);
636 }
637 break;
638
639 case AUTO_ADJ_HISTO_L:
640 histo_l_algo(image, first_image);
641 break;
642
643 case AUTO_ADJ_BLACK_EQ:
644 black_eq_algo(image);
645 break;
646
647 case AUTO_ADJ_NONE:
648 default:
649 assert(false);
650 break;
651 }
652 }
653
654 fromGr8 = (bool)TRasterGR8P(
655 image->getRaster()); // may have changed type due to auto-adjust
656
657 assert(returnResampled ||
658 !onlyForSwatch); // if onlyForSwatch, then returnResampled
659
660 // Allocate output colormap raster
661 TRasterCM32P finalRas;
662 if (!onlyForSwatch) {
663 finalRas = TRasterCM32P(outDim);
664 if (!finalRas) {
665 TImageCache::instance()->outputMap(outDim.lx * outDim.ly * 4,
666 "C:\\cachelog");
667 assert(!"failed finalRas allocation!");
668 return 0;
669 }
670 }
671
672 // In case the input raster was a greymap, we cannot reutilize finalRas's
673 // buffer to transform the final
674 // fullcolor pixels to colormap pixels directly (1 32-bit pixel would hold 4
675 // 8-bit pixels) - therefore,
676 // a secondary greymap is allocated.
677
678 // NOTE: This should be considered obsolete? By using TRop::resample(
679 // <TRaster32P& instance> , ...) we
680 // should get the same effect!!
681
682 TRasterP tmp_ras;
683
684 if (returnResampled || (fromGr8 && toGr8)) {
685 if (fromGr8 && toGr8)
686 tmp_ras = TRasterGR8P(outDim);
687 else
688 tmp_ras = TRaster32P(outDim);
689
690 if (!tmp_ras) {
691 TImageCache::instance()->outputMap(outDim.lx * outDim.ly * 4,
692 "C:\\cachelog");
693 assert(!"failed tmp_ras allocation!");
694 return 0;
695 }
696 } else
697 // if finalRas is allocated, and the intermediate raster has to be 32-bit,
698 // we can perform pixel
699 // conversion directly on the same output buffer
700 tmp_ras = TRaster32P(outDim.lx, outDim.ly, outDim.lx,
701 (TPixel32 *)finalRas->getRawData());
702
703 TRop::ResampleFilterType flt_type;
704 if (isSameDpi)
705 flt_type = TRop::ClosestPixel; // NearestNeighbor
706 else if (isCameraTest)
707 flt_type = TRop::Triangle;
708 else
709 flt_type = TRop::Hann2;
710 TRop::resample(tmp_ras, image->getRaster(), aff, flt_type, blur);
711
712 if ((TRaster32P)tmp_ras)
713 // Add white background to deal with semitransparent pixels
714 TRop::addBackground(tmp_ras, TPixel32::White);
715
716 if (resampleAff) *resampleAff = aff;
717
718 image->getRaster()->unlock();
719 image = TRasterImageP();
720
721 if (returnResampled) {
722 onlyResampledImage = TRasterImageP(tmp_ras);
723 onlyResampledImage->setDpi(outDpi.x, outDpi.y);
724 }
725
726 if (onlyForSwatch) return 0;
727
728 assert(finalRas);
729
730 // Copy current cleanup palette to parameters' colors
731 m_parameters->m_colors.update(m_parameters->m_cleanupPalette.getPointer(),
732 m_parameters->m_noAntialias);
733
734 if (toGr8) {
735 // No (color) processing. Not even thresholding. This just means that all
736 // the important
737 // stuff here is made in the brightness/contrast stage...
738
739 // NOTE: Most of the color processing should be DISABLED in this case!!
740
741 tmp_ras->lock();
742 finalRas->lock();
743 assert(tmp_ras->getSize() == finalRas->getSize());
744 assert(tmp_ras->getLx() == tmp_ras->getWrap());
745 assert(finalRas->getLx() == finalRas->getWrap());
746
747 int pixCount = outDim.lx * outDim.ly;
748
749 if (fromGr8) {
750 UCHAR *rowin = tmp_ras->getRawData();
751 TUINT32 *rowout = reinterpret_cast<TUINT32 *>(finalRas->getRawData());
752 for (int i = 0; i < pixCount; i++)
753 *rowout++ = *rowin++; // Direct copy for now... :(
754 } else {
755 TPixel32 *rowin = reinterpret_cast<TPixel32 *>(tmp_ras->getRawData());
756 TUINT32 *rowout = reinterpret_cast<TUINT32 *>(finalRas->getRawData());
757 for (int i = 0; i < pixCount; i++)
758 *rowout++ = TPixelGR8::from(*rowin++).value;
759 }
760
761 tmp_ras->unlock();
762 finalRas->unlock();
763 } else {
764 // WARNING: finalRas and tmp_ras may share the SAME buffer!
765 assert(TRaster32P(tmp_ras));
766 preprocessColors(finalRas, tmp_ras, m_parameters->m_colors);
767 }
768
769 TToonzImageP final;
770 final = TToonzImageP(finalRas, finalRas->getBounds());
771 final->setDpi(outDpi.x, outDpi.y);
772
773 CleanupPreprocessedImage *cpi =
774 new CleanupPreprocessedImage(m_parameters, final, toGr8);
775 cpi->m_autocentered = autocentered;
776 cpi->m_appliedAff = aff;
777 return cpi;
778 }
779
780 //------------------------------------------------------------------------------------
781
autocenterOnly(const TRasterImageP & image,bool isCameraTest,bool & autocentered)782 TRasterImageP TCleanupper::autocenterOnly(const TRasterImageP &image,
783 bool isCameraTest,
784 bool &autocentered) {
785 double xDpi, yDpi;
786 // double inlx, inly, zoom_factor, max_blur;
787 double skew = 0, angle = 0,
788 dist = 0 /*lq_nozoom, lp_nozoom,, cx, cy, scalex, scaley*/;
789 double cxin, cyin, cpout, cqout;
790 int rasterIsSavebox = true;
791 TAffine aff, preAff, inv;
792 int rasterLx, finalLx, rasterLy, finalLy;
793
794 rasterLx = finalLx = image->getRaster()->getLx();
795 rasterLy = finalLy = image->getRaster()->getLy();
796
797 TRect saveBox = image->getSavebox();
798 if ((saveBox == TRect()) &&
799 ((saveBox.getLx() > 0 && saveBox.getLx() < rasterLx) ||
800 (saveBox.getLy() > 0 && saveBox.getLy() < rasterLy)))
801 rasterIsSavebox = false;
802
803 int rotate = m_parameters->m_rotate;
804
805 image->getDpi(xDpi, yDpi);
806
807 if (!xDpi) // using 65.0 as default DPI
808 xDpi = (yDpi ? yDpi : 65);
809
810 if (!yDpi) yDpi = (xDpi ? xDpi : 65);
811
812 if (rasterIsSavebox) {
813 cxin = -saveBox.getP00().x + (saveBox.getLx() - 1) / 2.0;
814 cyin = -saveBox.getP00().y + (saveBox.getLy() - 1) / 2.0;
815 } else {
816 cxin = (rasterLx - 1) / 2.0;
817 cyin = (rasterLy - 1) / 2.0;
818 }
819
820 cpout = (rasterLx - 1) / 2.0;
821 cqout = (rasterLy - 1) / 2.0;
822
823 if (m_parameters->m_autocenterType != AUTOCENTER_NONE)
824 autocentered = doAutocenter(angle, skew, cxin, cyin, cqout, cpout, xDpi,
825 yDpi, rasterIsSavebox, saveBox, image, 1.0);
826 else
827 autocentered = true;
828
829 if (m_parameters->m_autocenterType == AUTOCENTER_CTR && skew) {
830 aff.a11 = cos(skew * M_PI_180);
831 aff.a21 = sin(skew * M_PI_180);
832 }
833
834 aff = aff * TRotation(angle);
835
836 aff = aff.place(cxin, cyin, cpout, cqout);
837
838 if (rotate != 0 && rotate != 180) std::swap(finalLx, finalLy);
839
840 TPointD pin = TPointD((rasterLx - 1) / 2.0, (rasterLy - 1) / 2.0);
841 TPointD pout = TPointD((finalLx - 1) / 2.0, (finalLy - 1) / 2.0);
842
843 if (rotate != 0) aff = TRotation(-(double)rotate).place(pin, pout) * aff;
844
845 if (m_parameters->m_flipx || m_parameters->m_flipy)
846 aff = TScale(m_parameters->m_flipx ? -1 : 1, m_parameters->m_flipy ? -1 : 1)
847 .place(pout, pout) *
848 aff;
849
850 if (!isCameraTest)
851 aff = TTranslation(m_parameters->m_offx * xDpi / 2,
852 m_parameters->m_offy * yDpi / 2) *
853 aff;
854
855 TRasterP tmpRas;
856 TPoint dp;
857 if (isCameraTest) // in cameratest, I don't want to crop the image to be
858 // shown.
859 // so, I resample without cropping, and I compute the offset needed to have it
860 // autocentered.
861 // That offset is stored in the RasterImage(setOffset below) and then used
862 // when displaying the image in camerastand (in method
863 // RasterPainter::onRasterImage)
864 {
865 // TPointD srcActualCenter = aff.inv()*TPointD(finalLx/2.0, finalLy/2.0);//
866 // the autocenter position in the source image
867 // TPointD srcCenter = imageToResample->getRaster()->getCenterD();*/
868 TPointD dstActualCenter = TPointD(finalLx / 2.0, finalLy / 2.0);
869 TPointD dstCenter = aff * image->getRaster()->getCenterD();
870
871 dp = convert(
872 dstCenter -
873 dstActualCenter); // the amount to be offset in the destination image.
874
875 TRect r = convert(aff * convert(image->getRaster()->getBounds()));
876 aff = (TTranslation(convert(-r.getP00())) * aff);
877 // aff = aff.place(srcActualCenter, dstActualCenter);
878 tmpRas = image->getRaster()->create(r.getLx(), r.getLy());
879
880 } else
881 tmpRas = image->getRaster()->create(finalLx, finalLy);
882
883 TRop::resample(tmpRas, image->getRaster(), aff);
884 // TImageWriter::save(TFilePath("C:\\temp\\incleanup.tif"), imageToResample);
885 // TImageWriter::save(TFilePath("C:\\temp\\outcleanup.tif"), tmp_ras);
886
887 TRasterImageP final(tmpRas);
888 final->setOffset(dp);
889
890 final->setDpi(xDpi, yDpi);
891 // final->sethPos(finalHPos);
892
893 return final;
894 }
895
896 //**************************************************************************************
897 // AutoCenter
898 //**************************************************************************************
899
doAutocenter(double & angle,double & skew,double & cxin,double & cyin,double & cqout,double & cpout,const double xdpi,const double ydpi,const int raster_is_savebox,const TRect saveBox,const TRasterImageP & image,const double scalex)900 bool TCleanupper::doAutocenter(
901
902 double &angle, double &skew, double &cxin, double &cyin, double &cqout,
903 double &cpout,
904
905 const double xdpi, const double ydpi, const int raster_is_savebox,
906 const TRect saveBox, const TRasterImageP &image, const double scalex) {
907 double sigma = 0, theta = 0;
908 FDG_INFO fdg_info = m_parameters->getFdgInfo();
909
910 switch (m_parameters->m_autocenterType) {
911 case AUTOCENTER_CTR:
912 angle = fdg_info.ctr_angle;
913 skew = fdg_info.ctr_skew;
914 cxin = mmToPixel(fdg_info.ctr_x, xdpi);
915 cyin = mmToPixel(fdg_info.ctr_y, ydpi);
916 if (raster_is_savebox) {
917 cxin -= saveBox.getP00().x;
918 cyin -= saveBox.getP00().y;
919 }
920
921 break;
922
923 case AUTOCENTER_FDG: {
924 // e se image->raster_is_savebox?
925 // cleanup_message ("Autocentering...");
926 int strip_width = compute_strip_pixel(&fdg_info, xdpi) + 1; /* ?!? */
927
928 switch (m_parameters->m_pegSide) {
929 case PEGS_BOTTOM:
930 sigma = 0.0;
931 break;
932 case PEGS_RIGHT:
933 sigma = 90.0;
934 break;
935 case PEGS_TOP:
936 sigma = 180.0;
937 break;
938 case PEGS_LEFT:
939 sigma = -90.0;
940 break;
941 default:
942 sigma = 0.0;
943 break;
944 }
945
946 theta = sigma;
947 if (theta > 180.0)
948 theta -= 360.0;
949 else if (theta <= -180.0)
950 theta += 360.0;
951
952 PEGS_SIDE pegs_ras_side;
953 if (theta == 0.0)
954 pegs_ras_side = PEGS_BOTTOM;
955 else if (theta == 90.0)
956 pegs_ras_side = PEGS_RIGHT;
957 else if (theta == 180.0)
958 pegs_ras_side = PEGS_TOP;
959 else if (theta == -90.0)
960 pegs_ras_side = PEGS_LEFT;
961 else
962 pegs_ras_side = PEGS_BOTTOM;
963
964 switch (pegs_ras_side) {
965 case PEGS_LEFT:
966 case PEGS_RIGHT:
967 notMoreThan(image->getRaster()->getLx(), strip_width);
968 break;
969 default:
970 notMoreThan(image->getRaster()->getLy(), strip_width);
971 break;
972 }
973
974 convert_dots_mm_to_pixel(&fdg_info.dots[0], fdg_info.dots.size(), xdpi,
975 ydpi);
976
977 double cx, cy;
978 if (!get_image_rotation_and_center(
979 image->getRaster(), strip_width, pegs_ras_side, &angle, &cx, &cy,
980 &fdg_info.dots[0], fdg_info.dots.size())) {
981 return false;
982 } else {
983 angle *= M_180_PI;
984 cxin = cx;
985 cyin = cy;
986 double dist =
987 (double)mmToPixel(fdg_info.dist_ctr_to_ctr_hole, xdpi * scalex);
988 switch (m_parameters->m_pegSide) {
989 case PEGS_BOTTOM:
990 cqout -= dist;
991 break;
992 case PEGS_TOP:
993 cqout += dist;
994 break;
995 case PEGS_LEFT:
996 cpout -= dist;
997 break;
998 case PEGS_RIGHT:
999 cpout += dist;
1000 break;
1001 default:
1002 // bad pegs side
1003 return false;
1004 }
1005 }
1006 fdg_info.dots.clear();
1007
1008 break;
1009 }
1010
1011 default:
1012 return false;
1013 }
1014
1015 return true;
1016 }
1017
1018 //**************************************************************************************
1019 // (Pre) Processing (ie the core Cleanup procedure)
1020 //**************************************************************************************
1021
preprocessColor(const TPixel32 & pix,const TargetColorData & blackColor,const std::vector<TargetColorData> & featureColors,int nFeatures,TPixelCM32 & outpix)1022 inline void preprocessColor(const TPixel32 &pix,
1023 const TargetColorData &blackColor,
1024 const std::vector<TargetColorData> &featureColors,
1025 int nFeatures, TPixelCM32 &outpix) {
1026 // Translate the pixel to HSV
1027 HSVColor pixHSV(
1028 HSVColor::fromRGB(pix.r / 255.0, pix.g / 255.0, pix.b / 255.0));
1029
1030 // First, check against matchline colors. This is needed as outline pixels'
1031 // tone is based upon that
1032 // extracted here.
1033 int idx = -1, tone = 255;
1034 double hDist = (std::numeric_limits<double>::max)(), newHDist;
1035
1036 for (int i = 0; i < nFeatures; ++i) {
1037 const TargetColorData &fColor = featureColors[i];
1038
1039 // Feature Color
1040
1041 // Retrieve the hue distance and, in case it's less than current one, this
1042 // idx better
1043 // approximates the color.
1044 newHDist = (pixHSV.m_h > fColor.m_hsv.m_h)
1045 ? std::min(pixHSV.m_h - fColor.m_hsv.m_h,
1046 fColor.m_hsv.m_h - pixHSV.m_h + 360.0)
1047 : std::min(fColor.m_hsv.m_h - pixHSV.m_h,
1048 pixHSV.m_h - fColor.m_hsv.m_h + 360.0);
1049 if (newHDist < hDist) {
1050 hDist = newHDist;
1051 idx = i;
1052 }
1053 }
1054
1055 if (idx >= 0) {
1056 const TargetColorData &fColor = featureColors[idx];
1057
1058 // First, perform saturation check
1059 bool saturationOk = (pixHSV.m_s > fColor.m_saturationLower) &&
1060 ((fColor.m_hueLower <= fColor.m_hueUpper)
1061 ? (pixHSV.m_h >= fColor.m_hueLower) &&
1062 (pixHSV.m_h <= fColor.m_hueUpper)
1063 : (pixHSV.m_h >= fColor.m_hueLower) ||
1064 (pixHSV.m_h <= fColor.m_hueUpper));
1065
1066 if (saturationOk) {
1067 tone = 255.0 * (1.0 - pixHSV.m_s) / (1.0 - fColor.m_saturationLower);
1068 idx = fColor.m_idx;
1069 } else
1070 idx = -1;
1071 }
1072
1073 // Check against outline color
1074 if (pixHSV.m_v < blackColor.m_hsv.m_v) {
1075 // Outline-sensitive tone is imposed when the value check passes
1076 tone = (tone * pixHSV.m_v / blackColor.m_hsv.m_v);
1077
1078 // A further Chroma test is applied to decide whether a would-be outline
1079 // color
1080 // is to be intended as a matchline color instead (it has too much color)
1081 if ((idx < 0) || (pixHSV.m_s * pixHSV.m_v) < blackColor.m_saturationLower)
1082 // Outline Color
1083 idx = 1;
1084 }
1085
1086 outpix = (idx > 0 && tone < 255) ? TPixelCM32(idx, 0, tone) : TPixelCM32();
1087 }
1088
1089 //-----------------------------------------------------------------------------------------
1090
preprocessColors(const TRasterCM32P & outRas,const TRaster32P & raster32,const TargetColors & colors)1091 void TCleanupper::preprocessColors(const TRasterCM32P &outRas,
1092 const TRaster32P &raster32,
1093 const TargetColors &colors) {
1094 assert(outRas && outRas->getSize() == raster32->getSize());
1095
1096 // Convert the target palette to HSV colorspace
1097 std::vector<TargetColorData> pencilsHSV;
1098 for (int i = 2; i < colors.getColorCount(); ++i) {
1099 TargetColorData cdata(colors.getColor(i));
1100 cdata.m_idx = i;
1101 pencilsHSV.push_back(cdata);
1102 }
1103
1104 // Extract the 'black' Value
1105 TargetColor black = colors.getColor(1);
1106 TargetColorData blackData(black);
1107 blackData.m_hsv.m_v += (1.0 - black.m_threshold / 100.0);
1108 blackData.m_saturationLower = sq(1.0 - black.m_hRange / 100.0);
1109
1110 raster32->lock();
1111 outRas->lock();
1112
1113 // For every image pixel, process it
1114 for (int j = 0; j < raster32->getLy(); j++) {
1115 TPixel32 *pix = raster32->pixels(j);
1116 TPixel32 *endPix = pix + raster32->getLx();
1117 TPixelCM32 *outPix = outRas->pixels(j);
1118
1119 while (pix < endPix) {
1120 if (*pix == TPixel32::White ||
1121 pix->m <
1122 255) // sometimes the resampling produces semitransparent pixels
1123 // on the border of the raster; I discards those pixels.
1124 //(which otherwise creates a black border in the final cleanupped image)
1125 // vinz
1126 *outPix = TPixelCM32();
1127 else
1128 preprocessColor(*pix, blackData, pencilsHSV, pencilsHSV.size(),
1129 *outPix);
1130
1131 pix++;
1132 outPix++;
1133 }
1134 }
1135
1136 raster32->unlock();
1137 outRas->unlock();
1138 }
1139
1140 //**************************************************************************************
1141 // Post-Processing
1142 //**************************************************************************************
1143
finalize(const TRaster32P & outRas,CleanupPreprocessedImage * srcImg)1144 void TCleanupper::finalize(const TRaster32P &outRas,
1145 CleanupPreprocessedImage *srcImg) {
1146 if (!outRas) return;
1147
1148 if (srcImg->m_wasFromGR8)
1149 doPostProcessingGR8(outRas, srcImg);
1150 else
1151 doPostProcessingColor(outRas, srcImg);
1152 }
1153
1154 //-----------------------------------------------------------------------------------------
1155
finalize(CleanupPreprocessedImage * src,bool isCleanupper)1156 TToonzImageP TCleanupper::finalize(CleanupPreprocessedImage *src,
1157 bool isCleanupper) {
1158 if (src->m_wasFromGR8)
1159 return doPostProcessingGR8(src);
1160 else
1161 return doPostProcessingColor(src->getImg(), isCleanupper);
1162 }
1163
1164 //-----------------------------------------------------------------------------------------
1165
doPostProcessingGR8(const TRaster32P & outRas,CleanupPreprocessedImage * srcImg)1166 void TCleanupper::doPostProcessingGR8(const TRaster32P &outRas,
1167 CleanupPreprocessedImage *srcImg) {
1168 TToonzImageP image = srcImg->getImg();
1169 TRasterCM32P rasCM32 = image->getRaster();
1170
1171 rasCM32->lock();
1172 outRas->lock();
1173
1174 TRasterCM32P cmout(outRas->getLx(), outRas->getLy(), outRas->getWrap(),
1175 (TPixelCM32 *)outRas->getRawData());
1176 TRop::copy(cmout, rasCM32);
1177
1178 rasCM32->unlock();
1179
1180 // Apply brightness/contrast and grayscale conversion directly
1181 brightnessContrastGR8(cmout, m_parameters->m_colors);
1182
1183 // Apply despeckling
1184 if (m_parameters->m_despeckling)
1185 TRop::despeckle(cmout, m_parameters->m_despeckling,
1186 m_parameters->m_transparencyCheckEnabled);
1187
1188 // Morphological antialiasing
1189 if (m_parameters->m_postAntialias) {
1190 TRasterCM32P newRas(cmout->getLx(), cmout->getLy());
1191 TRop::antialias(cmout, newRas, 10, m_parameters->m_aaValue);
1192
1193 cmout->unlock();
1194 cmout = newRas;
1195 cmout->lock();
1196 }
1197
1198 // Finally, do transparency check
1199 if (m_parameters->m_transparencyCheckEnabled)
1200 transparencyCheck(cmout, outRas);
1201 else
1202 // TRop::convert(outRas, cmout, m_parameters->m_cleanupPalette);
1203 TRop::convert(outRas, cmout, createToonzPaletteFromCleanupPalette());
1204
1205 outRas->unlock();
1206 }
1207
1208 //-----------------------------------------------------------------------------------------
1209
doPostProcessingGR8(const CleanupPreprocessedImage * img)1210 TToonzImageP TCleanupper::doPostProcessingGR8(
1211 const CleanupPreprocessedImage *img) {
1212 TToonzImageP image = img->getImg();
1213
1214 TRasterCM32P rasCM32 = image->getRaster();
1215 TRasterCM32P cmout(rasCM32->clone());
1216
1217 cmout->lock();
1218
1219 // Apply brightness/contrast and grayscale conversion directly
1220 brightnessContrastGR8(cmout, m_parameters->m_colors);
1221
1222 // Apply despeckling
1223 if (m_parameters->m_despeckling)
1224 TRop::despeckle(cmout, m_parameters->m_despeckling, false);
1225
1226 // Morphological antialiasing
1227 if (m_parameters->m_postAntialias) {
1228 TRasterCM32P newRas(cmout->getLx(), cmout->getLy());
1229 TRop::antialias(cmout, newRas, 10, m_parameters->m_aaValue);
1230
1231 cmout->unlock();
1232 cmout = newRas;
1233 cmout->lock();
1234 }
1235
1236 cmout->unlock();
1237
1238 // Rebuild the cmap's bbox
1239 TRect bbox;
1240 TRop::computeBBox(cmout, bbox);
1241
1242 // Copy the dpi
1243 TToonzImageP outImg(cmout, bbox);
1244 double dpix, dpiy;
1245 image->getDpi(dpix, dpiy);
1246 outImg->setDpi(dpix, dpiy);
1247
1248 return outImg;
1249 }
1250
1251 //-----------------------------------------------------------------------------------------
1252
doPostProcessingColor(const TRaster32P & outRas,CleanupPreprocessedImage * srcImg)1253 void TCleanupper::doPostProcessingColor(const TRaster32P &outRas,
1254 CleanupPreprocessedImage *srcImg) {
1255 assert(srcImg);
1256 assert(outRas->getSize() == srcImg->getSize());
1257
1258 TToonzImageP imgToProcess = srcImg->getImg();
1259 TRasterCM32P rasCM32 = imgToProcess->getRaster();
1260
1261 rasCM32->lock();
1262 outRas->lock();
1263
1264 TRasterCM32P cmout(outRas->getLx(), outRas->getLy(), outRas->getWrap(),
1265 (TPixelCM32 *)outRas->getRawData());
1266 TRop::copy(cmout, rasCM32);
1267
1268 rasCM32->unlock();
1269
1270 // First, deal with brightness/contrast
1271 brightnessContrast(cmout, m_parameters->m_colors);
1272
1273 // Then, apply despeckling
1274 if (m_parameters->m_despeckling)
1275 TRop::despeckle(cmout, m_parameters->m_despeckling,
1276 m_parameters->m_transparencyCheckEnabled);
1277
1278 // Morphological antialiasing
1279 if (m_parameters->m_postAntialias) {
1280 TRasterCM32P newRas(cmout->getLx(), cmout->getLy());
1281 TRop::antialias(cmout, newRas, 10, m_parameters->m_aaValue);
1282
1283 cmout->unlock();
1284 cmout = newRas;
1285 cmout->lock();
1286 }
1287
1288 // Finally, do transparency check
1289 if (m_parameters->m_transparencyCheckEnabled)
1290 transparencyCheck(cmout, outRas);
1291 else
1292 // TRop::convert(outRas, cmout, m_parameters->m_cleanupPalette);
1293 TRop::convert(outRas, cmout, createToonzPaletteFromCleanupPalette());
1294
1295 outRas->unlock();
1296 }
1297
1298 //------------------------------------------------------------------------------
1299
doPostProcessingColor(const TToonzImageP & imgToProcess,bool isCleanupper)1300 TToonzImageP TCleanupper::doPostProcessingColor(
1301 const TToonzImageP &imgToProcess, bool isCleanupper) {
1302 //(Build and) Copy imgToProcess to output image
1303 TToonzImageP outImage;
1304 if (isCleanupper)
1305 outImage = imgToProcess;
1306 else
1307 outImage = TToonzImageP(imgToProcess->cloneImage());
1308
1309 assert(outImage);
1310 assert(m_parameters->m_colors.getColorCount() < 9);
1311
1312 // Perform post-processing
1313 TRasterCM32P outRasCM32 = outImage->getRaster();
1314 outRasCM32->lock();
1315
1316 // Brightness/Contrast
1317 brightnessContrast(outRasCM32, m_parameters->m_colors);
1318
1319 // Despeckling
1320 if (m_parameters->m_despeckling)
1321 TRop::despeckle(outRasCM32, m_parameters->m_despeckling, false);
1322
1323 // Morphological antialiasing
1324 if (m_parameters->m_postAntialias) {
1325 TRasterCM32P newRas(outRasCM32->getLx(), outRasCM32->getLy());
1326 TRop::antialias(outRasCM32, newRas, 10, m_parameters->m_aaValue);
1327
1328 outRasCM32->unlock();
1329 outRasCM32 = newRas;
1330 outImage->setCMapped(outRasCM32);
1331 outRasCM32->lock();
1332 }
1333
1334 TRect bbox;
1335 TRop::computeBBox(outRasCM32, bbox);
1336 outImage->setSavebox(bbox);
1337
1338 outRasCM32->unlock();
1339 return outImage;
1340 }
1341