diff --git a/rtdata/images/Dark/actions/spot-active.png b/rtdata/images/Dark/actions/spot-active.png
new file mode 100644
index 000000000..56bd35f47
Binary files /dev/null and b/rtdata/images/Dark/actions/spot-active.png differ
diff --git a/rtdata/images/Dark/actions/spot-normal.png b/rtdata/images/Dark/actions/spot-normal.png
new file mode 100644
index 000000000..15bb94cd2
Binary files /dev/null and b/rtdata/images/Dark/actions/spot-normal.png differ
diff --git a/rtdata/images/Dark/actions/spot-prelight.png b/rtdata/images/Dark/actions/spot-prelight.png
new file mode 100644
index 000000000..cb9e555bd
Binary files /dev/null and b/rtdata/images/Dark/actions/spot-prelight.png differ
diff --git a/rtdata/images/Light/actions/spot-active.png b/rtdata/images/Light/actions/spot-active.png
new file mode 100644
index 000000000..56bd35f47
Binary files /dev/null and b/rtdata/images/Light/actions/spot-active.png differ
diff --git a/rtdata/images/Light/actions/spot-normal.png b/rtdata/images/Light/actions/spot-normal.png
new file mode 100644
index 000000000..15bb94cd2
Binary files /dev/null and b/rtdata/images/Light/actions/spot-normal.png differ
diff --git a/rtdata/images/Light/actions/spot-prelight.png b/rtdata/images/Light/actions/spot-prelight.png
new file mode 100644
index 000000000..cb9e555bd
Binary files /dev/null and b/rtdata/images/Light/actions/spot-prelight.png differ
diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais
index 7cfe47960..9aaac3fa3 100644
--- a/rtdata/languages/Francais
+++ b/rtdata/languages/Francais
@@ -461,7 +461,7 @@ HISTORY_MSG_232;N&B - Type de courbe 'Avant'
HISTORY_MSG_233;N&B - Courbe 'Après'
HISTORY_MSG_234;N&B - Type de courbe 'Après'
HISTORY_MSG_235;N&B - Mixeur - Mode auto
-HISTORY_MSG_236;--inutilisé--
+HISTORY_MSG_236;Retrait de taches - Modif. de points
HISTORY_MSG_237;N&B - Mixeur
HISTORY_MSG_238;FD - Étendu
HISTORY_MSG_239;FD - Force
@@ -632,6 +632,7 @@ HISTORY_MSG_403;O - NB - Sensibilité des bords
HISTORY_MSG_404;O - NB - Base amplification
HISTORY_MSG_405;O - Débruitage - Niveau 4
HISTORY_MSG_406;O - NB - Pixels voisins
+HISTORY_MSG_407;Retrait de taches
HISTORY_NEWSNAPSHOT;Ajouter
HISTORY_NEWSNAPSHOT_TOOLTIP;Raccourci: Alt-s
HISTORY_SNAPSHOT;Capture
@@ -1643,6 +1644,9 @@ TP_SHARPENMICRO_AMOUNT;Quantité
TP_SHARPENMICRO_LABEL;Microcontraste
TP_SHARPENMICRO_MATRIX;Matrice 3×3 au lieu de 5×5
TP_SHARPENMICRO_UNIFORMITY;Uniformité
+TP_SPOT_COUNTLABEL;%1 point(s)
+TP_SPOT_ENTRYCHANGED;Modification d'un point
+TP_SPOT_LABEL;Retrait de taches
TP_VIBRANCE_AVOIDCOLORSHIFT;Éviter les dérives de teinte
TP_VIBRANCE_CURVEEDITOR_SKINTONES;TT
TP_VIBRANCE_CURVEEDITOR_SKINTONES_LABEL;Tons chair
diff --git a/rtdata/languages/default b/rtdata/languages/default
index c8d145390..53efbf5eb 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -465,7 +465,7 @@ HISTORY_MSG_232;B&W - 'Before' curve type
HISTORY_MSG_233;B&W - 'After' curve
HISTORY_MSG_234;B&W - 'After' curve type
HISTORY_MSG_235;B&W - Auto channel mixer
-HISTORY_MSG_236;--unused--
+HISTORY_MSG_236;Spot removal - Point modif.
HISTORY_MSG_237;B&W - Mixer
HISTORY_MSG_238;GF - Feather
HISTORY_MSG_239;GF - Strength
@@ -670,6 +670,7 @@ HISTORY_MSG_437;Retinex - M - Method
HISTORY_MSG_438;Retinex - M - Equalizer
HISTORY_MSG_439;Retinex - Preview
HISTORY_MSG_440;CbDL - Method
+HISTORY_MSG_441;Spot removal
HISTORY_NEWSNAPSHOT;Add
HISTORY_NEWSNAPSHOT_TOOLTIP;Shortcut: Alt-s
HISTORY_SNAPSHOT;Snapshot
@@ -1774,6 +1775,9 @@ TP_SHARPENMICRO_AMOUNT;Quantity
TP_SHARPENMICRO_LABEL;Microcontrast
TP_SHARPENMICRO_MATRIX;3×3 matrix instead of 5×5
TP_SHARPENMICRO_UNIFORMITY;Uniformity
+TP_SPOT_COUNTLABEL;%1 point(s)
+TP_SPOT_ENTRYCHANGED;Point changed
+TP_SPOT_LABEL;Spot removal
TP_VIBRANCE_AVOIDCOLORSHIFT;Avoid color shift
TP_VIBRANCE_CURVEEDITOR_SKINTONES;HH
TP_VIBRANCE_CURVEEDITOR_SKINTONES_LABEL;Skin-tones
diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt
index 1f4f3375f..a1ab06657 100644
--- a/rtengine/CMakeLists.txt
+++ b/rtengine/CMakeLists.txt
@@ -11,7 +11,7 @@ set (RTENGINESOURCEFILES colortemp.cc curves.cc flatcurves.cc diagonalcurves.cc
dfmanager.cc ffmanager.cc gauss.cc rawimage.cc image8.cc image16.cc imagefloat.cc imagedata.cc imageio.cc improcfun.cc init.cc dcrop.cc
loadinitial.cc procparams.cc rawimagesource.cc demosaic_algos.cc shmap.cc simpleprocess.cc refreshmap.cc
fast_demo.cc amaze_demosaic_RT.cc CA_correct_RT.cc cfa_linedn_RT.cc green_equil_RT.cc hilite_recon.cc expo_before_b.cc
- stdimagesource.cc myfile.cc iccjpeg.cc improccoordinator.cc pipettebuffer.cc coord.cc
+ stdimagesource.cc myfile.cc iccjpeg.cc improccoordinator.cc pipettebuffer.cc coord.cc alpha.cc spot.cc
processingjob.cc rtthumbnail.cc utils.cc labimage.cc slicer.cc cieimage.cc
iplab2rgb.cc ipsharpen.cc iptransform.cc ipresize.cc ipvibrance.cc
imagedimensions.cc jpeg_memsrc.cc jdatasrc.cc iimage.cc
diff --git a/rtengine/alpha.cc b/rtengine/alpha.cc
new file mode 100644
index 000000000..e562350fc
--- /dev/null
+++ b/rtengine/alpha.cc
@@ -0,0 +1,75 @@
+/*
+ * This file is part of RawTherapee.
+ *
+ * Copyright (c) 2004-2010 Gabor Horvath
+ *
+ * RawTherapee is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * RawTherapee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with RawTherapee. If not, see .
+ */
+
+#include "alpha.h"
+
+namespace rtengine
+{
+
+Alpha::Alpha () {}
+
+Alpha::Alpha (int width, int height)
+{
+ if (width > 0 && height > 0) {
+ surface = Cairo::ImageSurface::create(Cairo::FORMAT_A8, width, height);
+ }
+}
+
+/*
+Alpha::~Alpha () {
+ surface->unreference();
+}
+*/
+
+void Alpha::setSize(int width, int height)
+{
+ if (width > 0 && height > 0) {
+ if (surface) {
+ if (width != getWidth() && height != getHeight()) {
+ surface.clear(); // does this delete the referenced object? Unreferencing doesn't work, since Cairo expect to have a non null refCount in the destructor!
+ } else {
+ return;
+ }
+ }
+
+ surface = Cairo::ImageSurface::create(Cairo::FORMAT_A8, width, height);
+ } else if (surface) {
+ surface.clear();
+ }
+}
+
+int Alpha::getWidth()
+{
+ if (surface) {
+ return surface->get_width();
+ }
+
+ return -1;
+}
+
+int Alpha::getHeight()
+{
+ if (surface) {
+ return surface->get_height();
+ }
+
+ return -1;
+}
+
+}
diff --git a/rtengine/alpha.h b/rtengine/alpha.h
new file mode 100644
index 000000000..57a0af343
--- /dev/null
+++ b/rtengine/alpha.h
@@ -0,0 +1,77 @@
+/*
+ * This file is part of RawTherapee.
+ *
+ * Copyright (c) 2004-2010 Gabor Horvath
+ *
+ * RawTherapee is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * RawTherapee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with RawTherapee. If not, see .
+ */
+#ifndef _ALPHA_H_
+#define _ALPHA_H_
+
+#include
+#include
+
+#define CHECK_BOUNDS 0
+
+namespace rtengine
+{
+
+/// Alpha channel class (8 bits)
+class Alpha
+{
+protected:
+ Cairo::RefPtr surface;
+
+public:
+ Alpha ();
+ Alpha (int width, int height);
+ //~Alpha ();
+
+ void setSize(int width, int height);
+ int getWidth();
+ int getHeight();
+
+ const Cairo::RefPtr getSurface ();
+
+ // TODO: to make the editing faster, we should add an iterator class
+
+ // Will send back the start of a row
+ unsigned char* operator () (unsigned row) const;
+ // Will send back a value at a given row, col position
+ unsigned char& operator () (unsigned row, unsigned col);
+ const unsigned char operator () (unsigned row, unsigned col) const;
+};
+
+
+
+inline const Cairo::RefPtr Alpha::getSurface () {
+ return surface; // to be used in bitmap edition
+}
+
+inline const unsigned char Alpha::operator () (unsigned row,
+ unsigned col) const {
+ return *(surface->get_data () + row * surface->get_width () + col);
+}
+
+inline unsigned char& Alpha::operator () (unsigned row, unsigned col) {
+ return *(surface->get_data () + row * surface->get_width () + col);
+}
+
+inline unsigned char* Alpha::operator () (unsigned row) const {
+ return surface->get_data () + row * surface->get_width ();
+}
+
+}
+
+#endif
diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc
index 77f0598b4..ac571d057 100644
--- a/rtengine/dcrop.cc
+++ b/rtengine/dcrop.cc
@@ -31,7 +31,7 @@ namespace rtengine
extern const Settings* settings;
Crop::Crop (ImProcCoordinator* parent, EditDataProvider *editDataProvider, bool isDetailWindow)
- : PipetteBuffer(editDataProvider), origCrop(NULL), laboCrop(NULL), labnCrop(NULL),
+ : PipetteBuffer(editDataProvider), origCrop(NULL), spotCrop(NULL), laboCrop(NULL), labnCrop(NULL),
cropImg(NULL), cbuf_real(NULL), cshmap(NULL), transCrop(NULL), cieCrop(NULL), cbuffer(NULL),
updating(false), newUpdatePending(false), skip(10),
cropx(0), cropy(0), cropw(-1), croph(-1),
@@ -215,18 +215,18 @@ void Crop::update (int todo)
if(settings->leveldnautsimpl == 1) {
if(params.dirpyrDenoise.Cmethod == "MAN" || params.dirpyrDenoise.Cmethod == "PON" ) {
PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
- parent->imgsrc->getImage (parent->currWB, tr, origCrop, pp, params.toneCurve, params.icm, params.raw );
+ parent->imgsrc->getImage (parent->currWB, tr, baseCrop, pp, params.toneCurve, params.icm, params.raw );
}
} else {
if(params.dirpyrDenoise.C2method == "MANU") {
PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
- parent->imgsrc->getImage (parent->currWB, tr, origCrop, pp, params.toneCurve, params.icm, params.raw );
+ parent->imgsrc->getImage (parent->currWB, tr, baseCrop, pp, params.toneCurve, params.icm, params.raw );
}
}
if((settings->leveldnautsimpl == 1 && params.dirpyrDenoise.Cmethod == "PRE") || (settings->leveldnautsimpl == 0 && params.dirpyrDenoise.C2method == "PREV")) {
PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
- parent->imgsrc->getImage (parent->currWB, tr, origCrop, pp, params.toneCurve, params.icm, params.raw );
+ parent->imgsrc->getImage (parent->currWB, tr, baseCrop, pp, params.toneCurve, params.icm, params.raw );
if((!isDetailWindow) && parent->adnListener && skip == 1 && params.dirpyrDenoise.enabled) {
float lowdenoise = 1.f;
@@ -308,15 +308,15 @@ void Crop::update (int todo)
//setCropSizes (centerTile_X[poscenterX], centerTile_Y[poscenterY], trafw*skip,trafh*skip , skip, true);
// we only need image reduced to 1/4 here
- int W = origCrop->getWidth();
- int H = origCrop->getHeight();
+ int W = baseCrop->getWidth();
+ int H = baseCrop->getHeight();
Imagefloat *provicalc = new Imagefloat ((W + 1) / 2, (H + 1) / 2); //for denoise curves
for(int ii = 0; ii < H; ii += 2) {
for(int jj = 0; jj < W; jj += 2) {
- provicalc->r(ii >> 1, jj >> 1) = origCrop->r(ii, jj);
- provicalc->g(ii >> 1, jj >> 1) = origCrop->g(ii, jj);
- provicalc->b(ii >> 1, jj >> 1) = origCrop->b(ii, jj);
+ provicalc->r(ii >> 1, jj >> 1) = baseCrop->r(ii, jj);
+ provicalc->g(ii >> 1, jj >> 1) = baseCrop->g(ii, jj);
+ provicalc->b(ii >> 1, jj >> 1) = baseCrop->b(ii, jj);
}
}
@@ -337,7 +337,7 @@ void Crop::update (int todo)
LUTf gamcurve(65536, 0);
float gam, gamthresh, gamslope;
parent->ipf.RGB_denoise_infoGamCurve(params.dirpyrDenoise, parent->imgsrc->isRAW(), gamcurve, gam, gamthresh, gamslope);
- parent->ipf.RGB_denoise_info(origCrop, provicalc, parent->imgsrc->isRAW(), gamcurve, gam, gamthresh, gamslope, params.dirpyrDenoise, parent->imgsrc->getDirPyrDenoiseExpComp(), chaut, Nb, redaut, blueaut, maxredaut, maxblueaut, minredaut, minblueaut, nresi, highresi, chromina, sigma, lumema, sigma_L, redyel, skinc, nsknc, true);
+ parent->ipf.RGB_denoise_info(baseCrop, provicalc, parent->imgsrc->isRAW(), gamcurve, gam, gamthresh, gamslope, params.dirpyrDenoise, parent->imgsrc->getDirPyrDenoiseExpComp(), chaut, Nb, redaut, blueaut, maxredaut, maxblueaut, minredaut, minblueaut, nresi, highresi, chromina, sigma, lumema, sigma_L, redyel, skinc, nsknc, true);
// printf("redy=%f skin=%f pcskin=%f\n",redyel, skinc,nsknc);
// printf("DCROP skip=%d cha=%4.0f Nb=%d red=%4.0f bl=%4.0f redM=%4.0f bluM=%4.0f L=%4.0f sigL=%4.0f Ch=%4.0f Si=%4.0f\n",skip, chaut,Nb, redaut,blueaut, maxredaut, maxblueaut, lumema, sigma_L, chromina, sigma);
float multip = 1.f;
@@ -600,10 +600,10 @@ void Crop::update (int todo)
//end evaluate noise
}
- // if(params.dirpyrDenoise.Cmethod=="AUT" || params.dirpyrDenoise.Cmethod=="PON") {//reinit origCrop after Auto
- if((settings->leveldnautsimpl == 1 && params.dirpyrDenoise.Cmethod == "AUT") || (settings->leveldnautsimpl == 0 && params.dirpyrDenoise.C2method == "AUTO")) { //reinit origCrop after Auto
+ // if(params.dirpyrDenoise.Cmethod=="AUT" || params.dirpyrDenoise.Cmethod=="PON") {//reinit baseCrop after Auto
+ if((settings->leveldnautsimpl == 1 && params.dirpyrDenoise.Cmethod == "AUT") || (settings->leveldnautsimpl == 0 && params.dirpyrDenoise.C2method == "AUTO")) { //reinit baseCrop after Auto
PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
- parent->imgsrc->getImage (parent->currWB, tr, origCrop, pp, params.toneCurve, params.icm, params.raw );
+ parent->imgsrc->getImage (parent->currWB, tr, baseCrop, pp, params.toneCurve, params.icm, params.raw );
}
DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise;
@@ -620,15 +620,15 @@ void Crop::update (int todo)
if((noiseLCurve || noiseCCurve ) && skip == 1 && denoiseParams.enabled) { //only allocate memory if enabled and skip
// we only need image reduced to 1/4 here
- int W = origCrop->getWidth();
- int H = origCrop->getHeight();
+ int W = baseCrop->getWidth();
+ int H = baseCrop->getHeight();
calclum = new Imagefloat ((W + 1) / 2, (H + 1) / 2); //for denoise curves
for(int ii = 0; ii < H; ii += 2) {
for(int jj = 0; jj < W; jj += 2) {
- calclum->r(ii >> 1, jj >> 1) = origCrop->r(ii, jj);
- calclum->g(ii >> 1, jj >> 1) = origCrop->g(ii, jj);
- calclum->b(ii >> 1, jj >> 1) = origCrop->b(ii, jj);
+ calclum->r(ii >> 1, jj >> 1) = baseCrop->r(ii, jj);
+ calclum->g(ii >> 1, jj >> 1) = baseCrop->g(ii, jj);
+ calclum->b(ii >> 1, jj >> 1) = baseCrop->b(ii, jj);
}
}
@@ -644,7 +644,7 @@ void Crop::update (int todo)
int kall = 0;
float chaut, redaut, blueaut, maxredaut, maxblueaut, nresi, highresi;
- parent->ipf.RGB_denoise(kall, origCrop, origCrop, calclum, ch_M, max_r, max_b, parent->imgsrc->isRAW(), /*Roffset,*/ denoiseParams, parent->imgsrc->getDirPyrDenoiseExpComp(), noiseLCurve, noiseCCurve, chaut, redaut, blueaut, maxredaut, maxblueaut, nresi, highresi);
+ parent->ipf.RGB_denoise(kall, baseCrop, baseCrop, calclum, ch_M, max_r, max_b, parent->imgsrc->isRAW(), /*Roffset,*/ denoiseParams, parent->imgsrc->getDirPyrDenoiseExpComp(), noiseLCurve, noiseCCurve, chaut, redaut, blueaut, maxredaut, maxblueaut, nresi, highresi);
if (parent->adnListener) {
parent->adnListener->noiseChanged(nresi, highresi);
@@ -663,7 +663,7 @@ void Crop::update (int todo)
}
}
- parent->imgsrc->convertColorSpace(origCrop, params.icm, parent->currWB);
+ parent->imgsrc->convertColorSpace(baseCrop, params.icm, parent->currWB);
delete [] ch_M;
delete [] max_r;
@@ -771,6 +771,27 @@ void Crop::update (int todo)
satLimitOpacity = 100.f * (moyS - 0.85f * eqty); //-0.85 sigma==>20% pixels with low saturation
}
+ if (params.spot.enabled) {
+ if (todo & M_SPOT) {
+ if(!spotCrop) {
+ spotCrop = new Imagefloat (cropw, croph);
+ }
+ baseCrop->copyData(spotCrop);
+
+ PreviewProps pp (cropx, cropy, cropw, croph, skip);
+ parent->ipf.removeSpots(spotCrop, params.spot.entries, pp);
+ }
+ } else {
+ if (spotCrop) {
+ delete spotCrop;
+ spotCrop = NULL;
+ }
+ }
+
+ if (spotCrop) {
+ baseCrop = spotCrop;
+ }
+
if (todo & M_RGBCURVE) {
double rrm, ggm, bbm;
DCPProfile *dcpProf = parent->imgsrc->getDCP(params.icm, parent->currWB);
diff --git a/rtengine/dcrop.h b/rtengine/dcrop.h
index f1230bf01..9b2c3dbaf 100644
--- a/rtengine/dcrop.h
+++ b/rtengine/dcrop.h
@@ -42,6 +42,7 @@ class Crop : public DetailedCrop, public PipetteBuffer
protected:
// --- permanently allocated in RAM and only renewed on size changes
Imagefloat* origCrop; // "one chunk" allocation
+ Imagefloat* spotCrop; // "one chunk" allocation
LabImage* laboCrop; // "one chunk" allocation
LabImage* labnCrop; // "one chunk" allocation
Image8* cropImg; // "one chunk" allocation
diff --git a/rtengine/iimage.h b/rtengine/iimage.h
index 4b0450e54..1bf0ab97f 100644
--- a/rtengine/iimage.h
+++ b/rtengine/iimage.h
@@ -325,6 +325,23 @@ public:
}
}
+ /** Copy the a sub-region of the data to another PlanarRGBData */
+ void copyData(PlanarWhateverData *dest, int x, int y, int width, int height)
+ {
+ assert (dest != NULL);
+ // Make sure that the size is the same, reallocate if necessary
+ dest->allocate(width, height);
+
+ if (dest->width == -1) {
+ printf("ERROR: PlanarRGBData::copyData >>> allocation failed!\n");
+ return;
+ }
+
+ for (int i = y, j = 0; i < y + height; ++i, ++j) {
+ memcpy (dest->v(i) + x, v(j), width * sizeof(T));
+ }
+ }
+
void rotate (int deg)
{
@@ -727,6 +744,25 @@ public:
}
}
+ /** Copy the a sub-region of the data to another PlanarRGBData */
+ void copyData(PlanarRGBData *dest, int x, int y, int width, int height)
+ {
+ assert (dest != NULL);
+ // Make sure that the size is the same, reallocate if necessary
+ dest->allocate(width, height);
+
+ if (dest->width == -1) {
+ printf("ERROR: PlanarRGBData::copyData >>> allocation failed!\n");
+ return;
+ }
+
+ for (int i = y, j = 0; i < y + height; ++i, ++j) {
+ memcpy (dest->r(i) + x, r(j), width * sizeof(T));
+ memcpy (dest->g(i) + x, g(j), width * sizeof(T));
+ memcpy (dest->b(i) + x, b(j), width * sizeof(T));
+ }
+ }
+
void rotate (int deg)
{
@@ -1342,6 +1378,23 @@ public:
memcpy (dest->data, data, 3 * width * height * sizeof(T));
}
+ /** Copy the a sub-region of the data to another PlanarRGBData */
+ void copyData(ChunkyRGBData *dest, int x, int y, int width, int height)
+ {
+ assert (dest != NULL);
+ // Make sure that the size is the same, reallocate if necessary
+ dest->allocate(width, height);
+
+ if (dest->width == -1) {
+ printf("ERROR: PlanarRGBData::copyData >>> allocation failed!\n");
+ return;
+ }
+
+ for (int i = y, j = 0; i < y + height; ++i, ++j) {
+ memcpy (dest->r(i) + x, r(j), 3 * width * sizeof(T));
+ }
+ }
+
void rotate (int deg)
{
diff --git a/rtengine/image16.cc b/rtengine/image16.cc
index 8ba88bd4c..a822d6aac 100644
--- a/rtengine/image16.cc
+++ b/rtengine/image16.cc
@@ -124,7 +124,21 @@ Image16* Image16::copy ()
return cp;
}
-void Image16::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp)
+Image16* Image16::copySubRegion (int x, int y, int width, int height)
+{
+ Image16* cp = NULL;
+ int realWidth = LIM(x + width, 0, this->width) - x;
+ int realHeight = LIM(y + height, 0, this->height) - y;
+
+ if (realWidth > 0 && realHeight > 0) {
+ cp = new Image16 (realWidth, realHeight);
+ copyData(cp, x, y, realWidth, realHeight);
+ }
+
+ return cp;
+}
+
+void Image16::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp)
{
// compute channel multipliers
diff --git a/rtengine/image16.h b/rtengine/image16.h
index 0e1ac6786..975596fac 100644
--- a/rtengine/image16.h
+++ b/rtengine/image16.h
@@ -41,11 +41,12 @@ public:
~Image16 ();
Image16* copy ();
+ Image16* copySubRegion (int x, int y, int width, int height);
Image8* to8();
Imagefloat* tofloat();
- virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp);
+ virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp);
virtual const char* getType () const
{
diff --git a/rtengine/image8.cc b/rtengine/image8.cc
index b27851a76..da4bea6ef 100644
--- a/rtengine/image8.cc
+++ b/rtengine/image8.cc
@@ -94,7 +94,7 @@ Image8* Image8::copy ()
return cp;
}
-void Image8::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp)
+void Image8::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp)
{
// compute channel multipliers
double drm, dgm, dbm;
diff --git a/rtengine/image8.h b/rtengine/image8.h
index 188e20146..5f7cfea63 100644
--- a/rtengine/image8.h
+++ b/rtengine/image8.h
@@ -40,7 +40,7 @@ public:
Image8* copy ();
- virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp);
+ virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp);
virtual const char* getType () const
{
diff --git a/rtengine/imagedimensions.cc b/rtengine/imagedimensions.cc
index f7d291483..a3c22cfbc 100644
--- a/rtengine/imagedimensions.cc
+++ b/rtengine/imagedimensions.cc
@@ -20,7 +20,7 @@
#include "imagedimensions.h"
#include "rtengine.h"
-void ImageDimensions::transform (PreviewProps pp, int tran, int &sx1, int &sy1, int &sx2, int &sy2)
+void ImageDimensions::transform (const PreviewProps & pp, int tran, int &sx1, int &sy1, int &sx2, int &sy2)
{
int sw = width, sh = height;
diff --git a/rtengine/imagedimensions.h b/rtengine/imagedimensions.h
index e3b98f7c5..485209844 100644
--- a/rtengine/imagedimensions.h
+++ b/rtengine/imagedimensions.h
@@ -24,8 +24,9 @@ class PreviewProps
{
public:
int x, y, w, h, skip;
- PreviewProps (int _x, int _y, int _w, int _h, int _skip)
- : x(_x), y(_y), w(_w), h(_h), skip(_skip) {}
+ PreviewProps (int x, int y, int w, int h, int skip);
+
+ void set (int x, int y, int w, int h, int skip);
};
/*
@@ -39,25 +40,44 @@ public:
int height;
public:
- ImageDimensions() : width(-1), height(-1) {}
- int getW ()
- {
- return width;
- }
- int getH ()
- {
- return height;
- }
- int getWidth () const
- {
- return width;
- }
- int getHeight () const
- {
- return height;
- }
- void transform (PreviewProps pp, int tran, int &sx1, int &sy1, int &sx2, int &sy2);
+ ImageDimensions ();
+ int getW ();
+ int getH ();
+ int getWidth () const;
+ int getHeight () const;
+ void transform (const PreviewProps & pp, int tran, int &sx1, int &sy1, int &sx2, int &sy2);
};
+inline PreviewProps::PreviewProps (int x, int y, int w, int h, int skip) :
+ x (x), y (y), w (w), h (h), skip (skip) {
+}
+
+inline void PreviewProps::set (int x, int y, int w, int h, int skip) {
+ this->x = x;
+ this->y = y;
+ this->w = w;
+ this->h = h;
+ this->skip = skip;
+}
+
+inline ImageDimensions::ImageDimensions () :
+ width (-1), height (-1) {
+}
+
+inline int ImageDimensions::getW () {
+ return width;
+}
+
+inline int ImageDimensions::getH () {
+ return height;
+}
+
+inline int ImageDimensions::getWidth () const {
+ return width;
+}
+
+inline int ImageDimensions::getHeight () const {
+ return height;
+}
#endif
diff --git a/rtengine/imagefloat.cc b/rtengine/imagefloat.cc
index 26ea8ae6e..fae39069b 100644
--- a/rtengine/imagefloat.cc
+++ b/rtengine/imagefloat.cc
@@ -170,8 +170,22 @@ Imagefloat* Imagefloat::copy ()
return cp;
}
+Imagefloat* Imagefloat::copySubRegion (int x, int y, int width, int height)
+{
+ Imagefloat* cp = NULL;
+ int realWidth = LIM(x + width, 0, this->width) - x;
+ int realHeight = LIM(y + height, 0, this->height) - y;
+
+ if (realWidth > 0 && realHeight > 0) {
+ cp = new Imagefloat (realWidth, realHeight);
+ copyData(cp, x, y, realWidth, realHeight);
+ }
+
+ return cp;
+}
+
// This is called by the StdImageSource class. We assume that fp images from StdImageSource don't have to deal with gamma
-void Imagefloat::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp)
+void Imagefloat::getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp)
{
// compute channel multipliers
diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h
index 50b0d4aeb..649799cc2 100644
--- a/rtengine/imagefloat.h
+++ b/rtengine/imagefloat.h
@@ -45,11 +45,12 @@ public:
~Imagefloat ();
Imagefloat* copy ();
+ Imagefloat* copySubRegion (int x, int y, int width, int height);
Image8* to8();
Image16* to16();
- virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp);
+ virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp);
virtual const char* getType () const
{
diff --git a/rtengine/imageio.h b/rtengine/imageio.h
index 1a13458c1..fc53d6cf4 100644
--- a/rtengine/imageio.h
+++ b/rtengine/imageio.h
@@ -130,7 +130,7 @@ public:
return sampleArrangement;
}
- virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, bool first, procparams::ToneCurveParams hrp)
+ virtual void getStdImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, bool first, procparams::ToneCurveParams hrp)
{
printf("getStdImage NULL!\n");
}
diff --git a/rtengine/imagesource.h b/rtengine/imagesource.h
index 80fc53f23..21317452e 100644
--- a/rtengine/imagesource.h
+++ b/rtengine/imagesource.h
@@ -81,7 +81,7 @@ public:
virtual bool IsrgbSourceModified() = 0; // tracks whether cached rgb output of demosaic has been modified
// use right after demosaicing image, add coarse transformation and put the result in the provided Imagefloat*
- virtual void getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, ToneCurveParams hlp, ColorManagementParams cmp, RAWParams raw) {}
+ virtual void getImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, ToneCurveParams hlp, ColorManagementParams cmp, RAWParams raw) {}
virtual eSensorType getSensorType ()
{
return ST_NONE;
diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc
index 2f14c1ded..0ff75ba38 100644
--- a/rtengine/improccoordinator.cc
+++ b/rtengine/improccoordinator.cc
@@ -30,8 +30,8 @@ namespace rtengine
extern const Settings* settings;
ImProcCoordinator::ImProcCoordinator ()
- : orig_prev(NULL), oprevi(NULL), oprevl(NULL), nprevl(NULL), previmg(NULL), workimg(NULL),
- ncie(NULL), imgsrc(NULL), shmap(NULL), lastAwbEqual(0.), ipf(¶ms, true), monitorIntent(RI_RELATIVE), scale(10),
+ : orig_prev(NULL), oprevi(NULL), spotprevi(NULL), oprevl(NULL), nprevl(NULL), previmg(NULL), workimg(NULL),
+ ncie(NULL), imgsrc(NULL), shmap(NULL), lastAwbEqual(0.), ipf(¶ms, true), previewProps(-1, -1, -1, -1, 1), monitorIntent(RI_RELATIVE), scale(10),
highDetailPreprocessComputed(false), highDetailRawComputed(false), allocated(false),
bwAutoR(-9000.f), bwAutoG(-9000.f), bwAutoB(-9000.f), CAMMean(0.),
@@ -136,7 +136,7 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
{
MyMutex::MyLock processingLock(mProcessing);
- int numofphases = 14;
+ int numofphases = 15;
int readyphase = 0;
bwAutoR = bwAutoG = bwAutoB = -9000.f;
@@ -316,11 +316,11 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
// Will (re)allocate the preview's buffers
setScale (scale);
- PreviewProps pp (0, 0, fw, fh, scale);
+ previewProps.set(0, 0, fw, fh, scale);
// Tells to the ImProcFunctions' tools what is the preview scale, which may lead to some simplifications
ipf.setScale (scale);
- imgsrc->getImage (currWB, tr, orig_prev, pp, params.toneCurve, params.icm, params.raw);
+ imgsrc->getImage (currWB, tr, orig_prev, previewProps, params.toneCurve, params.icm, params.raw);
//ColorTemp::CAT02 (orig_prev, ¶ms) ;
// printf("orig_prevW=%d\n scale=%d",orig_prev->width, scale);
/* Issue 2785, disabled some 1:1 tools
@@ -377,11 +377,11 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
if (!needstransform && orig_prev != oprevi) {
delete oprevi;
- oprevi = orig_prev;
+ spotprevi = oprevi = orig_prev;
}
if (needstransform && orig_prev == oprevi) {
- oprevi = new Imagefloat (pW, pH);
+ spotprevi = oprevi = new Imagefloat (pW, pH);
}
if ((todo & M_TRANSFORM) && needstransform)
@@ -433,6 +433,24 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
}
}
+ progress ("Spot Removal...", 100 * readyphase / numofphases);
+
+ if ((todo & M_SPOT) && params.spot.enabled && !params.spot.entries.empty()) {
+ if(spotprevi == oprevi) {
+ spotprevi = new Imagefloat (pW, pH);
+ }
+
+ oprevi->copyData(spotprevi);
+ ipf.removeSpots(spotprevi, params.spot.entries, previewProps);
+ } else {
+ if (spotprevi != oprevi) {
+ delete spotprevi;
+ spotprevi = oprevi;
+ }
+ }
+
+ readyphase++;
+
progress ("Exposure curve & CIELAB conversion...", 100 * readyphase / numofphases);
if ((todo & M_RGBCURVE) || (todo & M_CROP)) {
@@ -445,9 +463,9 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
params.toneCurve.curveMode, params.toneCurve.curve, params.toneCurve.curveMode2, params.toneCurve.curve2,
vhist16, histCropped, hltonecurve, shtonecurve, tonecurve, histToneCurve, customToneCurve1, customToneCurve2, scale == 1 ? 1 : 1);
- CurveFactory::RGBCurve (params.rgbCurves.rcurve, rCurve, scale == 1 ? 1 : 1);
- CurveFactory::RGBCurve (params.rgbCurves.gcurve, gCurve, scale == 1 ? 1 : 1);
- CurveFactory::RGBCurve (params.rgbCurves.bcurve, bCurve, scale == 1 ? 1 : 1);
+ CurveFactory::RGBCurve (params.rgbCurves.rcurve, rCurve, /*scale==1 ? 1 :*/ 1);
+ CurveFactory::RGBCurve (params.rgbCurves.gcurve, gCurve, /*scale==1 ? 1 :*/ 1);
+ CurveFactory::RGBCurve (params.rgbCurves.bcurve, bCurve, /*scale==1 ? 1 :*/ 1);
TMatrix wprof = iccStore->workingSpaceMatrix (params.icm.working);
@@ -484,7 +502,7 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
if(params.colorToning.enabled && params.colorToning.autosat) { //for colortoning evaluation of saturation settings
float moyS = 0.f;
float eqty = 0.f;
- ipf.moyeqt (oprevi, moyS, eqty);//return image : mean saturation and standard dev of saturation
+ ipf.moyeqt (spotprevi, moyS, eqty);//return image : mean saturation and standard dev of saturation
//printf("moy=%f ET=%f\n", moyS,eqty);
float satp = ((moyS + 1.5f * eqty) - 0.3f) / 0.7f; //1.5 sigma ==> 93% pixels with high saturation -0.3 / 0.7 convert to Hombre scale
@@ -537,7 +555,7 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall)
double bbm = 33.;
DCPProfile *dcpProf = imgsrc->getDCP(params.icm, currWB);
- ipf.rgbProc (oprevi, oprevl, NULL, hltonecurve, shtonecurve, tonecurve, shmap, params.toneCurve.saturation,
+ ipf.rgbProc (spotprevi, oprevl, NULL, hltonecurve, shtonecurve, tonecurve, shmap, params.toneCurve.saturation,
rCurve, gCurve, bCurve, satLimit , satLimitOpacity, ctColorCurve, ctOpacityCurve, opautili, clToningcurve, cl2Toningcurve, customToneCurve1, customToneCurve2, beforeToneCurveBW, afterToneCurveBW, rrm, ggm, bbm, bwAutoR, bwAutoG, bwAutoB, params.toneCurve.expcomp, params.toneCurve.hlcompr, params.toneCurve.hlcomprthresh, dcpProf);
if(params.blackwhite.enabled && params.blackwhite.autoc && abwListener) {
@@ -867,11 +885,16 @@ void ImProcCoordinator::freeAll ()
}
if (allocated) {
- if (orig_prev != oprevi) {
+ if (spotprevi && spotprevi != oprevi) {
+ delete spotprevi;
+ }
+ spotprevi = NULL;
+
+ if (oprevi && oprevi != orig_prev) {
delete oprevi;
}
-
oprevi = NULL;
+
delete orig_prev;
orig_prev = NULL;
delete oprevl;
@@ -882,7 +905,6 @@ void ImProcCoordinator::freeAll ()
if (ncie) {
delete ncie;
}
-
ncie = NULL;
if (imageListener) {
@@ -928,7 +950,7 @@ void ImProcCoordinator::setScale (int prevscale)
prevscale--;
PreviewProps pp (0, 0, fw, fh, prevscale);
imgsrc->getSize (tr, pp, nW, nH);
- } while(nH < 400 && prevscale > 1 && (nW * nH < 1000000) ); // sctually hardcoded values, perhaps a better choice is possible
+ } while(nH < 400 && prevscale > 1 && (nW * nH < 1000000) ); // actually hardcoded values, perhaps a better choice is possible
if (settings->verbose) {
printf ("setscale starts (%d, %d)\n", nW, nH);
@@ -942,7 +964,7 @@ void ImProcCoordinator::setScale (int prevscale)
pH = nH;
orig_prev = new Imagefloat (pW, pH);
- oprevi = orig_prev;
+ spotprevi = oprevi = orig_prev;
oprevl = new LabImage (pW, pH);
nprevl = new LabImage (pW, pH);
//ncie is only used in ImProcCoordinator::updatePreviewImage, it will be allocated on first use and deleted if not used anymore
diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h
index 4d442482e..4d9a5b5c1 100644
--- a/rtengine/improccoordinator.h
+++ b/rtengine/improccoordinator.h
@@ -55,6 +55,7 @@ class ImProcCoordinator : public StagedImageProcessor
protected:
Imagefloat *orig_prev;
Imagefloat *oprevi;
+ Imagefloat *spotprevi;
LabImage *oprevl;
LabImage *nprevl;
Image8 *previmg;
@@ -71,6 +72,7 @@ protected:
double lastAwbEqual;
ImProcFunctions ipf;
+ PreviewProps previewProps;
Glib::ustring monitorProfile;
diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h
index 90a046149..d67256a63 100644
--- a/rtengine/improcfun.h
+++ b/rtengine/improcfun.h
@@ -354,6 +354,9 @@ public:
float Mad(float * DataList, const int datalen);
float MadRgb(float * DataList, const int datalen);
+ // spot removal tool
+ void removeSpots (Imagefloat* img, const std::vector &entries, const PreviewProps &pp);
+
// pyramid wavelet
void dirpyr_equalizer (float ** src, float ** dst, int srcwidth, int srcheight, float ** l_a, float ** l_b, float ** dest_a, float ** dest_b, const double * mult, const double dirpyrThreshold, const double skinprot, const bool gamutlab, float b_l, float t_l, float t_r, float b_r, int choice, int scale);//Emil's directional pyramid wavelet
void dirpyr_equalizercam (CieImage* ncie, float ** src, float ** dst, int srcwidth, int srcheight, float ** h_p, float ** C_p, const double * mult, const double dirpyrThreshold, const double skinprot, bool execdir, const bool gamutlab, float b_l, float t_l, float t_r, float b_r, int choice, int scale);//Emil's directional pyramid wavelet
diff --git a/rtengine/procevents.h b/rtengine/procevents.h
index 3d4f90af1..20b099d6d 100644
--- a/rtengine/procevents.h
+++ b/rtengine/procevents.h
@@ -262,7 +262,7 @@ enum ProcEvent {
EvBWAfterCurve = 232,
EvBWAfterCurveMode = 233,
EvAutoch = 234,
-// EvFixedch=235, -- can be reused --
+ EvSpotEntry = 235,
EvNeutralBW = 236,
EvGradientFeather = 237,
EvGradientStrength = 238,
@@ -467,6 +467,7 @@ enum ProcEvent {
EvRetinexmapcurve = 437,
EvviewMethod = 438,
EvcbdlMethod = 439,
+ EvSpotEnabled = 440,
NUMOFEVENTS
};
diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc
index 9c4ddbd58..86d88240f 100644
--- a/rtengine/procparams.cc
+++ b/rtengine/procparams.cc
@@ -46,6 +46,9 @@ const char *RAWParams::XTransSensor::methodstring[RAWParams::XTransSensor::numMe
const char *RAWParams::ff_BlurTypestring[RAWParams::numFlatFileBlurTypes] = {/*"Parametric",*/ "Area Flatfield", "Vertical Flatfield", "Horizontal Flatfield", "V+H Flatfield"};
std::vector WBParams::wbEntries;
+const short SpotParams::minRadius = 5;
+const short SpotParams::maxRadius = 35;
+
bool ToneCurveParams::HLReconstructionNecessary(LUTu &histRedRaw, LUTu &histGreenRaw, LUTu &histBlueRaw)
{
if (options.rtSettings.verbose)
@@ -856,6 +859,12 @@ void CoarseTransformParams::setDefaults()
vflip = false;
}
+void SpotParams::setDefaults()
+{
+ enabled = false;
+ entries.clear();
+}
+
void RAWParams::setDefaults()
{
bayersensor.method = RAWParams::BayerSensor::methodstring[RAWParams::BayerSensor::amaze];
@@ -2488,7 +2497,7 @@ int ProcParams::save (Glib::ustring fname, Glib::ustring fname2, bool fnameAbsol
keyFile.set_integer ("Vignetting Correction", "CenterY", vignetting.centerY);
}
-
+ // save resizing settings
if (!pedited || pedited->resize.enabled) {
keyFile.set_boolean ("Resize", "Enabled", resize.enabled);
}
@@ -2517,6 +2526,28 @@ int ProcParams::save (Glib::ustring fname, Glib::ustring fname2, bool fnameAbsol
keyFile.set_integer ("Resize", "Height", resize.height);
}
+ // save spot removal settings
+ if (!pedited || pedited->spot.enabled) {
+ keyFile.set_boolean ("Spot removal", "Enabled", spot.enabled);
+ }
+
+ if (!pedited || pedited->spot.entries) {
+ std::vector vEntries;
+
+ for (size_t i = 0; i < spot.entries.size(); ++i) {
+ vEntries.push_back(double(spot.entries.at(i).sourcePos.x));
+ vEntries.push_back(double(spot.entries.at(i).sourcePos.y));
+ vEntries.push_back(double(spot.entries.at(i).targetPos.x));
+ vEntries.push_back(double(spot.entries.at(i).targetPos.y));
+ vEntries.push_back(double(spot.entries.at(i).radius));
+ vEntries.push_back(double(spot.entries.at(i).feather));
+ vEntries.push_back(double(spot.entries.at(i).opacity));
+ }
+
+ Glib::ArrayHandle entries = vEntries;
+ keyFile.set_double_list("Spot removal", "Entries", entries);
+ }
+
if (!pedited || pedited->prsharpening.enabled) {
keyFile.set_boolean ("PostResizeSharpening", "Enabled", prsharpening.enabled);
}
@@ -5669,6 +5700,36 @@ int ProcParams::load (Glib::ustring fname, ParamsEdited* pedited)
}
}
+ // load spot removal settings
+ if (keyFile.has_group ("Spot removal")) {
+ if (keyFile.has_key ("Spot removal", "Enabled")) {
+ spot.enabled = keyFile.get_boolean ("Spot removal", "Enabled");
+
+ if (pedited) {
+ pedited->spot.enabled = true;
+ }
+ }
+
+ if (keyFile.has_key ("Spot removal", "Entries")) {
+ Glib::ArrayHandle entries = keyFile.get_double_list ("Spot removal", "Entries");
+ const double epsilon = 0.001;
+ SpotEntry entry;
+
+ for (size_t i = 0; i < entries.size(); i += 7) {
+ entry.sourcePos.set(int(entries.data()[i ] + epsilon), int(entries.data()[i + 1] + epsilon));
+ entry.targetPos.set(int(entries.data()[i + 2] + epsilon), int(entries.data()[i + 3] + epsilon));
+ entry.radius = LIM(int (entries.data()[i + 4] + epsilon), SpotParams::minRadius, SpotParams::maxRadius);
+ entry.feather = float(entries.data()[i + 5]);
+ entry.opacity = float(entries.data()[i + 6]);
+ spot.entries.push_back(entry);
+ }
+
+ if (pedited) {
+ pedited->spot.entries = true;
+ }
+ }
+ }
+
// load post resize sharpening
if (keyFile.has_group ("PostResizeSharpening")) {
if (keyFile.has_key ("PostResizeSharpening", "Enabled")) {
@@ -7787,6 +7848,8 @@ bool ProcParams::operator== (const ProcParams& other)
&& resize.dataspec == other.resize.dataspec
&& resize.width == other.resize.width
&& resize.height == other.resize.height
+ && spot.enabled == other.spot.enabled
+ && spot.entries == other.spot.entries
&& raw.bayersensor.method == other.raw.bayersensor.method
&& raw.bayersensor.ccSteps == other.raw.bayersensor.ccSteps
&& raw.bayersensor.black0 == other.raw.bayersensor.black0
diff --git a/rtengine/procparams.h b/rtengine/procparams.h
index 7946ce4d9..772cffd77 100644
--- a/rtengine/procparams.h
+++ b/rtengine/procparams.h
@@ -946,6 +946,55 @@ public:
int height;
};
+class SpotEntry
+{
+public:
+ Coord sourcePos;
+ Coord targetPos;
+ int radius;
+ float feather;
+ float opacity;
+
+ SpotEntry() : radius(5), feather(1.f), opacity(1.f) {}
+
+ bool operator== (const SpotEntry& other) const
+ {
+ return other.sourcePos == sourcePos && other.targetPos == targetPos &&
+ other.radius == radius && other.feather == feather && other.opacity == opacity;
+ }
+
+ bool operator!= (const SpotEntry& other) const
+ {
+ return other.sourcePos != sourcePos || other.targetPos != targetPos ||
+ other.radius != radius || other.feather != feather || other.opacity != opacity;
+ }
+
+
+};
+
+/**
+ * Parameters of the dust removal tool
+ */
+class SpotParams
+{
+
+public:
+ // REWRITE TODO: all parameter should have getter and setter to maintain their validity
+ // the following constant can be used to adjust the tools correctly
+ static const short minRadius;
+ static const short maxRadius;
+
+ bool enabled;
+ std::vector entries;
+
+ SpotParams()
+ {
+ setDefaults();
+ }
+ void setDefaults();
+};
+
+
/**
* Parameters of the color spaces used during the processing
*/
@@ -1279,6 +1328,7 @@ public:
ChannelMixerParams chmixer; ///< Channel mixer parameters
BlackWhiteParams blackwhite; ///< Black & White parameters
ResizeParams resize; ///< Resize parameters
+ SpotParams spot; ///< Spot removal tool
ColorManagementParams icm; ///< profiles/color spaces used during the image processing
RAWParams raw; ///< RAW parameters before demosaicing
WaveletParams wavelet; ///< Wavelet parameters
diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc
index 1a4e0dbb5..758a37b29 100644
--- a/rtengine/rawimagesource.cc
+++ b/rtengine/rawimagesource.cc
@@ -617,7 +617,7 @@ calculate_scale_mul(float scale_mul[4], const float pre_mul_[4], const float c_w
return gain;
}
-void RawImageSource::getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw )
+void RawImageSource::getImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw )
{
MyMutex::MyLock lock(getImageMutex);
diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h
index 0dd6c9098..241f8300b 100644
--- a/rtengine/rawimagesource.h
+++ b/rtengine/rawimagesource.h
@@ -137,7 +137,7 @@ public:
void cfaboxblur (RawImage *riFlatFile, float* cfablur, int boxH, int boxW );
void scaleColors (int winx, int winy, int winw, int winh, const RAWParams &raw); // raw for cblack
- void getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw);
+ void getImage (ColorTemp ctemp, int tran, Imagefloat* image, const PreviewProps & pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw);
eSensorType getSensorType ()
{
return ri != NULL ? ri->getSensorType() : ST_NONE;
diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc
index 26e8f53d4..52dae275e 100644
--- a/rtengine/refreshmap.cc
+++ b/rtengine/refreshmap.cc
@@ -119,8 +119,8 @@ int refreshmap[rtengine::NUMOFEVENTS] = {
ALLNORAW, // EvDPDNLuma,
ALLNORAW, // EvDPDNChroma,
ALLNORAW, // EvDPDNGamma,
- ALLNORAW, // EvDirPyrEqualizer,
- ALLNORAW, // EvDirPyrEqlEnabled,
+ ALLNORAW, // EvDirPyrEqualizer,
+ ALLNORAW, // EvDirPyrEqlEnabled,
LUMINANCECURVE, // EvLSaturation,
LUMINANCECURVE, // EvLaCurve,
LUMINANCECURVE, // EvLbCurve,
@@ -262,7 +262,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = {
RGBCURVE, // EvBWAfterCurve
RGBCURVE, // EvBWAfterCurveMode
RGBCURVE, // EvAutoch
- 0, // --unused--
+ SPOT, // EvSpotEntry
RGBCURVE, // EvNeutralBW
TRANSFORM, // EvGradientFeather
TRANSFORM, // EvGradientStrength
@@ -275,12 +275,12 @@ int refreshmap[rtengine::NUMOFEVENTS] = {
LUMINANCECURVE, // EvLCLCurve
LUMINANCECURVE, // EvLLHCurve
LUMINANCECURVE, // EvLHHCurve
- ALLNORAW, // EvDirPyrEqualizerThreshold
+ ALLNORAW, // EvDirPyrEqualizerThreshold
ALLNORAW, // EvDPDNenhance
RGBCURVE, // EvBWMethodalg
- ALLNORAW, // EvDirPyrEqualizerSkin
- ALLNORAW, // EvDirPyrEqlgamutlab
- ALLNORAW, // EvDirPyrEqualizerHueskin
+ ALLNORAW, // EvDirPyrEqualizerSkin
+ ALLNORAW, // EvDirPyrEqlgamutlab
+ ALLNORAW, // EvDirPyrEqualizerHueskin
ALLNORAW, // EvDPDNmedian
ALLNORAW, // EvDPDNmedmet
RGBCURVE, // EvColorToningEnabled
@@ -465,7 +465,8 @@ int refreshmap[rtengine::NUMOFEVENTS] = {
RETINEX, // EvLradius
RETINEX, // EvmapMethod
DEMOSAIC, // EvRetinexmapcurve
- DEMOSAIC, // EvviewMethod
- ALLNORAW // EvcbdlMethod
+ DEMOSAIC, // EvviewMethod
+ ALLNORAW, // EvcbdlMethod
+ SPOT // EvSpotEnabled
};
diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h
index 23e179f9f..adb8cbc27 100644
--- a/rtengine/refreshmap.h
+++ b/rtengine/refreshmap.h
@@ -20,15 +20,16 @@
#define __REFRESHMAP__
// Use M_VOID if you wish to update the proc params without updating the preview at all !
-#define M_VOID (1<<16)
+#define M_VOID (1<<31)
// Use M_MINUPDATE if you wish to update the preview without modifying the image (think about it like a "refreshPreview")
// Must NOT be used with other event (i.e. will be used for MINUPDATE only)
-#define M_MINUPDATE (1<<15)
+#define M_MINUPDATE (1<<30)
// Force high quality
-#define M_HIGHQUAL (1<<14)
+#define M_HIGHQUAL (1<<29)
// Elementary functions that can be done to
// the preview image when an event occurs
+#define M_SPOT (1<<14)
#define M_MONITOR (1<<13)
#define M_RETINEX (1<<12)
#define M_CROP (1<<11)
@@ -46,22 +47,23 @@
// Bitfield of functions to do to the preview image when an event occurs
// Use those or create new ones for your new events
-#define FIRST (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL
-#define ALL (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL
-#define DARKFRAME (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define ALLNORAW (M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define TRANSFORM (M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define AUTOEXP (M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define RGBCURVE (M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define LUMINANCECURVE (M_LUMACURVE|M_LUMINANCE|M_COLOR)
-#define SHARPENING (M_LUMINANCE|M_COLOR)
-#define IMPULSEDENOISE (M_LUMINANCE|M_COLOR)
-#define DEFRINGE (M_LUMINANCE|M_COLOR)
-#define DIRPYRDENOISE (M_LUMINANCE|M_COLOR)
-#define DIRPYREQUALIZER (M_LUMINANCE|M_COLOR)
-#define GAMMA (M_LUMINANCE|M_COLOR)
+#define FIRST (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL
+#define ALL (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) // without HIGHQUAL
+#define DARKFRAME (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define ALLNORAW (M_INIT|M_LINDENOISE|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define TRANSFORM (M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define AUTOEXP (M_AUTOEXP|M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define SPOT (M_SPOT|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define RGBCURVE (M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define LUMINANCECURVE (M_LUMACURVE|M_LUMINANCE|M_COLOR)
+#define SHARPENING (M_LUMINANCE|M_COLOR)
+#define IMPULSEDENOISE (M_LUMINANCE|M_COLOR)
+#define DEFRINGE (M_LUMINANCE|M_COLOR)
+#define DIRPYRDENOISE (M_LUMINANCE|M_COLOR)
+#define DIRPYREQUALIZER (M_LUMINANCE|M_COLOR)
+#define GAMMA (M_LUMINANCE|M_COLOR)
#define CROP M_CROP
#define RESIZE M_VOID
#define EXIF M_VOID
diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc
index c03addb42..15ebe5a5a 100644
--- a/rtengine/simpleprocess.cc
+++ b/rtengine/simpleprocess.cc
@@ -764,6 +764,10 @@ IImage16* processImage (ProcessingJob* pjob, int& errorCode, ProgressListener* p
shmap->update (baseImg, shradius, ipf.lumimul, params.sh.hq, 1);
}
+ if (params.spot.enabled && !params.spot.entries.empty()) {
+ ipf.removeSpots(baseImg, params.spot.entries, pp);
+ }
+
// RGB processing
LUTf curve1 (65536);
diff --git a/rtengine/spot.cc b/rtengine/spot.cc
new file mode 100644
index 000000000..b37c1d62c
--- /dev/null
+++ b/rtengine/spot.cc
@@ -0,0 +1,370 @@
+/*
+ * This file is part of RawTherapee.
+ *
+ * Copyright (c) 2004-2010 Gabor Horvath
+ *
+ * RawTherapee is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * RawTherapee is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with RawTherapee. If not, see .
+ */
+
+#include "improcfun.h"
+#include "alpha.h"
+
+namespace rtengine
+{
+
+/* Code taken from Gimp 2.8.10 and converted for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014
+ *
+ * ORIGINAL NOTES
+ *
+ * The method used here is similar to the lighting invariant correction
+ * method but slightly different: we do not divide the RGB components,
+ * but substract them I2 = I0 - I1, where I0 is the sample image to be
+ * corrected, I1 is the reference pattern. Then we solve DeltaI=0
+ * (Laplace) with I2 Dirichlet conditions at the borders of the
+ * mask. The solver is a unoptimized red/black checker Gauss-Siedel
+ * with an over-relaxation factor of 1.8. It can benefit from a
+ * multi-grid evaluation of an initial solution before the main
+ * iteration loop.
+ *
+ * I reduced the convergence criteria to 0.1% (0.001) as we are
+ * dealing here with RGB integer components, more is overkill.
+ *
+ * Jean-Yves Couleaud cjyves@free.fr
+ */
+
+/* Original Algorithm Design:
+ *
+ * T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning
+ * http://www.tgeorgiev.net/Photoshop_Healing.pdf
+ */
+void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector &entries, const PreviewProps &pp)
+{
+ Alpha mask;
+
+ //printf("img(%04d, %04d)\n", img->width, img->height);
+
+ for (const auto entry : entries) {
+ float srcX = float (entry.sourcePos.x);
+ float srcY = float (entry.sourcePos.y);
+ float dstX = float (entry.targetPos.x);
+ float dstY = float (entry.targetPos.y);
+ //float radius = float (entry.radius) + 0.5f;
+
+ float featherRadius = entry.radius * (1.f + entry.feather);
+ int scaledFeatherRadius = featherRadius / pp.skip;
+
+ int src_XMin = int ((srcX - featherRadius - pp.x) / float (pp.skip) + 0.5f);
+ int src_XMax = int ((srcX + featherRadius - pp.x) / float (pp.skip) + 0.5f);
+ int src_YMin = int ((srcY - featherRadius - pp.y) / float (pp.skip) + 0.5f);
+ int src_YMax = int ((srcY + featherRadius - pp.y) / float (pp.skip) + 0.5f);
+
+ int dst_XMin = int ((dstX - featherRadius - pp.x) / float (pp.skip) + 0.5f);
+ int dst_XMax = int ((dstX + featherRadius - pp.x) / float (pp.skip) + 0.5f);
+ int dst_YMin = int ((dstY - featherRadius - pp.y) / float (pp.skip) + 0.5f);
+ int dst_YMax = int ((dstY + featherRadius - pp.y) / float (pp.skip) + 0.5f);
+
+ //printf(" -> X: %04d > %04d\n -> Y: %04d > %04d\n", dst_XMin, dst_XMax, dst_YMin, dst_YMax);
+
+ // scaled spot is too small, we do not preview it
+ if (scaledFeatherRadius < 2 && pp.skip != 1) {
+#ifndef NDEBUG
+ if (options.rtSettings.verbose) {
+ printf ("Skipping spot located at %d x %d, too small for the preview zoom rate\n", entry.sourcePos.x, entry.sourcePos.y);
+ }
+#endif
+ continue;
+ }
+
+ // skipping entries totally transparent
+ if (entry.opacity == 0.) {
+#ifndef NDEBUG
+ if (options.rtSettings.verbose) {
+ printf ("Skipping spot located at %d x %d: opacity=%.3f\n", entry.sourcePos.x, entry.sourcePos.y, entry.opacity);
+ }
+ continue;
+#endif
+ }
+
+ // skipping entries where the source circle isn't completely inside the image bounds
+ if (src_XMin < 0 || src_XMax >= img->width || src_YMin < 0 || src_YMax >= img->height) {
+#ifndef NDEBUG
+ if (options.rtSettings.verbose) {
+ printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity);
+ printf ("%d < 0 || %d >= %d || %d < 0 || %d >= %d\n",
+ src_XMin, src_XMax, img->width, src_YMin, src_YMax, img->height);
+ }
+#endif
+ continue;
+ }
+
+ // skipping entries where the dest circle is completely outside the image bounds
+ if (dst_XMin >= img->width || dst_XMax <= 0 || dst_YMin >= img->height || dst_YMax <= 0) {
+#ifndef NDEBUG
+ if (options.rtSettings.verbose) {
+ printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity);
+ printf ("%d >= %d || %d <= 0 || %d >= %d || %d <= 0\n",
+ dst_XMin, img->width, dst_XMax, dst_YMin, img->height, dst_YMax);
+ }
+#endif
+ continue;
+ }
+
+ // ----------------- Core function -----------------
+
+#if 0
+ int scaledPPX = pp.x / pp.skip;
+ int scaledPPY = pp.y / pp.skip;
+ int scaledPPW = pp.w / pp.skip + (pp.w % pp.skip > 0);
+ int scaledPPH = pp.h / pp.skip + (pp.h % pp.skip > 0);
+
+ int sizeX = dst_XMax - dst_XMin + 1;
+ int sizeY = dst_YMax - dst_YMin + 1;
+
+ Imagefloat matrix (sizeX, sizeY);
+ Imagefloat solution (sizeX, sizeY);
+
+ // allocate the mask and draw it
+ mask.setSize (sizeX, sizeY);
+ {
+ Cairo::RefPtr cr = Cairo::Context::create (mask.getSurface());
+
+ // clear the bitmap
+ cr->set_source_rgba (0., 0., 0., 0.);
+ cr->rectangle (0., 0., sizeX, sizeY);
+ cr->set_line_width (0.);
+ cr->fill();
+
+ // draw the mask
+ cr->set_antialias (Cairo::ANTIALIAS_GRAY);
+ cr->set_line_width (featherRadius);
+ double gradientCenterX = double (sizeX) / 2.;
+ double gradientCenterY = double (sizeY) / 2.;
+ {
+ Cairo::RefPtr radialGradient = Cairo::RadialGradient::create (
+ gradientCenterX, gradientCenterY, radius,
+ gradientCenterX, gradientCenterY, featherRadius
+ );
+ radialGradient->add_color_stop_rgb (0., 0., 0., 1.);
+ radialGradient->add_color_stop_rgb (1., 0., 0., 0.);
+ cr->set_source_rgba (0., 0., 0., 1.);
+ cr->mask (radialGradient);
+ cr->rectangle (0., 0., sizeX, sizeY);
+ cr->fill();
+ }
+ }
+
+ // copy the src part to a temporary buffer to avoid possible self modified source
+ Imagefloat *srcBuff = img->copySubRegion (srcX, srcY, sizeX, sizeY);
+
+
+ // subtract pattern to image and store the result as a double in matrix
+ for (int i = 0, i2 = dst_YMin; i2 < sizeY - 1; ++i, ++i2) {
+ for (int j = 0, j2 = dst_XMin; i2 < sizeX - 1; ++j, ++j2) {
+ matrix.r (i, j) = img->r (i2, j2) - srcBuff->r (i, j);
+ matrix.g (i, j) = img->g (i2, j2) - srcBuff->g (i, j);
+ matrix.b (i, j) = img->b (i2, j2) - srcBuff->b (i, j);
+ }
+ }
+
+
+ // FIXME: is a faster implementation needed?
+#define EPSILON 0.001
+#define MAX_ITER 500
+
+ // repeat until convergence or max iterations
+ for (int n = 0; n < MAX_ITER; ++n) {
+
+ printf ("<<< n=#%d\n", n);
+ // ----------------------------------------------------------------
+
+ /* Perform one iteration of the Laplace solver for matrix. Store the
+ * result in solution and get the square of the cumulative error
+ * of the solution.
+ */
+ int i, j;
+ double tmp, diff;
+ double sqr_err_r = 0.0;
+ double sqr_err_g = 0.0;
+ double sqr_err_b = 0.0;
+ const double w = 1.80 * 0.25; /* Over-relaxation = 1.8 */
+
+ // we use a red/black checker model of the discretization grid
+
+ // do reds
+ for (i = 0; i < matrix.getHeight(); ++i) {
+ for (j = i % 2; j < matrix.getWidth(); j += 2) {
+ printf ("/%d,%d", j, i);
+
+ if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) {
+ // do nothing at the boundary or outside mask
+ solution.r (i, j) = matrix.r (i, j);
+ solution.g (i, j) = matrix.g (i, j);
+ solution.b (i, j) = matrix.b (i, j);
+ } else {
+ // Use Gauss Siedel to get the correction factor then over-relax it
+ tmp = solution.r (i, j);
+ solution.r (i, j) = (matrix.r (i, j) + w *
+ (
+ matrix.r (i, j - 1) + // west
+ matrix.r (i, j + 1) + // east
+ matrix.r (i - 1, j) + // north
+ matrix.r (i + 1, j) - 4.0 * matrix.r (i, j) // south
+ )
+ );
+
+ diff = solution.r (i, j) - tmp;
+ sqr_err_r += diff * diff;
+
+
+ tmp = solution.g (i, j);
+ solution.g (i, j) = (matrix.g (i, j) + w *
+ (
+ matrix.g (i, j - 1) + // west
+ matrix.g (i, j + 1) + // east
+ matrix.g (i - 1, j) + // north
+ matrix.g (i + 1, j) - 4.0 * matrix.g (i, j) // south
+ )
+ );
+
+ diff = solution.g (i, j) - tmp;
+ sqr_err_g += diff * diff;
+
+
+
+ tmp = solution.b (i, j);
+ solution.b (i, j) = (matrix.b (i, j) + w *
+ (
+ matrix.b (i, j - 1) + // west
+ matrix.b (i, j + 1) + // east
+ matrix.b (i - 1, j) + // north
+ matrix.b (i + 1, j) - 4.0 * matrix.b (i, j) // south
+ )
+ );
+
+ diff = solution.b (i, j) - tmp;
+ sqr_err_b += diff * diff;
+
+ }
+ }
+ }
+
+
+ /* Do blacks
+ *
+ * As we've done the reds earlier, we can use them right now to
+ * accelerate the convergence. So we have "solution" in the solver
+ * instead of "matrix" above
+ */
+ for (i = 0; i < matrix.getHeight(); i++) {
+ for (j = (i % 2) ? 0 : 1; j < matrix.getWidth(); j += 2) {
+ printf (":%d,%d", j, i);
+
+ if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) {
+ // do nothing at the boundary or outside mask
+ solution.r (i, j) = matrix.r (i, j);
+ solution.g (i, j) = matrix.g (i, j);
+ solution.b (i, j) = matrix.b (i, j);
+ } else {
+ // Use Gauss Siedel to get the correction factor then over-relax it
+ tmp = solution.r (i, j);
+ solution.r (i, j) = (matrix.r (i, j) + w *
+ (
+ matrix.r (i, j - 1) + // west
+ matrix.r (i, j + 1) + // east
+ matrix.r (i - 1, j) + // north
+ matrix.r (i + 1, j) - 4.0 * matrix.r (i, j) // south
+ )
+ );
+
+ diff = solution.r (i, j) - tmp;
+ sqr_err_r += diff * diff;
+
+
+
+ tmp = solution.g (i, j);
+ solution.g (i, j) = (matrix.g (i, j) + w *
+ (
+ matrix.g (i, j - 1) + // west
+ matrix.g (i, j + 1) + // east
+ matrix.g (i - 1, j) + // north
+ matrix.g (i + 1, j) - 4.0 * matrix.g (i, j) // south
+ )
+ );
+
+ diff = solution.g (i, j) - tmp;
+ sqr_err_g += diff * diff;
+
+
+
+ tmp = solution.b (i, j);
+ solution.b (i, j) = (matrix.b (i, j) + w *
+ (
+ matrix.b (i, j - 1) + // west
+ matrix.b (i, j + 1) + // east
+ matrix.b (i - 1, j) + // north
+ matrix.b (i + 1, j) - 4.0 * matrix.b (i, j) // south
+ )
+ );
+
+ diff = solution.b (i, j) - tmp;
+ sqr_err_b += diff * diff;
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------
+
+ // copy solution to matrix
+ solution.copyData (&matrix);
+
+ if (sqr_err_r < EPSILON && sqr_err_g < EPSILON && sqr_err_b < EPSILON) {
+ break;
+ }
+
+ printf ("\n>>> n=#%d\n", n);
+ }
+
+ printf ("\n");
+#endif
+
+ // add solution to original image and store in tempPR
+ for (int i = 0, i2 = dst_YMin; i2 < dst_YMax - 1; ++i, ++i2) {
+ if (i2 < 0 || i2 >= img->height) {
+ continue;
+ }
+ for (int j = 0, j2 = dst_XMin; j2 < dst_XMax - 1; ++j, ++j2) {
+ if (j2 < 0 || j2 >= img->width) {
+ continue;
+ }
+ //float c2 = float (mask (i, j)) / 255.f;
+ //float c1 = 1.f - c2;
+ //resultPR->r(i,j) = (unsigned char) CLAMP0255 ( ROUND( double(first->r(i,j)) + double(secondPR->r(i,j)) ) );
+
+
+ img->r (i2, j2) = 65535.0f; //img->r(i2,j2)*c1 + srcBuff->r(i,j)*c2;
+ img->g (i2, j2) = 0.0f; //img->g(i2,j2)*c1 + srcBuff->g(i,j)*c2;
+ img->b (i2, j2) = 0.0f; //img->b(i2,j2)*c1 + srcBuff->b(i,j)*c2;
+ /*
+ img->r(i2,j2) = img->r(i2,j2)*c1 + (solution.r(i,j) + srcBuff->r(i,j))*c2;
+ img->g(i2,j2) = img->g(i2,j2)*c1 + (solution.g(i,j) + srcBuff->g(i,j))*c2;
+ img->b(i2,j2) = img->b(i2,j2)*c1 + (solution.b(i,j) + srcBuff->b(i,j))*c2;
+ */
+ }
+ }
+
+ }
+}
+
+}
+
diff --git a/rtengine/stdimagesource.cc b/rtengine/stdimagesource.cc
index aabb35a30..8c85c287f 100644
--- a/rtengine/stdimagesource.cc
+++ b/rtengine/stdimagesource.cc
@@ -213,7 +213,7 @@ int StdImageSource::load (Glib::ustring fname, bool batch)
return 0;
}
-void StdImageSource::getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw)
+void StdImageSource::getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps & pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw)
{
// the code will use OpenMP as of now.
diff --git a/rtengine/stdimagesource.h b/rtengine/stdimagesource.h
index 6017fae74..51bbfab1d 100644
--- a/rtengine/stdimagesource.h
+++ b/rtengine/stdimagesource.h
@@ -45,7 +45,7 @@ public:
~StdImageSource ();
int load (Glib::ustring fname, bool batch = false);
- void getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw);
+ void getImage (ColorTemp ctemp, int tran, Imagefloat* image, PreviewProps & pp, ToneCurveParams hrp, ColorManagementParams cmp, RAWParams raw);
ColorTemp getWB ()
{
return wb;
diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt
index 1adc6ddd4..b3094091d 100644
--- a/rtgui/CMakeLists.txt
+++ b/rtgui/CMakeLists.txt
@@ -6,7 +6,7 @@ set (BASESOURCEFILES
cachemanager.cc cacheimagedata.cc shcselector.cc perspective.cc thresholdselector.cc thresholdadjuster.cc
clipboard.cc thumbimageupdater.cc bqentryupdater.cc lensgeom.cc coloredbar.cc edit.cc coordinateadjuster.cc
coarsepanel.cc cacorrection.cc chmixer.cc blackwhite.cc
- resize.cc icmpanel.cc crop.cc shadowshighlights.cc
+ resize.cc icmpanel.cc crop.cc shadowshighlights.cc spot.cc
impulsedenoise.cc dirpyrdenoise.cc epd.cc
exifpanel.cc toolpanel.cc lensprofile.cc
sharpening.cc vibrance.cc rgbcurves.cc colortoning.cc
diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc
index 1ae650a5f..0af4e9fdb 100644
--- a/rtgui/paramsedited.cc
+++ b/rtgui/paramsedited.cc
@@ -342,6 +342,10 @@ void ParamsEdited::set (bool v)
resize.width = v;
resize.height = v;
resize.enabled = v;
+
+ spot.enabled = v;
+ spot.entries = v;
+
icm.input = v;
icm.toneCurve = v;
icm.applyLookTable = v;
@@ -835,6 +839,8 @@ void ParamsEdited::initFrom (const std::vector
resize.width = resize.width && p.resize.width == other.resize.width;
resize.height = resize.height && p.resize.height == other.resize.height;
resize.enabled = resize.enabled && p.resize.enabled == other.resize.enabled;
+ spot.enabled = spot.enabled && p.spot.enabled == other.spot.enabled;
+ spot.entries = spot.entries && p.spot.entries == other.spot.entries;
icm.input = icm.input && p.icm.input == other.icm.input;
icm.toneCurve = icm.toneCurve && p.icm.toneCurve == other.icm.toneCurve;
icm.applyLookTable = icm.applyLookTable && p.icm.applyLookTable == other.icm.applyLookTable;
@@ -2164,6 +2170,14 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten
toEdit.resize.enabled = mods.resize.enabled;
}
+ if (spot.enabled) {
+ toEdit.spot.enabled = mods.spot.enabled;
+ }
+
+ if (spot.entries) {
+ toEdit.spot.entries = mods.spot.entries;
+ }
+
if (icm.input) {
toEdit.icm.input = mods.icm.input;
}
diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h
index 8543fbfa8..cf3beb380 100644
--- a/rtgui/paramsedited.h
+++ b/rtgui/paramsedited.h
@@ -529,6 +529,13 @@ public:
bool enabled;
};
+class SpotParamsEdited
+{
+public:
+ bool enabled;
+ bool entries;
+};
+
class ColorManagementParamsEdited
{
@@ -773,6 +780,7 @@ public:
ChannelMixerParamsEdited chmixer;
BlackWhiteParamsEdited blackwhite;
ResizeParamsEdited resize;
+ SpotParamsEdited spot;
ColorManagementParamsEdited icm;
RAWParamsEdited raw;
DirPyrEqualizerParamsEdited dirpyrequalizer;
diff --git a/rtgui/spot.cc b/rtgui/spot.cc
new file mode 100644
index 000000000..0d1b1d596
--- /dev/null
+++ b/rtgui/spot.cc
@@ -0,0 +1,690 @@
+/*
+ * This file is part of RawTherapee.
+ */
+#include "spot.h"
+#include "rtimage.h"
+#include
+#include "../rtengine/rt_math.h"
+#include "guiutils.h"
+
+using namespace rtengine;
+using namespace rtengine::procparams;
+
+#define STATIC_VISIBLE_OBJ_NBR 6
+#define STATIC_MO_OBJ_NBR 6
+
+Spot::Spot() : FoldableToolPanel (this, "spot", M ("TP_SPOT_LABEL"), true, true), EditSubscriber (ET_OBJECTS), lastObject (-1), activeSpot (-1),
+ sourceIcon ("spot-normal.png", "spot-active.png", "spot-active.png", "spot-prelight.png", "", Geometry::DP_CENTERCENTER), editedCheckBox(NULL)
+{
+ countLabel = Gtk::manage (new Gtk::Label (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)));
+
+ edit = Gtk::manage (new Gtk::ToggleButton());
+ edit->add (*Gtk::manage (new RTImage ("editmodehand.png")));
+ editConn = edit->signal_toggled().connect ( sigc::mem_fun (*this, &Spot::editToggled) );
+
+ reset = Gtk::manage (new Gtk::Button ());
+ reset->add (*Gtk::manage (new RTImage ("gtk-undo-ltr-small.png", "gtk-undo-rtl-small.png")));
+ reset->set_relief (Gtk::RELIEF_NONE);
+ reset->set_border_width (0);
+ reset->signal_clicked().connect ( sigc::mem_fun (*this, &Spot::resetPressed) );
+
+ labelBox = Gtk::manage (new Gtk::HBox());
+ labelBox->set_spacing (2);
+ labelBox->pack_start (*countLabel, false, false, 0);
+ labelBox->pack_end (*edit, false, false, 0);
+ labelBox->pack_end (*reset, false, false, 0);
+ pack_start (*labelBox);
+
+ sourceIcon.datum = Geometry::IMAGE;
+ sourceIcon.setActive (false);
+ sourceIcon.state = Geometry::ACTIVE;
+ sourceCircle.datum = Geometry::IMAGE;
+ sourceCircle.setActive (false);
+ sourceCircle.radiusInImageSpace = true;
+ sourceMODisc.datum = Geometry::IMAGE;
+ sourceMODisc.setActive (false);
+ sourceMODisc.radiusInImageSpace = true;
+ sourceMODisc.filled = true;
+ sourceMODisc.innerLineWidth = 0.;
+ targetCircle.datum = Geometry::IMAGE;
+ targetCircle.setActive (false);
+ targetCircle.radiusInImageSpace = true;
+ targetMODisc.datum = Geometry::IMAGE;
+ targetMODisc.setActive (false);
+ targetMODisc.radiusInImageSpace = true;
+ targetMODisc.filled = true;
+ targetMODisc.innerLineWidth = 0.;
+ sourceFeatherCircle.datum = Geometry::IMAGE;
+ sourceFeatherCircle.setActive (false);
+ sourceFeatherCircle.radiusInImageSpace = true;
+ targetFeatherCircle.datum = Geometry::IMAGE;
+ targetFeatherCircle.setActive (false);
+ targetFeatherCircle.radiusInImageSpace = true;
+ link.datum = Geometry::IMAGE;
+ link.setActive (false);
+
+ show_all();
+}
+
+Spot::~Spot()
+{
+ // delete all dynamically allocated geometry
+ if (EditSubscriber::visibleGeometry.size()) {
+ for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
+ delete EditSubscriber::visibleGeometry.at (i);
+ }
+ }
+ // We do not delete the mouseOverGeometry, because the referenced objects are either
+ // shared with visibleGeometry or instantiated by the class's ctor
+}
+
+void Spot::read (const ProcParams* pp, const ParamsEdited* pedited)
+{
+ disableListener ();
+
+ size_t oldSize = spots.size();
+ spots = pp->spot.entries;
+
+ if (pedited) {
+ set_inconsistent (multiImage && !pedited->spot.enabled);
+ }
+
+ setEnabled (pp->spot.enabled);
+ lastEnabled = pp->spot.enabled;
+
+ if (spots.size() != oldSize) {
+ createGeometry();
+ }
+
+ updateGeometry();
+
+ enableListener ();
+}
+
+void Spot::write (ProcParams* pp, ParamsEdited* pedited)
+{
+ pp->spot.enabled = getEnabled();
+ pp->spot.entries = spots;
+
+ if (pedited) {
+ pedited->spot.enabled = !get_inconsistent();
+ pedited->spot.entries = !editedCheckBox->get_active();
+ }
+}
+
+void Spot::resetPressed()
+{
+ if (batchMode) {
+ // no need to handle the Geometry in batch mode, since point editing is disabled
+ spots.clear();
+ editedConn.block (true);
+ editedCheckBox->set_active (true);
+ editedConn.block (false);
+
+ if (listener) {
+ listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size()));
+ }
+ } else {
+ if (!spots.empty()) {
+ spots.clear();
+ activeSpot = -1;
+ lastObject = -1;
+ createGeometry();
+ updateGeometry();
+
+ if (listener) {
+ listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0));
+ }
+ }
+ }
+}
+
+void Spot::setBatchMode (bool batchMode)
+{
+ ToolPanel::setBatchMode (batchMode);
+
+ if (batchMode) {
+ removeIfThere (labelBox, edit, false);
+
+ if (!editedCheckBox) {
+ removeIfThere (labelBox, countLabel, false);
+ countLabel = NULL;
+ editedCheckBox = Gtk::manage (new Gtk::CheckButton (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)));
+ labelBox->pack_start (*editedCheckBox, Gtk::PACK_SHRINK, 2);
+ labelBox->reorder_child (*editedCheckBox, 0);
+ editedConn = editedCheckBox->signal_toggled().connect ( sigc::mem_fun (*this, &Spot::editedToggled) );
+ editedCheckBox->show();
+ }
+ }
+}
+
+void Spot::editedToggled ()
+{
+ if (listener) {
+ listener->panelChanged (EvSpotEntry, !editedCheckBox->get_active() ? M ("GENERAL_UNCHANGED") : Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size()));
+ }
+}
+
+
+void Spot::enabledChanged ()
+{
+ if (listener) {
+ if (get_inconsistent()) {
+ listener->panelChanged (EvSpotEnabled, M("GENERAL_UNCHANGED"));
+ } else if (getEnabled()) {
+ listener->panelChanged (EvSpotEnabled, M("GENERAL_ENABLED"));
+ } else {
+ listener->panelChanged (EvSpotEnabled, M("GENERAL_DISABLED"));
+ }
+ }
+}
+
+void Spot::setEditProvider (EditDataProvider* provider)
+{
+ EditSubscriber::setEditProvider (provider);
+}
+
+void Spot::editToggled ()
+{
+ if (edit->get_active()) {
+ subscribe();
+ } else {
+ unsubscribe();
+ }
+}
+
+Geometry* Spot::getVisibleGeometryFromMO (int MOID)
+{
+ if (MOID == -1) {
+ return NULL;
+ }
+
+ if (MOID == 0) {
+ return getActiveSpotIcon();
+ }
+
+ if (MOID == 1) { // sourceMODisc
+ return &sourceIcon;
+ }
+
+ return EditSubscriber::mouseOverGeometry.at (MOID);
+}
+
+void Spot::createGeometry ()
+{
+ int nbrEntry = spots.size();
+
+ if (!batchMode) {
+ countLabel->set_text(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), nbrEntry));
+ }
+
+ //printf("CreateGeometry(%d)\n", nbrEntry);
+ // delete all dynamically allocated geometry
+ if (EditSubscriber::visibleGeometry.size() > STATIC_VISIBLE_OBJ_NBR)
+ for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
+ delete EditSubscriber::visibleGeometry.at (i);
+ }
+
+ size_t i = 0, j = 0;
+ EditSubscriber::mouseOverGeometry.resize (STATIC_MO_OBJ_NBR + nbrEntry);
+ EditSubscriber::visibleGeometry.resize (nbrEntry + STATIC_VISIBLE_OBJ_NBR);
+
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetMODisc; // STATIC_MO_OBJ_NBR + 0
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceMODisc; // STATIC_MO_OBJ_NBR + 1
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetCircle; // STATIC_MO_OBJ_NBR + 2
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceCircle; // STATIC_MO_OBJ_NBR + 3
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetFeatherCircle; // STATIC_MO_OBJ_NBR + 4
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceFeatherCircle; // STATIC_MO_OBJ_NBR + 5
+
+ // recreate all spots geometry
+ Cairo::RefPtr normalImg = sourceIcon.getNormalImg();
+ Cairo::RefPtr prelightImg = sourceIcon.getPrelightImg();
+ Cairo::RefPtr activeImg = sourceIcon.getActiveImg();
+
+ for (; j < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i, ++j) {
+ EditSubscriber::mouseOverGeometry.at (i) = EditSubscriber::visibleGeometry.at (j) = new OPIcon (normalImg, activeImg, prelightImg, Cairo::RefPtr(NULL), Cairo::RefPtr(NULL), Geometry::DP_CENTERCENTER);
+ EditSubscriber::visibleGeometry.at (j)->setActive (true);
+ EditSubscriber::visibleGeometry.at (j)->datum = Geometry::IMAGE;
+ EditSubscriber::visibleGeometry.at (j)->state = Geometry::NORMAL;
+ //printf("mouseOverGeometry.at(%d) = %p\n", (unsigned int)i, (void*)EditSubscriber::mouseOverGeometry.at(i));
+ }
+
+ EditSubscriber::visibleGeometry.at (j++) = &sourceIcon; // STATIC_VISIBLE_OBJ_NBR + 0
+ EditSubscriber::visibleGeometry.at (j++) = &sourceFeatherCircle; // STATIC_VISIBLE_OBJ_NBR + 1
+ EditSubscriber::visibleGeometry.at (j++) = &link; // STATIC_VISIBLE_OBJ_NBR + 2
+ EditSubscriber::visibleGeometry.at (j++) = &sourceCircle; // STATIC_VISIBLE_OBJ_NBR + 3
+ EditSubscriber::visibleGeometry.at (j++) = &targetFeatherCircle; // STATIC_VISIBLE_OBJ_NBR + 4
+ EditSubscriber::visibleGeometry.at (j++) = &targetCircle; // STATIC_VISIBLE_OBJ_NBR + 5
+}
+
+void Spot::updateGeometry()
+{
+ EditDataProvider* dataProvider = getEditProvider();
+
+ if (dataProvider) {
+ int imW, imH;
+ dataProvider->getImageSize (imW, imH);
+
+ if (activeSpot > -1) {
+ // Target point circle
+ targetCircle.center = spots.at (activeSpot).targetPos;
+ targetCircle.radius = spots.at (activeSpot).radius;
+ targetCircle.setActive (true);
+
+ // Target point Mouse Over disc
+ targetMODisc.center = targetCircle.center;
+ targetMODisc.radius = targetCircle.radius;
+ targetMODisc.setActive (true);
+
+ // Source point Icon
+ sourceIcon.position = spots.at (activeSpot).sourcePos;
+ sourceIcon.setActive (true);
+
+ // Source point circle
+ sourceCircle.center = spots.at (activeSpot).sourcePos;
+ sourceCircle.radius = spots.at (activeSpot).radius;
+ sourceCircle.setActive (true);
+
+ // Source point Mouse Over disc
+ sourceMODisc.center = sourceCircle.center;
+ sourceMODisc.radius = sourceCircle.radius;
+ sourceMODisc.setActive (true);
+
+ // Target point feather circle
+ targetFeatherCircle.center = spots.at (activeSpot).targetPos;
+ targetFeatherCircle.radius = float (spots.at (activeSpot).radius) * (1.f + spots.at (activeSpot).feather);
+ targetFeatherCircle.radiusInImageSpace = true;
+ targetFeatherCircle.setActive (true);
+
+ // Source point feather circle
+ sourceFeatherCircle.center = spots.at (activeSpot).sourcePos;
+ sourceFeatherCircle.radius = targetFeatherCircle.radius;
+ sourceFeatherCircle.setActive (true);
+
+ // Link line
+ PolarCoord p;
+ p = targetCircle.center - sourceCircle.center;
+
+ if (p.radius > sourceCircle.radius + targetCircle.radius) {
+ PolarCoord p2 (sourceCircle.radius, p.angle);
+ Coord p3;
+ p3 = p2;
+ link.begin = sourceCircle.center + p3;
+ p2.set (targetCircle.radius, p.angle + 180);
+ p3 = p2;
+ link.end = targetCircle.center + p3;
+ link.setActive (true);
+ } else {
+ link.setActive (false);
+ }
+ } else {
+ targetCircle.setActive (false);
+ targetMODisc.setActive (false);
+ sourceIcon.setActive (false);
+ sourceCircle.setActive (false);
+ sourceMODisc.setActive (false);
+ targetFeatherCircle.setActive (false);
+ sourceFeatherCircle.setActive (false);
+ link.setActive (false);
+ }
+
+ for (size_t i = 0; i < spots.size(); ++i) {
+ // Target point icon
+ OPIcon* geom = static_cast (EditSubscriber::visibleGeometry.at (i));
+ geom->position = spots.at (i).targetPos;
+ geom->setActive (true);
+
+ if (int (i) == activeSpot) {
+ geom->setHoverable (false);
+ }
+ }
+ }
+}
+
+OPIcon *Spot::getActiveSpotIcon()
+{
+ if (activeSpot > -1) {
+ return static_cast (EditSubscriber::visibleGeometry.at (activeSpot));
+ }
+
+ return NULL;
+}
+
+void Spot::addNewEntry()
+{
+ EditDataProvider* editProvider = getEditProvider();
+ // we create a new entry
+ SpotEntry se;
+ se.targetPos = editProvider->posImage;
+ se.sourcePos = se.targetPos;
+ spots.push_back (se); // this make a copy of se ...
+ activeSpot = spots.size() - 1;
+ lastObject = 1;
+
+ //printf("ActiveSpot = %d\n", activeSpot);
+
+ createGeometry();
+ updateGeometry();
+ EditSubscriber::visibleGeometry.at (activeSpot)->state = Geometry::ACTIVE;
+ sourceIcon.state = Geometry::DRAGGED;
+ // TODO: find a way to disable the active spot's Mouse Over geometry but still displaying its location...
+
+ if (listener) {
+ listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
+ }
+}
+
+void Spot::deleteSelectedEntry()
+{
+ // delete the activeSpot
+ if (activeSpot > -1) {
+ std::vector::iterator i = spots.begin();
+ for (int j = 0; j < activeSpot; ++j) {
+ ++i;
+ }
+ spots.erase (i);
+ }
+
+ lastObject = -1;
+ activeSpot = -1;
+
+ createGeometry();
+ updateGeometry();
+
+ if (listener) {
+ listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
+ }
+}
+
+// TODO
+CursorShape Spot::getCursor (const int objectID)
+{
+ return CSOpenHand;
+}
+
+bool Spot::mouseOver (const int modifierKey)
+{
+ EditDataProvider* editProvider = getEditProvider();
+
+ if (editProvider && editProvider->object != lastObject) {
+ if (lastObject > -1) {
+ if (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc) {
+ getVisibleGeometryFromMO (lastObject)->state = Geometry::ACTIVE;
+ } else {
+ getVisibleGeometryFromMO (lastObject)->state = Geometry::NORMAL;
+ }
+
+ sourceIcon.state = Geometry::ACTIVE;
+ }
+
+ if (editProvider->object > -1) {
+ getVisibleGeometryFromMO (editProvider->object)->state = Geometry::PRELIGHT;
+
+ if (editProvider->object >= STATIC_MO_OBJ_NBR) {
+ // a Spot is being edited
+ int oldActiveSpot = activeSpot;
+ activeSpot = editProvider->object - STATIC_MO_OBJ_NBR;
+
+ if (activeSpot != oldActiveSpot) {
+ if (oldActiveSpot > -1) {
+ EditSubscriber::visibleGeometry.at (oldActiveSpot)->state = Geometry::NORMAL;
+ EditSubscriber::mouseOverGeometry.at (oldActiveSpot + STATIC_MO_OBJ_NBR)->state = Geometry::NORMAL;
+ }
+
+ EditSubscriber::visibleGeometry.at (activeSpot)->state = Geometry::PRELIGHT;
+ EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::PRELIGHT;
+ //printf("ActiveSpot = %d (was %d before)\n", activeSpot, oldActiveSpot);
+ }
+ }
+ }
+
+ lastObject = editProvider->object;
+
+ if (lastObject > -1 && EditSubscriber::mouseOverGeometry.at (lastObject) == getActiveSpotIcon()) {
+ lastObject = 0; // targetMODisc
+ }
+
+ updateGeometry();
+ return true;
+ }
+
+ return false;
+}
+
+// Create a new Target and Source point or start the drag of a Target point under the cursor
+bool Spot::button1Pressed (const int modifierKey)
+{
+ EditDataProvider* editProvider = getEditProvider();
+
+ if (editProvider) {
+ if (lastObject == -1 && (modifierKey & GDK_CONTROL_MASK)) {
+ addNewEntry();
+ EditSubscriber::action = ES_ACTION_DRAGGING;
+ return true;
+ } else if (lastObject > -1) {
+ getVisibleGeometryFromMO (lastObject)->state = Geometry::DRAGGED;
+ EditSubscriber::action = ES_ACTION_DRAGGING;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// End the drag of a Target point
+bool Spot::button1Released()
+{
+ Geometry *loGeom = getVisibleGeometryFromMO (lastObject);
+
+ if (!loGeom) {
+ EditSubscriber::action = ES_ACTION_NONE;
+ return false;
+ }
+
+ loGeom->state = Geometry::PRELIGHT;
+ EditSubscriber::action = ES_ACTION_NONE;
+ updateGeometry();
+ return true;
+}
+
+// Delete a point
+bool Spot::button2Pressed (const int modifierKey)
+{
+ EditDataProvider* editProvider = getEditProvider();
+
+ if (!editProvider || lastObject == -1 || activeSpot==-1) {
+ return false;
+ }
+
+ if (!(modifierKey & (GDK_SHIFT_MASK|GDK_SHIFT_MASK))) {
+ EditSubscriber::action = ES_ACTION_PICKING;
+ }
+
+ return false;
+}
+
+// Create a new Target and Source point or start the drag of a Target point under the cursor
+bool Spot::button3Pressed (const int modifierKey)
+{
+ EditDataProvider* editProvider = getEditProvider();
+
+ if (!editProvider || lastObject == -1 || activeSpot==-1) {
+ return false;
+ }
+
+ if ((modifierKey & GDK_CONTROL_MASK) && (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR)) {
+ lastObject = 1; // sourceMODisc
+ sourceIcon.state = Geometry::DRAGGED;
+ EditSubscriber::action = ES_ACTION_DRAGGING;
+ return true;
+ } else if (!(modifierKey & (GDK_SHIFT_MASK|GDK_SHIFT_MASK))) {
+ EditSubscriber::action = ES_ACTION_PICKING;
+ }
+
+ return false;
+}
+
+bool Spot::button3Released()
+{
+ Geometry *loGeom = getVisibleGeometryFromMO (lastObject);
+
+ if (!loGeom) {
+ EditSubscriber::action = ES_ACTION_NONE;
+ return false;
+ }
+
+ lastObject = -1;
+ sourceIcon.state = Geometry::ACTIVE;
+ updateGeometry();
+ EditSubscriber::action = ES_ACTION_NONE;
+ return true;
+
+ return false;
+}
+
+bool Spot::drag1 (const int modifierKey)
+{
+ EditDataProvider *editProvider = getEditProvider();
+ int imW, imH;
+ editProvider->getImageSize (imW, imH);
+ bool modified = false;
+
+ //printf("Drag1 / LastObject=%d\n", lastObject);
+
+ Geometry *loGeom = EditSubscriber::mouseOverGeometry.at (lastObject);
+
+ if (loGeom == &sourceMODisc) {
+ //printf("sourceMODisc / deltaPrevImage = %d / %d\n", editProvider->deltaPrevImage.x, editProvider->deltaPrevImage.y);
+ rtengine::Coord currPos = spots.at (activeSpot).sourcePos;
+ spots.at (activeSpot).sourcePos += editProvider->deltaPrevImage;
+ spots.at (activeSpot).sourcePos.clip (imW, imH);
+
+ if (spots.at (activeSpot).sourcePos != currPos) {
+ modified = true;
+ }
+
+ EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::DRAGGED;
+ } else if (loGeom == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR) {
+ //printf("targetMODisc / deltaPrevImage = %d / %d\n", editProvider->deltaPrevImage.x, editProvider->deltaPrevImage.y);
+ rtengine::Coord currPos = spots.at (activeSpot).targetPos;
+ spots.at (activeSpot).targetPos += editProvider->deltaPrevImage;
+ spots.at (activeSpot).targetPos.clip (imW, imH);
+
+ if (spots.at (activeSpot).targetPos != currPos) {
+ modified = true;
+ }
+ } else if (loGeom == &sourceCircle) {
+ //printf("sourceCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
+ int lastRadius = spots.at (activeSpot).radius;
+ rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
+ rtengine::PolarCoord currPolar = currPos - spots.at (activeSpot).sourcePos;
+ spots.at (activeSpot).radius = LIM (int (currPolar.radius), SpotParams::minRadius, SpotParams::maxRadius);
+
+ if (spots.at (activeSpot).radius != lastRadius) {
+ modified = true;
+ }
+ } else if (loGeom == &targetCircle) {
+ //printf("targetCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
+ int lastRadius = spots.at (activeSpot).radius;
+ rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
+ rtengine::PolarCoord currPolar = currPos - spots.at (activeSpot).targetPos;
+ spots.at (activeSpot).radius = LIM (int (currPolar.radius), SpotParams::minRadius, SpotParams::maxRadius);
+
+ if (spots.at (activeSpot).radius != lastRadius) {
+ modified = true;
+ }
+ } else if (loGeom == &sourceFeatherCircle) {
+ //printf("sourceFeatherCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
+ float currFeather = spots.at (activeSpot).feather;
+ rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
+ rtengine::PolarCoord currPolar = currPos -spots.at (activeSpot).sourcePos;
+ spots.at (activeSpot).feather = LIM01 ((currPolar.radius - double (spots.at (activeSpot).radius)) / double (spots.at (activeSpot).radius));
+
+ if (spots.at (activeSpot).feather != currFeather) {
+ modified = true;
+ }
+ } else if (loGeom == &targetFeatherCircle) {
+ //printf("targetFeatherCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
+ float currFeather = spots.at (activeSpot).feather;
+ rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
+ rtengine::PolarCoord currPolar = currPos - spots.at (activeSpot).targetPos;
+ spots.at (activeSpot).feather = LIM01 ((currPolar.radius - double (spots.at (activeSpot).radius)) / double (spots.at (activeSpot).radius));
+
+ if (spots.at (activeSpot).feather != currFeather) {
+ modified = true;
+ }
+ }
+
+ if (listener && modified) {
+ updateGeometry();
+ listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
+ }
+
+ return modified;
+}
+
+bool Spot::drag3 (const int modifierKey)
+{
+ EditDataProvider *editProvider = getEditProvider();
+ int imW, imH;
+ editProvider->getImageSize (imW, imH);
+ bool modified = false;
+
+ Geometry *loGeom = EditSubscriber::mouseOverGeometry.at (lastObject);
+
+ if (loGeom == &sourceMODisc) {
+ rtengine::Coord currPos = spots.at (activeSpot).sourcePos;
+ spots.at (activeSpot).sourcePos += editProvider->deltaPrevImage;
+ spots.at (activeSpot).sourcePos.clip (imW, imH);
+
+ if (spots.at (activeSpot).sourcePos != currPos) {
+ modified = true;
+ }
+ }
+
+ if (listener) {
+ updateGeometry();
+ listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
+ }
+
+ return modified;
+}
+
+bool Spot::pick2(const bool picked)
+{
+ return pick3(picked);
+}
+
+bool Spot::pick3 (const bool picked)
+{
+ EditDataProvider* editProvider = getEditProvider();
+
+ if (!picked) {
+ if (editProvider->object != lastObject) {
+ return false;
+ }
+ }
+
+ // Object is picked, we delete it
+ deleteSelectedEntry();
+ EditSubscriber::action = ES_ACTION_NONE;
+ updateGeometry();
+ return true;
+}
+
+
+void Spot::switchOffEditMode ()
+{
+ if (edit->get_active()) {
+ // switching off the toggle button
+ bool wasBlocked = editConn.block (true);
+ edit->set_active (false);
+
+ if (!wasBlocked) {
+ editConn.block (false);
+ }
+ }
+
+ EditSubscriber::switchOffEditMode(); // disconnect
+}
+
diff --git a/rtgui/spot.h b/rtgui/spot.h
new file mode 100644
index 000000000..27b4708bc
--- /dev/null
+++ b/rtgui/spot.h
@@ -0,0 +1,101 @@
+/*
+ * This file is part of RawTherapee.
+ */
+#ifndef _SPOT_H_
+#define _SPOT_H_
+
+#include
+#include "toolpanel.h"
+#include "edit.h"
+
+/**
+ * @brief Let the user create/edit/delete points for Spot Removal tool
+ *
+ * User Interface:
+ *
+ * For the rest of this documentation, T represent a "target" point (where the image is edited) and
+ * S represent the "source" location (where the edition takes its source data).
+ *
+ * When the edit button is active, all T points are shown by a small "dot". When the user
+ * move the cursor over one of them, a circle is displayed to show the radius of the brush, as well
+ * as a second circle representing the source data (S point). The user can then use the left mouse button
+ * over the icon to drag the T point. The left mouse button can be used over the S circle or the right
+ * mouse button can be used over the T point to move the S point.
+ *
+ * Using the left mouse button over the circle of the T point will let the user adjust its radius.
+ *
+ * Using the left mouse button over the feather circle will let the user adjust its radius by setting
+ * a coefficient (0.0 = same radius than the inner circle ; 1.0 = 2 times the inner radius).
+ *
+ * To create a new point, just move over a free area, and press the left mouse button while holding
+ * the CTRL key. This will create a new S and T pair of points. The CTRL key can be released, but keep
+ * the left mouse button pressed and move away to position the S point.
+ *
+ * To delete a point, move your mouse over any of its geometry press the middle or right mouse button
+ * (the point will be deleted on button release).
+ */
+class Spot : public ToolParamBlock, public FoldableToolPanel, public EditSubscriber
+{
+
+private:
+ int lastObject; // current object that is hovered
+ int activeSpot; // currently active spot, being edited
+ std::vector spots; // list of edited spots
+ OPIcon sourceIcon; // to show the source location
+ Circle sourceCircle; // to show and change the Source radius
+ Circle sourceMODisc; // to change the Source position
+ Circle targetCircle; // to show and change the Target radius
+ Circle targetMODisc; // to change the Target position
+ Circle sourceFeatherCircle; // to show the Feather radius at the Source position
+ Circle targetFeatherCircle; // to show the Feather radius at the Target position
+ Line link; // to show the link between the Source and Target position
+
+ OPIcon *getActiveSpotIcon ();
+ void updateGeometry ();
+ void createGeometry ();
+ void addNewEntry ();
+ void deleteSelectedEntry ();
+ void resetPressed ();
+
+protected:
+ Gtk::HBox* labelBox;
+ Gtk::CheckButton* editedCheckBox;
+ Gtk::Label* countLabel;
+ Gtk::ToggleButton* edit;
+ Gtk::Button* reset;
+ sigc::connection editConn, editedConn;
+
+ void editToggled ();
+ void editedToggled ();
+ Geometry* getVisibleGeometryFromMO (int MOID);
+
+public:
+
+ Spot ();
+ ~Spot ();
+
+ void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = NULL);
+ void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = NULL);
+
+ void enabledChanged ();
+
+ void setEditProvider (EditDataProvider* provider);
+
+ void setBatchMode (bool batchMode);
+
+ // EditSubscriber interface
+ CursorShape getCursor (const int objectID);
+ bool mouseOver (const int modifierKey);
+ bool button1Pressed (const int modifierKey);
+ bool button1Released ();
+ bool button2Pressed (const int modifierKey);
+ bool button3Pressed (const int modifierKey);
+ bool button3Released ();
+ bool drag1 (const int modifierKey);
+ bool drag3 (const int modifierKey);
+ bool pick2 (const bool picked);
+ bool pick3 (const bool picked);
+ void switchOffEditMode ();
+};
+
+#endif
diff --git a/rtgui/thumbimageupdater.cc b/rtgui/thumbimageupdater.cc
index 29160236e..5f78e817b 100644
--- a/rtgui/thumbimageupdater.cc
+++ b/rtgui/thumbimageupdater.cc
@@ -39,8 +39,8 @@ public:
Job(ThumbBrowserEntryBase* tbe, bool* priority, bool upgrade,
ThumbImageUpdateListener* listener):
tbe_(tbe),
- /*pparams_(pparams),
- height_(height), */
+ /*pparams_(pparams),
+ height_(height), */
priority_(priority),
upgrade_(upgrade),
listener_(listener)
diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc
index a9f78273e..9349fc5f6 100644
--- a/rtgui/toolpanelcoord.cc
+++ b/rtgui/toolpanelcoord.cc
@@ -43,6 +43,7 @@ ToolPanelCoordinator::ToolPanelCoordinator () : ipc(nullptr), editDataProvider(n
shadowshighlights = Gtk::manage (new ShadowsHighlights ());
impulsedenoise = Gtk::manage (new ImpulseDenoise ());
defringe = Gtk::manage (new Defringe ());
+ spot = Gtk::manage (new Spot ());
dirpyrdenoise = Gtk::manage (new DirPyrDenoise ());
epd = Gtk::manage (new EdgePreservingDecompositionUI ());
sharpening = Gtk::manage (new Sharpening ());
@@ -110,6 +111,8 @@ ToolPanelCoordinator::ToolPanelCoordinator () : ipc(nullptr), editDataProvider(n
toolPanels.push_back (blackwhite);
addPanel (exposurePanel, shadowshighlights);
toolPanels.push_back (shadowshighlights);
+ addPanel (detailsPanel, spot);
+ toolPanels.push_back (spot);
addPanel (detailsPanel, sharpening);
toolPanels.push_back (sharpening);
addPanel (detailsPanel, sharpenEdge);
diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h
index 46285ce3e..bdd8118c0 100644
--- a/rtgui/toolpanelcoord.h
+++ b/rtgui/toolpanelcoord.h
@@ -53,6 +53,7 @@
#include "vignetting.h"
#include "retinex.h"
#include "gradient.h"
+#include "spot.h"
#include "pcvignette.h"
#include "toolbar.h"
#include "lensgeom.h"
@@ -118,6 +119,7 @@ protected:
Crop* crop;
ToneCurve* toneCurve;
ShadowsHighlights* shadowshighlights;
+ Spot* spot;
Defringe* defringe;
ImpulseDenoise* impulsedenoise;
DirPyrDenoise* dirpyrdenoise;
diff --git a/tools/source_icons/scalable/spot-active.file b/tools/source_icons/scalable/spot-active.file
new file mode 100644
index 000000000..9704c0e95
--- /dev/null
+++ b/tools/source_icons/scalable/spot-active.file
@@ -0,0 +1 @@
+spot-active.png,w12,actions
diff --git a/tools/source_icons/scalable/spot-active.svg b/tools/source_icons/scalable/spot-active.svg
new file mode 100644
index 000000000..bc36a7a19
--- /dev/null
+++ b/tools/source_icons/scalable/spot-active.svg
@@ -0,0 +1,81 @@
+
+
+
+
diff --git a/tools/source_icons/scalable/spot-normal.file b/tools/source_icons/scalable/spot-normal.file
new file mode 100644
index 000000000..b521a910d
--- /dev/null
+++ b/tools/source_icons/scalable/spot-normal.file
@@ -0,0 +1 @@
+spot-normal.png,w16,actions
diff --git a/tools/source_icons/scalable/spot-normal.svg b/tools/source_icons/scalable/spot-normal.svg
new file mode 100644
index 000000000..ca9a933c2
--- /dev/null
+++ b/tools/source_icons/scalable/spot-normal.svg
@@ -0,0 +1,70 @@
+
+
+
+
diff --git a/tools/source_icons/scalable/spot-prelight.file b/tools/source_icons/scalable/spot-prelight.file
new file mode 100644
index 000000000..825705ece
--- /dev/null
+++ b/tools/source_icons/scalable/spot-prelight.file
@@ -0,0 +1 @@
+spot-prelight.png,w12,actions
diff --git a/tools/source_icons/scalable/spot-prelight.svg b/tools/source_icons/scalable/spot-prelight.svg
new file mode 100644
index 000000000..9cfc1acfa
--- /dev/null
+++ b/tools/source_icons/scalable/spot-prelight.svg
@@ -0,0 +1,74 @@
+
+
+
+