tahoma2d/toonz/sources/stdfx/magicwandfx.cpp

575 lines
21 KiB
C++
Raw Normal View History

2016-03-19 06:57:51 +13:00
#include <stack>
#include <time.h>
#include "stdfx.h"
#include "tsystem.h"
#include "tconvert.h"
#include "trop.h"
/* Riferimenti:
2016-06-15 18:43:10 +12:00
[1] "Filling a region in a frame buffer", Ken Fishkin, su Graphics Gems vol.1,
pag 278;
2016-03-19 06:57:51 +13:00
*/
// TODO
// v integrare in zcomp
// v aggiungere: selezioni multiple, antialias, feather, .
2016-06-15 18:43:10 +12:00
// v aggiungere: gestione del mouse, indicatore del colore del pixel
// selezionato, media sui vicini del pixel selezionato
2016-03-19 06:57:51 +13:00
// . maglass
// . verifiche ed ottimizzazioni
2016-06-15 18:43:10 +12:00
namespace {} // anonymous namespace
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
static int invocazioni = 0; // tmp: numero di invocazioni della MagicWand
2016-03-19 06:57:51 +13:00
// Shadow Segment
2016-06-15 18:43:10 +12:00
// "Ombra" proiettata da una riga di pixel idonei sulle righe adiacenti
// superiore ed inferiore; vedi [1].
class ShadowSegment {
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
ShadowSegment(int Lx, int Rx, int pLx, int pRx, int y, int dir)
: m_lx(Lx), m_rx(Rx), m_pLx(pLx), m_pRx(pRx), m_y(y), m_dir(dir) {}
int m_rx, // Right endpoint
m_lx, // Left endpoint
m_pRx, // parent Right endpoint
m_pLx, // parent Left endpoint
m_y, // this segment line
m_dir; // upward, downward
2016-03-19 06:57:51 +13:00
};
// Stack per la memorizzazione dei segmenti.
typedef std::stack<ShadowSegment> ShadowSegmentStack;
2016-06-15 18:43:10 +12:00
class MagicWandFx : public TStandardRasterFx {
FX_PLUGIN_DECLARATION(MagicWandFx)
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TRasterFxPort m_input;
TDoubleParamP m_tolerance; // tolleranza
TDoubleParamP m_blurRadius; // ampiezza del campione per il SEED
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TPointParamP m_point; // coordinate del SEED (passate da zviewer)
TBoolParamP m_contiguous; // selezione di regioni non connesse alla regione
// contentente il SEED
TBoolParamP m_antialiased; // applicazione dell'antialiasing
TBoolParamP m_euclideanD; // funzione alternativa per il calcolo della
// "similitudine" tra punti
TBoolParamP m_preMolt; // premoltiplicazione
TBoolParamP m_isShiftPressed; // per le selezioni multiple: SUB
TBoolParamP m_isAltPressed; // per le selezioni multiple: ADD
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
MagicWandFx()
: m_tolerance(15.0)
, m_blurRadius(0.0)
, m_point(TPointD(0, 0))
{
m_contiguous = TBoolParamP(true);
m_antialiased = TBoolParamP(true);
m_euclideanD = TBoolParamP(true);
m_preMolt = TBoolParamP(true);
m_isShiftPressed = TBoolParamP(false);
m_isAltPressed = TBoolParamP(false);
addParam("Tolerance", m_tolerance);
addParam("Feather", m_blurRadius);
addParam("Point", m_point);
addParam("Contiguous", m_contiguous);
addParam("Antialias", m_antialiased);
addParam("EuclideanD", m_euclideanD);
addParam("PreMultiply", m_preMolt);
addParam("isShiftPressed", m_isShiftPressed);
addParam("isAltPressed", m_isAltPressed);
addInputPort("Source", m_input);
m_tolerance->setValueRange(0, 255);
m_blurRadius->setValueRange(0, 100);
}
~MagicWandFx(){};
TRect getInvalidRect(const TRect &max);
void doCompute(TTile &tile, double frame, const TRasterFxRenderInfo *ri);
void doMagicWand(TTile &tile, double frame, const TRasterFxRenderInfo *ri);
void EnqueueSegment(int num, int dir, int pLx, int pRx, int Lx, int Rx,
int y);
bool pixelProcessor(TPixel32 *testPix, TPixelGR8 *maskPix);
bool getBBox(double frame, TRectD &rect, TPixel32 &bgColor) {
return m_input->getBBox(frame, rect, bgColor);
}
TRasterGR8P m_maskGR8; // maschera
TPixel32 *m_pickedPix; // puntatore al pixel SEED
TPixelGR8 *m_maskPickedPix;
int m_imageHeigth; // altezza del raster
int m_imageWidth; // larghezza del raster
double m_tol; // le uso per evitare di dover richiamare la funzione getValue
// per ogni punto: sistemare?
int m_cont; // le uso per evitare di dover richiamare la funzione getValue
// per ogni punto: sistemare?
bool m_antial; // le uso per evitare di dover richiamare la funzione getValue
// per ogni punto: sistemare?
bool m_euclid; // le uso per evitare di dover richiamare la funzione getValue
// per ogni punto: sistemare?
bool m_add;
bool m_sub;
int m_id_invocazione; // contatore delle invocazioni
ShadowSegmentStack m_sSStack; // stack dei segmenti shadow
2016-03-19 06:57:51 +13:00
};
const int EmptyPixel = 0;
2016-06-15 18:43:10 +12:00
const int FullPixel = 255;
2016-03-19 06:57:51 +13:00
//--------------------------------------
// tmp: per l'analisi delle prestazioni
int pixelProcessed;
int pixelMasked;
int shadowEnqueued;
int pixelReprocessed;
int shadowOutOfBorder;
bool maskValue;
2016-06-15 18:43:10 +12:00
bool MagicWandFx::pixelProcessor(TPixel32 *testPix, TPixelGR8 *maskPix) {
pixelProcessed++;
unsigned int maskValue = 0;
double diff = 0;
// valuto la distanza tra il testPix ed il SEED e la metto in diff
if (m_euclid) {
// calcolo la Distanza Euclidea tra i punti nello spazio RGB
diff = sqrt((m_pickedPix->r - testPix->r) * (m_pickedPix->r - testPix->r) +
(m_pickedPix->g - testPix->g) * (m_pickedPix->g - testPix->g) +
(m_pickedPix->b - testPix->b) * (m_pickedPix->b - testPix->b));
} else {
// GIMP-like: confronto la tolleranza con il massimo tra gli scarti delle
// componenti
diff = abs(m_pickedPix->r - testPix->r);
double diffNext = abs(m_pickedPix->g - testPix->g);
if (diffNext >= diff) diff = diffNext;
diffNext = abs(m_pickedPix->b - testPix->b);
if (diffNext >= diff) diff = diffNext;
}
if (diff <= m_tol)
maskValue = FullPixel;
else
maskValue = EmptyPixel;
if (maskValue) {
// il pixel soddisfa il criterio di compatibilita
if (m_add) {
// sto aggiungendo selezioni
if (testPix->m != EmptyPixel) {
pixelReprocessed++;
return false;
} // gia' trattato
//* DECIDERE SE VOGLIO CHE LA SELEZIONE INTERESSI AREE GIA' SELEZIONATE IN
2016-06-20 14:23:05 +12:00
// PRECEDENZA
2016-06-15 18:43:10 +12:00
// if (maskPix->value == EmptyPixel) { //pixel c-compatibile, non
// gia' mascherato
testPix->m = maskValue; // set(mV)m
maskPix->value = maskValue; // set(mV)a
pixelMasked++;
// } else { // pixel c-compatibile gia' mascherato precedentemente
// testPix->m = maskValue; // set(mV)m
// }
} else if (m_sub) {
// sto togliendo selezioni
if (testPix->m != EmptyPixel) return false; // gia' trattato
testPix->m = maskValue; // set(mV)m
maskPix->value = EmptyPixel; // set(0)a
} else { // prima selezione
if (testPix->m != EmptyPixel) return false; // gia' trattato
testPix->m = maskValue; // set(mV)m
maskPix->value = maskValue; // set(mV)a
pixelMasked++;
}
return true;
} else
return false;
} // pixelProcessor
2016-03-19 06:57:51 +13:00
// [1]: aggiunge le ombre necessarie alla pila.
2016-06-15 18:43:10 +12:00
void MagicWandFx::EnqueueSegment(int num, int dir, int pLx, int pRx, int Lx,
int Rx, int y) {
int pushRx = Rx + 1;
int pushLx = Lx + 1;
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
// 1>]\tStack Size:"+toString((int)
// m_sSStack.size())+"\tLx:"+toString(Lx)+"\tRx:"+toString(Rx)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y)+"\tdir:"+toString(dir)+"\n");
assert((Lx <= Rx) && (pushLx <= pushRx) && (Lx >= 0));
m_sSStack.push(ShadowSegment(Lx, Rx, pushLx, pushRx, (y + dir), dir));
shadowEnqueued++;
if (Rx > pRx) { // U-turn a destra
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
// 2>]\tStack Size:"+toString((int)
// m_sSStack.size())+"\tLx:"+toString(pRx+1)+"\tRx:"+toString(Rx)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y-dir)+"\tdir:"+toString(dir)+"\n");
assert(((pRx + 1) <= (Rx)) && (pushLx <= pushRx) && ((pRx + 1) >= 0));
m_sSStack.push(
ShadowSegment((pRx + 1), Rx, pushLx, pushRx, (y - dir), (-dir)));
shadowEnqueued++;
}
if (Lx < pLx) { // U-turn a sinistra
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":"+toString(num)+")<PUSH
// 3>]\tStack Size:"+toString((int)
// m_sSStack.size())+"\tLx:"+toString(Lx)+"\tRx:"+toString(pLx-1)+"\tpLx:"+toString(pushLx)+"\tpRx:"+toString(pushRx)+"\ty:"+toString(y-dir)+"\tdir:"+toString(dir)+"\n");
assert(((Lx) <= (pLx - 1)) && (pushLx <= pushRx) && (Lx >= 0));
m_sSStack.push(
ShadowSegment(Lx, (pLx - 1), pushLx, pushRx, (y - dir), (-dir)));
shadowEnqueued++;
}
// W-Turn = 2 U-Turn
} // EnqueueSegment
void MagicWandFx::doMagicWand(TTile &tile, double frame,
const TRasterFxRenderInfo *ri) {
clock_t start_time = clock(); // debug
clock_t stop_time;
invocazioni++;
m_id_invocazione = invocazioni;
m_tol = m_tolerance->getValue(frame);
m_antial = m_antialiased->getValue();
m_euclid = m_euclideanD->getValue(); // temporaneo?
m_cont = m_contiguous->getValue(); // selezione di aree cromaticamente
// compatibili ma non contigue: Selezione
// ByColor
m_add = m_isShiftPressed->getValue();
m_sub = m_isAltPressed->getValue();
tile.getRaster()->lock();
TRaster32P ras32 = tile.getRaster();
TPixel32 vPixel;
TPixel32 *tmpPix;
TPixel32 *rowStart;
TPixelGR8 *maskRowStart;
TPixelGR8 *maskPix;
if (ras32) {
pixelProcessed = 0;
pixelMasked = 1;
shadowEnqueued = 2;
pixelReprocessed = 0;
shadowOutOfBorder = 0;
m_imageWidth = ras32->getLx();
m_imageHeigth = ras32->getLy();
// assert(m_imageWidth == 800);
assert(m_imageHeigth <= 600);
int lx = m_imageWidth;
int ly = m_imageHeigth;
if (!m_maskGR8) {
// prima esecuzione creo il raster gr8 x la maschera e azzero gli alpha
TRectD bBoxD;
TPixel32 bgColor;
bool getBBoxOk = getBBox(frame, bBoxD, bgColor);
assert(getBBoxOk);
TRect bBoxI = convert(bBoxD);
m_maskGR8 = TRasterGR8P(bBoxI.getLx(), bBoxI.getLy());
m_maskGR8->clear();
}
m_maskGR8->lock();
// sono arrivato qui: sto verificando se serve davvero il gr8.
for (int iy = 0; iy < m_imageHeigth; iy++) { // y
tmpPix = ras32->pixels(iy);
for (int ix = 0; ix < m_imageWidth; ix++) { // x
tmpPix->m = EmptyPixel;
tmpPix++;
} // x
} // y
if (m_add) { // ho premuto Shift sto aggiungendo alla selezione
} else if (m_sub) {
// ho premuto Alt sto sottraendo dalla selezione
} else {
// non ho premuto niente nuova selezione
// ripulisco il canale alpha dell'immagine e la maschera
for (int iy = 0; iy < m_imageHeigth; iy++) {
tmpPix = ras32->pixels(iy);
maskPix = m_maskGR8->pixels(iy);
for (int ix = 0; ix < m_imageWidth; ix++) {
tmpPix->m = 0;
maskPix->value = EmptyPixel;
tmpPix++;
maskPix++;
} // x
} // y
}
// trovo il pixel in X,Y soluzione temporanea in attesa della gestione del
// mouse.
// converto le coordinate mondo (-500;+500) in coordinate raster
// (0;m_imageWidth);
TPointD point = m_point->getValue(frame);
// coordinate dagli sliders
// int x = (int) (500+point.x)*m_imageWidth/1000; if (x>0) x--;
// int y = (int) (500+point.y)*m_imageHeigth/1000; if (y>0) y--;
// coordinate dalla ZViewer:leftButtonClick
int x = tcrop((int)(point.x + m_imageWidth / 2), 0, (m_imageWidth - 1));
int y = tcrop((int)(point.y + m_imageHeigth / 2), 0, (m_imageHeigth - 1));
TSystem::outputDebug("\n[MWfx(" + toString(m_id_invocazione) +
")<begin>]\nSize:" + toString(m_imageWidth) + "x" +
toString(m_imageHeigth) + "\tx:" + toString(x) +
"\ty:" + toString(y) + "\tToll:" + toString(m_tol) +
/* "\tRadius:" + toString(radius) +*/ (
(m_cont) ? "\tContiguous" : "\tNon Contiguous") +
((m_antial) ? "\tAnti Aliased" : "\tAliased") +
((m_euclid) ? "\tEuclidean\n" : "\tNon Euclidean\n"));
lx = m_imageWidth;
ly = m_imageHeigth;
m_pickedPix = ras32->pixels(y) + x;
m_maskPickedPix = m_maskGR8->pixels(y) + x;
pixelProcessed = 1;
if (m_cont) { // seleziono esclusivamente i pixel connessi al pixel SEED
//- ALGORITMO FLOOD FILL: GRAPHICS GEM 1 p.280
//----------------------------------------
int xAux, yAux, lxAux, rxAux, dirAux, pRxAux, pLxAux;
bool inSpan = true;
// trova Rx e Lx dello span contentente il SEED point
int xCont = x;
tmpPix = m_pickedPix; // puntatore al SEED
maskPix = m_maskPickedPix; //******
// cerco Lx
maskValue = pixelProcessor(tmpPix, maskPix);
bool tmpMv = maskValue;
while ((xCont >= 0) && (maskValue)) {
tmpPix--;
maskPix--;
xCont--;
if (xCont >= 0) maskValue = pixelProcessor(tmpPix, maskPix);
}
if (tmpMv)
lxAux = xCont + 1;
else
lxAux = xCont;
// cerco Rx
tmpPix = m_pickedPix;
maskPix = m_maskPickedPix; //******
xCont = x;
maskValue = tmpMv;
while ((xCont < m_imageWidth) && (maskValue)) {
tmpPix++;
maskPix++;
xCont++;
if (xCont < m_imageWidth) maskValue = pixelProcessor(tmpPix, maskPix);
}
if (tmpMv)
rxAux = xCont - 1;
else
rxAux = xCont;
assert((lxAux <= rxAux) && (lxAux >= 0));
// metto nella pila delle ombre la riga sopra e sotto quella contentente
// il seed.
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
// Size:"+toString((int)
// m_sSStack.size())+"\tLx:"+toString(lxAux)+"\tRx:"+toString(rxAux)+"\tpLx:"+toString(lxAux)+"\tpRx:"+toString(rxAux)+"\ty:"+toString(y+1)+"\tdir:"+toString(1)+"\n");
m_sSStack.push(ShadowSegment(lxAux, rxAux, lxAux, rxAux, y + 1,
+1)); // cerca in alto
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
// Size:"+toString((int)
// m_sSStack.size())+"\tLx:"+toString(lxAux)+"\tRx:"+toString(rxAux)+"\tpLx:"+toString(lxAux)+"\tpRx:"+toString(rxAux)+"\ty:"+toString(y-1)+"\tdir:"+toString(-1)+"\n");
m_sSStack.push(ShadowSegment(lxAux, rxAux, lxAux, rxAux, y - 1,
-1)); // cerca in basso
while (!m_sSStack.empty()) {
ShadowSegment sSegment = m_sSStack.top();
m_sSStack.pop();
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+":0)<POP
// >]\tStack Size:"+toString((int)
// 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");
dirAux = sSegment.m_dir;
pRxAux = sSegment.m_pRx;
pLxAux = sSegment.m_pLx;
lxAux = sSegment.m_lx;
rxAux = sSegment.m_rx;
yAux = sSegment.m_y;
if ((yAux < 0) || (yAux >= m_imageHeigth)) {
shadowOutOfBorder++;
continue; // questo segmento sta fuori dal raster oppure l'ho gia'
// colorato: lo salto
}
assert((lxAux <= rxAux) && (pLxAux <= pRxAux));
assert((m_sSStack.size() <= 1000));
xAux = lxAux + 1;
rowStart = ras32->pixels(yAux);
maskRowStart = m_maskGR8->pixels(yAux); //**
tmpPix = rowStart + lxAux;
maskPix = maskRowStart + lxAux;
maskValue = pixelProcessor(tmpPix, maskPix);
inSpan = (maskValue);
if (maskValue) { // il punto e' cromaticompatibile
lxAux--;
if (lxAux >= 0) {
tmpPix--;
maskPix--;
maskValue = pixelProcessor(tmpPix, maskPix);
}
while ((maskValue) &&
(lxAux >= 0)) { // sto nello span E nell'immagine
lxAux--;
if (lxAux >= 0) {
tmpPix--;
maskPix--;
maskValue = pixelProcessor(tmpPix, maskPix);
}
} // sto nello span E nell'immagine
} // il punto e' cromaticompatibile
lxAux++;
// rowStart = ras32->pixels(yAux);
while (xAux < m_imageWidth) { // mi sposto a destra lungo la X
if (inSpan) {
tmpPix = rowStart + xAux;
maskPix = maskRowStart + xAux; //***
maskValue = pixelProcessor(tmpPix, maskPix);
if (maskValue) { // case 1
// fa tutto nella pixel processor
} // case 1
else { // case 2
EnqueueSegment(1, dirAux, pLxAux, pRxAux, lxAux, (xAux - 1),
yAux);
inSpan = false;
} // case 2
} // inSpan
else { // non ero nello span
if (xAux > rxAux) break;
tmpPix = rowStart + xAux;
maskPix = maskRowStart + xAux;
maskValue = pixelProcessor(tmpPix, maskPix);
if (maskValue) { // case 3
inSpan = true;
lxAux = xAux;
} // case 3
else { // case 4
}
} // non ero nello span
xAux++;
// TSystem::outputDebug("[MWfx("+toString(m_id_invocazione)+")]\tStack
// Size:"+toString((int)
// m_sSStack.size())+"\txAux:"+toString(xAux)+"\ty:"+toString(yAux)+"\n");
} // mi sposto a destra lungo la X: endloop 1
if (inSpan) {
EnqueueSegment(2, dirAux, pLxAux, pRxAux, lxAux, (xAux - 1), yAux);
}
} // finche' la pila non e' vuota: endloop 2
} // if m_cont
else { // anche le regioni simili NON contigue: questo rimane anche in caso
// di modifica della parte m_cont
for (int iy = 0; iy < m_imageHeigth; iy++) {
tmpPix = ras32->pixels(iy);
maskPix = m_maskGR8->pixels(iy);
for (int ix = 0; ix < m_imageWidth; ix++) {
maskValue = pixelProcessor(tmpPix, maskPix);
// if (maskValue) { } // if// il colore e' simile =>
// va incluso nella selezione // fa tutto nella pixel
// processor
tmpPix++;
maskPix++;
} // ix
} // iy
} // else m_cont
int blurRadius = (int)(m_blurRadius->getValue(frame));
if ((m_antial) && (blurRadius < 2)) blurRadius = 1;
if (blurRadius > 0)
TRop::blur(m_maskGR8, m_maskGR8, (blurRadius + 1), 0, 0);
// copio la maschera sull'alpha channel dell'immagine
// lo faccio a mano chiedere se esiste una funziona apposita
for (iy = 0; iy < m_imageHeigth; iy++) {
tmpPix = ras32->pixels(iy);
maskPix = m_maskGR8->pixels(iy);
for (int ix = 0; ix < m_imageWidth; ix++) {
tmpPix->m = maskPix->value;
tmpPix++;
maskPix++;
} // ix
} // iy
if (m_preMolt->getValue()) TRop::premultiply(ras32);
stop_time = clock();
double durata = (double)(stop_time - start_time) / CLOCKS_PER_SEC;
TSystem::outputDebug(
"\n#Pixel:\t" + toString(m_imageWidth * m_imageHeigth) + "\nProc:\t" +
toString(pixelProcessed) + "\t[" +
toString((pixelProcessed * 100 / (m_imageWidth * m_imageHeigth))) +
"%t]" + "\nMask:\t" + toString(pixelMasked) + "\t[" +
toString((pixelMasked * 100 / (m_imageWidth * m_imageHeigth))) + "%t]" +
"\t[" + toString((pixelMasked * 100 / (pixelProcessed))) + "%p]" +
"\nEnqu:\t" + toString(shadowEnqueued) + "\nRepr:\t" +
toString(pixelReprocessed) + "\t[" +
toString((pixelReprocessed * 100 / (m_imageWidth * m_imageHeigth))) +
"%t]" + "\t[" + toString((pixelReprocessed * 100 / (pixelProcessed))) +
"%p]" + "\nOutB:\t" + toString(shadowOutOfBorder) + "\t[" +
toString((shadowOutOfBorder * 100 / (shadowEnqueued))) + "%t]" +
"\nTime:\t" + toString(durata, 3) + " sec\n[MagicWandFX <end>]\n");
} // if (ras32)
else {
TRasterGR8P rasGR8 = tile.getRaster();
if (rasGR8) {
}
}
tile.getRaster()->unlock();
m_maskGR8->unlock();
} // doMagicWand
void MagicWandFx::doCompute(TTile &tile, double frame,
const TRasterFxRenderInfo *ri) {
if (!m_input.isConnected()) return;
m_input->compute(tile, frame, ri);
doMagicWand(tile, frame, ri);
2016-03-19 06:57:51 +13:00
}
//------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
TRect MagicWandFx::getInvalidRect(const TRect &max) { return max; }
2016-03-19 06:57:51 +13:00
//------------------------------------------------------------------
FX_PLUGIN_IDENTIFIER(MagicWandFx, magicWandFx);