1 
2 
3 #include <stack>
4 #include <time.h>
5 
6 #include "stdfx.h"
7 #include "tsystem.h"
8 #include "tconvert.h"
9 #include "trop.h"
10 
11 /* Riferimenti:
12 [1] "Filling a region in a frame buffer", Ken Fishkin, su Graphics Gems vol.1,
13 pag 278;
14 */
15 
16 // TODO
17 // v integrare in zcomp
18 // v aggiungere: selezioni multiple, antialias, feather, .
19 // v aggiungere: gestione del mouse, indicatore del colore del pixel
20 // selezionato, media sui vicini del pixel selezionato
21 // . maglass
22 // . verifiche ed ottimizzazioni
23 
24 namespace {}  // anonymous namespace
25 
26 static int invocazioni = 0;  // tmp: numero di invocazioni della MagicWand
27 
28 // Shadow Segment
29 // "Ombra" proiettata da una riga di pixel idonei sulle righe adiacenti
30 // superiore ed inferiore; vedi [1].
31 class ShadowSegment {
32 public:
ShadowSegment(int Lx,int Rx,int pLx,int pRx,int y,int dir)33   ShadowSegment(int Lx, int Rx, int pLx, int pRx, int y, int dir)
34       : m_lx(Lx), m_rx(Rx), m_pLx(pLx), m_pRx(pRx), m_y(y), m_dir(dir) {}
35   int m_rx,   // Right endpoint
36       m_lx,   // Left endpoint
37       m_pRx,  // parent Right endpoint
38       m_pLx,  // parent Left endpoint
39       m_y,    // this segment line
40       m_dir;  // upward, downward
41 };
42 
43 // Stack per la memorizzazione dei segmenti.
44 typedef std::stack<ShadowSegment> ShadowSegmentStack;
45 
46 class MagicWandFx : public TStandardRasterFx {
47   FX_PLUGIN_DECLARATION(MagicWandFx)
48 
49   TRasterFxPort m_input;
50   TDoubleParamP m_tolerance;   // tolleranza
51   TDoubleParamP m_blurRadius;  // ampiezza del campione per il SEED
52 
53   TPointParamP m_point;       // coordinate del SEED (passate da zviewer)
54   TBoolParamP m_contiguous;   // selezione di regioni non connesse alla regione
55                               // contentente il SEED
56   TBoolParamP m_antialiased;  // applicazione dell'antialiasing
57   TBoolParamP m_euclideanD;   // funzione alternativa per il calcolo della
58                               // "similitudine" tra punti
59   TBoolParamP m_preMolt;      // premoltiplicazione
60   TBoolParamP m_isShiftPressed;  // per le selezioni multiple: SUB
61   TBoolParamP m_isAltPressed;    // per le selezioni multiple: ADD
62 
63 public:
MagicWandFx()64   MagicWandFx()
65       : m_tolerance(15.0)
66       , m_blurRadius(0.0)
67       , m_point(TPointD(0, 0))
68 
69   {
70     m_contiguous     = TBoolParamP(true);
71     m_antialiased    = TBoolParamP(true);
72     m_euclideanD     = TBoolParamP(true);
73     m_preMolt        = TBoolParamP(true);
74     m_isShiftPressed = TBoolParamP(false);
75     m_isAltPressed   = TBoolParamP(false);
76 
77     addParam("Tolerance", m_tolerance);
78     addParam("Feather", m_blurRadius);
79     addParam("Point", m_point);
80     addParam("Contiguous", m_contiguous);
81     addParam("Antialias", m_antialiased);
82     addParam("EuclideanD", m_euclideanD);
83     addParam("PreMultiply", m_preMolt);
84     addParam("isShiftPressed", m_isShiftPressed);
85     addParam("isAltPressed", m_isAltPressed);
86     addInputPort("Source", m_input);
87 
88     m_tolerance->setValueRange(0, 255);
89     m_blurRadius->setValueRange(0, 100);
90   }
91 
~MagicWandFx()92   ~MagicWandFx(){};
93 
94   TRect getInvalidRect(const TRect &max);
95   void doCompute(TTile &tile, double frame, const TRasterFxRenderInfo *ri);
96   void doMagicWand(TTile &tile, double frame, const TRasterFxRenderInfo *ri);
97   void EnqueueSegment(int num, int dir, int pLx, int pRx, int Lx, int Rx,
98                       int y);
99   bool pixelProcessor(TPixel32 *testPix, TPixelGR8 *maskPix);
100 
getBBox(double frame,TRectD & rect,TPixel32 & bgColor)101   bool getBBox(double frame, TRectD &rect, TPixel32 &bgColor) {
102     return m_input->getBBox(frame, rect, bgColor);
103   }
104 
105   TRasterGR8P m_maskGR8;  // maschera
106   TPixel32 *m_pickedPix;  // puntatore al pixel SEED
107   TPixelGR8 *m_maskPickedPix;
108 
109   int m_imageHeigth;  // altezza del raster
110   int m_imageWidth;   // larghezza del raster
111   double m_tol;   // le uso per evitare di dover richiamare la funzione getValue
112                   // per ogni punto: sistemare?
113   int m_cont;     // le uso per evitare di dover richiamare la funzione getValue
114                   // per ogni punto: sistemare?
115   bool m_antial;  // le uso per evitare di dover richiamare la funzione getValue
116                   // per ogni punto: sistemare?
117   bool m_euclid;  // le uso per evitare di dover richiamare la funzione getValue
118                   // per ogni punto: sistemare?
119   bool m_add;
120   bool m_sub;
121   int m_id_invocazione;          // contatore delle invocazioni
122   ShadowSegmentStack m_sSStack;  // stack dei segmenti shadow
123 };
124 
125 const int EmptyPixel = 0;
126 const int FullPixel  = 255;
127 
128 //--------------------------------------
129 
130 // tmp: per l'analisi delle prestazioni
131 int pixelProcessed;
132 int pixelMasked;
133 int shadowEnqueued;
134 int pixelReprocessed;
135 int shadowOutOfBorder;
136 bool maskValue;
137 
pixelProcessor(TPixel32 * testPix,TPixelGR8 * maskPix)138 bool MagicWandFx::pixelProcessor(TPixel32 *testPix, TPixelGR8 *maskPix) {
139   pixelProcessed++;
140   unsigned int maskValue = 0;
141   double diff            = 0;
142 
143   // valuto la distanza tra il testPix ed il SEED e la metto in diff
144   if (m_euclid) {
145     // calcolo la Distanza Euclidea tra i punti nello spazio RGB
146     diff = sqrt((m_pickedPix->r - testPix->r) * (m_pickedPix->r - testPix->r) +
147                 (m_pickedPix->g - testPix->g) * (m_pickedPix->g - testPix->g) +
148                 (m_pickedPix->b - testPix->b) * (m_pickedPix->b - testPix->b));
149   } else {
150     // GIMP-like: confronto la tolleranza con il massimo tra gli scarti delle
151     // componenti
152     diff                       = abs(m_pickedPix->r - testPix->r);
153     double diffNext            = abs(m_pickedPix->g - testPix->g);
154     if (diffNext >= diff) diff = diffNext;
155     diffNext                   = abs(m_pickedPix->b - testPix->b);
156     if (diffNext >= diff) diff = diffNext;
157   }
158 
159   if (diff <= m_tol)
160     maskValue = FullPixel;
161   else
162     maskValue = EmptyPixel;
163 
164   if (maskValue) {
165     // il pixel soddisfa il criterio di compatibilita
166 
167     if (m_add) {
168       // sto aggiungendo selezioni
169       if (testPix->m != EmptyPixel) {
170         pixelReprocessed++;
171         return false;
172       }  // gia' trattato
173 
174       //* DECIDERE SE VOGLIO CHE LA SELEZIONE INTERESSI AREE GIA' SELEZIONATE IN
175       // PRECEDENZA
176       //      if (maskPix->value == EmptyPixel) { //pixel c-compatibile, non
177       //      gia' mascherato
178       testPix->m     = maskValue;  // set(mV)m
179       maskPix->value = maskValue;  // set(mV)a
180       pixelMasked++;
181       //      } else { // pixel c-compatibile gia' mascherato precedentemente
182       //               testPix->m = maskValue;  // set(mV)m
183       //     }
184     } else if (m_sub) {
185       // sto togliendo selezioni
186       if (testPix->m != EmptyPixel) return false;  // gia' trattato
187       testPix->m     = maskValue;                  // set(mV)m
188       maskPix->value = EmptyPixel;                 // set(0)a
189     } else {                                       // prima selezione
190       if (testPix->m != EmptyPixel) return false;  // gia' trattato
191       testPix->m     = maskValue;                  // set(mV)m
192       maskPix->value = maskValue;                  // set(mV)a
193       pixelMasked++;
194     }
195     return true;
196   } else
197     return false;
198 }  // pixelProcessor
199 
200 // [1]: aggiunge le ombre necessarie alla pila.
EnqueueSegment(int num,int dir,int pLx,int pRx,int Lx,int Rx,int y)201 void MagicWandFx::EnqueueSegment(int num, int dir, int pLx, int pRx, int Lx,
202                                  int Rx, int y) {
203   int pushRx = Rx + 1;
204   int pushLx = Lx + 1;
205 
206   //  TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
207   //  1>]\tStack Size:"+toString((int)
208   //  m_sSStack.size())+"\tLx:"+toString(Lx)+"\tRx:"+toString(Rx)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y)+"\tdir:"+toString(dir)+"\n");
209   assert((Lx <= Rx) && (pushLx <= pushRx) && (Lx >= 0));
210   m_sSStack.push(ShadowSegment(Lx, Rx, pushLx, pushRx, (y + dir), dir));
211   shadowEnqueued++;
212 
213   if (Rx > pRx) {  // U-turn a destra
214                    //  TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
215                    //  2>]\tStack Size:"+toString((int)
216                    //  m_sSStack.size())+"\tLx:"+toString(pRx+1)+"\tRx:"+toString(Rx)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y-dir)+"\tdir:"+toString(dir)+"\n");
217     assert(((pRx + 1) <= (Rx)) && (pushLx <= pushRx) && ((pRx + 1) >= 0));
218     m_sSStack.push(
219         ShadowSegment((pRx + 1), Rx, pushLx, pushRx, (y - dir), (-dir)));
220     shadowEnqueued++;
221   }
222   if (Lx < pLx) {  // U-turn a sinistra
223                    //    TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
224                    //    3>]\tStack Size:"+toString((int)
225                    //    m_sSStack.size())+"\tLx:"+toString(Lx)+"\tRx:"+toString(pLx-1)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y-dir)+"\tdir:"+toString(dir)+"\n");
226     assert(((Lx) <= (pLx - 1)) && (pushLx <= pushRx) && (Lx >= 0));
227     m_sSStack.push(
228         ShadowSegment(Lx, (pLx - 1), pushLx, pushRx, (y - dir), (-dir)));
229     shadowEnqueued++;
230   }
231   // W-Turn = 2 U-Turn
232 }  // EnqueueSegment
233 
doMagicWand(TTile & tile,double frame,const TRasterFxRenderInfo * ri)234 void MagicWandFx::doMagicWand(TTile &tile, double frame,
235                               const TRasterFxRenderInfo *ri) {
236   clock_t start_time = clock();  // debug
237   clock_t stop_time;
238 
239   invocazioni++;
240   m_id_invocazione = invocazioni;
241   m_tol            = m_tolerance->getValue(frame);
242   m_antial         = m_antialiased->getValue();
243   m_euclid         = m_euclideanD->getValue();  // temporaneo?
244   m_cont = m_contiguous->getValue();  // selezione di aree cromaticamente
245                                       // compatibili ma non contigue: Selezione
246                                       // ByColor
247   m_add = m_isShiftPressed->getValue();
248   m_sub = m_isAltPressed->getValue();
249 
250   tile.getRaster()->lock();
251   TRaster32P ras32 = tile.getRaster();
252 
253   TPixel32 vPixel;
254   TPixel32 *tmpPix;
255   TPixel32 *rowStart;
256   TPixelGR8 *maskRowStart;
257   TPixelGR8 *maskPix;
258 
259   if (ras32) {
260     pixelProcessed    = 0;
261     pixelMasked       = 1;
262     shadowEnqueued    = 2;
263     pixelReprocessed  = 0;
264     shadowOutOfBorder = 0;
265 
266     m_imageWidth  = ras32->getLx();
267     m_imageHeigth = ras32->getLy();
268     // assert(m_imageWidth == 800);
269     assert(m_imageHeigth <= 600);
270     int lx = m_imageWidth;
271     int ly = m_imageHeigth;
272 
273     if (!m_maskGR8) {
274       // prima esecuzione creo il raster gr8 x la maschera e azzero gli alpha
275       TRectD bBoxD;
276       TPixel32 bgColor;
277       bool getBBoxOk = getBBox(frame, bBoxD, bgColor);
278       assert(getBBoxOk);
279       TRect bBoxI = convert(bBoxD);
280       m_maskGR8   = TRasterGR8P(bBoxI.getLx(), bBoxI.getLy());
281       m_maskGR8->clear();
282     }
283     m_maskGR8->lock();
284     // sono arrivato qui: sto verificando se serve davvero il gr8.
285 
286     for (int iy = 0; iy < m_imageHeigth; iy++) {  // y
287       tmpPix = ras32->pixels(iy);
288       for (int ix = 0; ix < m_imageWidth; ix++) {  // x
289         tmpPix->m = EmptyPixel;
290         tmpPix++;
291       }  // x
292     }    // y
293 
294     if (m_add) {  // ho premuto Shift sto aggiungendo alla selezione
295 
296     } else if (m_sub) {
297       // ho premuto Alt sto sottraendo dalla selezione
298 
299     } else {
300       // non ho premuto niente nuova selezione
301 
302       // ripulisco il canale alpha dell'immagine e la maschera
303       for (int iy = 0; iy < m_imageHeigth; iy++) {
304         tmpPix  = ras32->pixels(iy);
305         maskPix = m_maskGR8->pixels(iy);
306         for (int ix = 0; ix < m_imageWidth; ix++) {
307           tmpPix->m = 0;
308 
309           maskPix->value = EmptyPixel;
310           tmpPix++;
311           maskPix++;
312         }  // x
313       }    // y
314     }
315 
316     // trovo il pixel in X,Y soluzione temporanea in attesa della gestione del
317     // mouse.
318     // converto le coordinate mondo (-500;+500) in coordinate raster
319     // (0;m_imageWidth);
320     TPointD point = m_point->getValue(frame);
321 
322     // coordinate dagli sliders
323     //    int x = (int) (500+point.x)*m_imageWidth/1000; if (x>0) x--;
324     //    int y = (int) (500+point.y)*m_imageHeigth/1000; if (y>0) y--;
325 
326     // coordinate dalla ZViewer:leftButtonClick
327 
328     int x = tcrop((int)(point.x + m_imageWidth / 2), 0, (m_imageWidth - 1));
329     int y = tcrop((int)(point.y + m_imageHeigth / 2), 0, (m_imageHeigth - 1));
330 
331     TSystem::outputDebug("\n[MWfx(" + toString(m_id_invocazione) +
332                          ")<begin>]\nSize:" + toString(m_imageWidth) + "x" +
333                          toString(m_imageHeigth) + "\tx:" + toString(x) +
334                          "\ty:" + toString(y) + "\tToll:" + toString(m_tol) +
335                          /*      "\tRadius:" + toString(radius) +*/ (
336                              (m_cont) ? "\tContiguous" : "\tNon Contiguous") +
337                          ((m_antial) ? "\tAnti Aliased" : "\tAliased") +
338                          ((m_euclid) ? "\tEuclidean\n" : "\tNon Euclidean\n"));
339 
340     lx = m_imageWidth;
341     ly = m_imageHeigth;
342 
343     m_pickedPix     = ras32->pixels(y) + x;
344     m_maskPickedPix = m_maskGR8->pixels(y) + x;
345 
346     pixelProcessed = 1;
347 
348     if (m_cont) {  // seleziono esclusivamente i pixel connessi al pixel SEED
349 
350       //- ALGORITMO FLOOD FILL: GRAPHICS GEM 1 p.280
351       //----------------------------------------
352       int xAux, yAux, lxAux, rxAux, dirAux, pRxAux, pLxAux;
353       bool inSpan = true;
354 
355       // trova Rx e Lx dello span contentente il SEED point
356       int xCont = x;
357       tmpPix    = m_pickedPix;      // puntatore al SEED
358       maskPix   = m_maskPickedPix;  //******
359 
360       // cerco Lx
361       maskValue  = pixelProcessor(tmpPix, maskPix);
362       bool tmpMv = maskValue;
363       while ((xCont >= 0) && (maskValue)) {
364         tmpPix--;
365         maskPix--;
366         xCont--;
367         if (xCont >= 0) maskValue = pixelProcessor(tmpPix, maskPix);
368       }
369       if (tmpMv)
370         lxAux = xCont + 1;
371       else
372         lxAux = xCont;
373 
374       // cerco Rx
375       tmpPix  = m_pickedPix;
376       maskPix = m_maskPickedPix;  //******
377 
378       xCont     = x;
379       maskValue = tmpMv;
380       while ((xCont < m_imageWidth) && (maskValue)) {
381         tmpPix++;
382         maskPix++;
383         xCont++;
384         if (xCont < m_imageWidth) maskValue = pixelProcessor(tmpPix, maskPix);
385       }
386       if (tmpMv)
387         rxAux = xCont - 1;
388       else
389         rxAux = xCont;
390 
391       assert((lxAux <= rxAux) && (lxAux >= 0));
392 
393       // metto nella pila delle ombre la riga sopra e sotto quella contentente
394       // il seed.
395       //            TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
396       //            Size:"+toString((int)
397       //            m_sSStack.size())+"\tLx:"+toString(lxAux)+"\tRx:"+toString(rxAux)+"\tpLx:"+toString(lxAux)+"\tpRx:"+toString(rxAux)+"\ty:"+toString(y+1)+"\tdir:"+toString(1)+"\n");
398       m_sSStack.push(ShadowSegment(lxAux, rxAux, lxAux, rxAux, y + 1,
399                                    +1));  // cerca in alto
400                                           //            TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
401                                           //            Size:"+toString((int)
402                                           //            m_sSStack.size())+"\tLx:"+toString(lxAux)+"\tRx:"+toString(rxAux)+"\tpLx:"+toString(lxAux)+"\tpRx:"+toString(rxAux)+"\ty:"+toString(y-1)+"\tdir:"+toString(-1)+"\n");
403       m_sSStack.push(ShadowSegment(lxAux, rxAux, lxAux, rxAux, y - 1,
404                                    -1));  // cerca in basso
405 
406       while (!m_sSStack.empty()) {
407         ShadowSegment sSegment = m_sSStack.top();
408         m_sSStack.pop();
409         //        TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":0)<POP
410         //        >]\tStack Size:"+toString((int)
411         //        m_sSStack.size())+"\tLx:"+toString(sSegment.m_lx)+"\tRx:"+toString(sSegment.m_rx)+"\tpLx:"+toString(sSegment.m_pLx)+"\tpRx:"+toString(sSegment.m_pRx)+"\ty:"+toString(sSegment.m_y)+"\tdir:"+toString(sSegment.m_dir)+"\n");
412 
413         dirAux = sSegment.m_dir;
414         pRxAux = sSegment.m_pRx;
415         pLxAux = sSegment.m_pLx;
416         lxAux  = sSegment.m_lx;
417         rxAux  = sSegment.m_rx;
418         yAux   = sSegment.m_y;
419 
420         if ((yAux < 0) || (yAux >= m_imageHeigth)) {
421           shadowOutOfBorder++;
422           continue;  // questo segmento sta fuori dal raster oppure l'ho gia'
423                      // colorato: lo salto
424         }
425         assert((lxAux <= rxAux) && (pLxAux <= pRxAux));
426         assert((m_sSStack.size() <= 1000));
427 
428         xAux = lxAux + 1;
429 
430         rowStart = ras32->pixels(yAux);
431 
432         maskRowStart = m_maskGR8->pixels(yAux);  //**
433 
434         tmpPix  = rowStart + lxAux;
435         maskPix = maskRowStart + lxAux;
436 
437         maskValue = pixelProcessor(tmpPix, maskPix);
438 
439         inSpan = (maskValue);
440 
441         if (maskValue) {  // il punto e' cromaticompatibile
442           lxAux--;
443           if (lxAux >= 0) {
444             tmpPix--;
445             maskPix--;
446             maskValue = pixelProcessor(tmpPix, maskPix);
447           }
448           while ((maskValue) &&
449                  (lxAux >= 0)) {  // sto nello span E nell'immagine
450             lxAux--;
451             if (lxAux >= 0) {
452               tmpPix--;
453               maskPix--;
454               maskValue = pixelProcessor(tmpPix, maskPix);
455             }
456           }  // sto nello span E nell'immagine
457         }    // il punto e' cromaticompatibile
458         lxAux++;
459         //        rowStart = ras32->pixels(yAux);
460         while (xAux < m_imageWidth) {  // mi sposto a destra lungo la X
461           if (inSpan) {
462             tmpPix    = rowStart + xAux;
463             maskPix   = maskRowStart + xAux;  //***
464             maskValue = pixelProcessor(tmpPix, maskPix);
465             if (maskValue) {  // case 1
466                               // fa tutto nella pixel processor
467             }                 // case 1
468             else {            // case 2
469               EnqueueSegment(1, dirAux, pLxAux, pRxAux, lxAux, (xAux - 1),
470                              yAux);
471               inSpan = false;
472             }     // case 2
473           }       // inSpan
474           else {  // non ero nello span
475             if (xAux > rxAux) break;
476             tmpPix    = rowStart + xAux;
477             maskPix   = maskRowStart + xAux;
478             maskValue = pixelProcessor(tmpPix, maskPix);
479             if (maskValue) {  // case 3
480               inSpan = true;
481               lxAux  = xAux;
482             }       // case 3
483             else {  // case 4
484             }
485           }  // non ero nello span
486           xAux++;
487           //          TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
488           //          Size:"+toString((int)
489           //          m_sSStack.size())+"\txAux:"+toString(xAux)+"\ty:"+toString(yAux)+"\n");
490         }  // mi sposto a destra lungo la X: endloop 1
491         if (inSpan) {
492           EnqueueSegment(2, dirAux, pLxAux, pRxAux, lxAux, (xAux - 1), yAux);
493         }
494       }     // finche' la pila non e' vuota: endloop 2
495     }       // if m_cont
496     else {  // anche le regioni simili NON contigue: questo rimane anche in caso
497             // di modifica della parte m_cont
498       for (int iy = 0; iy < m_imageHeigth; iy++) {
499         tmpPix  = ras32->pixels(iy);
500         maskPix = m_maskGR8->pixels(iy);
501         for (int ix = 0; ix < m_imageWidth; ix++) {
502           maskValue = pixelProcessor(tmpPix, maskPix);
503           //                 if (maskValue) { } // if// il colore e' simile =>
504           //                 va incluso nella selezione // fa tutto nella pixel
505           //                 processor
506           tmpPix++;
507           maskPix++;
508         }  // ix
509       }    // iy
510     }      // else m_cont
511 
512     int blurRadius = (int)(m_blurRadius->getValue(frame));
513     if ((m_antial) && (blurRadius < 2)) blurRadius = 1;
514 
515     if (blurRadius > 0)
516       TRop::blur(m_maskGR8, m_maskGR8, (blurRadius + 1), 0, 0);
517 
518     // copio la maschera sull'alpha channel dell'immagine
519     // lo faccio a mano chiedere se esiste una funziona apposita
520     for (iy = 0; iy < m_imageHeigth; iy++) {
521       tmpPix  = ras32->pixels(iy);
522       maskPix = m_maskGR8->pixels(iy);
523       for (int ix = 0; ix < m_imageWidth; ix++) {
524         tmpPix->m = maskPix->value;
525         tmpPix++;
526         maskPix++;
527       }  // ix
528     }    // iy
529 
530     if (m_preMolt->getValue()) TRop::premultiply(ras32);
531     stop_time     = clock();
532     double durata = (double)(stop_time - start_time) / CLOCKS_PER_SEC;
533 
534     TSystem::outputDebug(
535         "\n#Pixel:\t" + toString(m_imageWidth * m_imageHeigth) + "\nProc:\t" +
536         toString(pixelProcessed) + "\t[" +
537         toString((pixelProcessed * 100 / (m_imageWidth * m_imageHeigth))) +
538         "%t]" + "\nMask:\t" + toString(pixelMasked) + "\t[" +
539         toString((pixelMasked * 100 / (m_imageWidth * m_imageHeigth))) + "%t]" +
540         "\t[" + toString((pixelMasked * 100 / (pixelProcessed))) + "%p]" +
541         "\nEnqu:\t" + toString(shadowEnqueued) + "\nRepr:\t" +
542         toString(pixelReprocessed) + "\t[" +
543         toString((pixelReprocessed * 100 / (m_imageWidth * m_imageHeigth))) +
544         "%t]" + "\t[" + toString((pixelReprocessed * 100 / (pixelProcessed))) +
545         "%p]" + "\nOutB:\t" + toString(shadowOutOfBorder) + "\t[" +
546         toString((shadowOutOfBorder * 100 / (shadowEnqueued))) + "%t]" +
547         "\nTime:\t" + toString(durata, 3) + " sec\n[MagicWandFX <end>]\n");
548 
549   }  // if (ras32)
550   else {
551     TRasterGR8P rasGR8 = tile.getRaster();
552     if (rasGR8) {
553     }
554   }
555   tile.getRaster()->unlock();
556   m_maskGR8->unlock();
557 }  // doMagicWand
558 
doCompute(TTile & tile,double frame,const TRasterFxRenderInfo * ri)559 void MagicWandFx::doCompute(TTile &tile, double frame,
560                             const TRasterFxRenderInfo *ri) {
561   if (!m_input.isConnected()) return;
562 
563   m_input->compute(tile, frame, ri);
564 
565   doMagicWand(tile, frame, ri);
566 }
567 
568 //------------------------------------------------------------------
569 
getInvalidRect(const TRect & max)570 TRect MagicWandFx::getInvalidRect(const TRect &max) { return max; }
571 
572 //------------------------------------------------------------------
573 
574 FX_PLUGIN_IDENTIFIER(MagicWandFx, magicWandFx);
575