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