diff --git a/rtdata/images/svg/spot-normal.svg b/rtdata/images/svg/spot-normal.svg
index ca9a933c2..d0320e31f 100644
--- a/rtdata/images/svg/spot-normal.svg
+++ b/rtdata/images/svg/spot-normal.svg
@@ -9,62 +9,65 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="16px"
- height="16px"
- id="svg2985"
+ width="11"
+ height="11"
+ id="svg2"
version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="New document 2">
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="spot-normal.svg">
+ id="defs4" />
+ id="metadata7">
image/svg+xml
-
+
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1041.3622)">
+ style="fill:#000000;fill-opacity:0.66666667;stroke:none"
+ d="M 6,1043.3622 5.378906,1046.7411 2,1047.3622 v 1 l 3.378906,0.6211 L 6,1052.3622 H 7 L 7.621094,1048.9833 11,1048.3622 v -1 L 7.621094,1046.7411 7,1043.3622 Z"
+ id="path818"
+ inkscape:connector-curvature="0" />
+ d="m 5,1042.3622 h 1 l 0.8286408,4.5 L 6,1051.3622 H 5 l -0.8286408,-4.5 z"
+ id="rect2989"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccc" />
+
diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc
index 72f5c6d72..488a56b46 100644
--- a/rtengine/dcrop.cc
+++ b/rtengine/dcrop.cc
@@ -623,7 +623,7 @@ void Crop::update(int todo)
spotsDone = true;
PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip);
//parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw);
- parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr);
+ parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, nullptr, tr);
}
DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise;
@@ -709,7 +709,7 @@ void Crop::update(int todo)
baseCrop->copyData (spotCrop);
PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
int tr = getCoarseBitMask(params.coarse);
- parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr);
+ parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, ¶ms.icm, tr);
} else {
if (spotCrop) {
delete spotCrop;
diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc
index 2c49bdef1..22bafd844 100644
--- a/rtengine/improccoordinator.cc
+++ b/rtengine/improccoordinator.cc
@@ -540,7 +540,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange)
allocCache(spotprev);
orig_prev->copyData(spotprev);
PreviewProps pp(0, 0, fw, fh, scale);
- ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, tr);
+ ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, ¶ms->icm, tr);
} else {
if (spotprev) {
delete spotprev;
diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h
index 5700a354b..a1b79fc38 100644
--- a/rtengine/improcfun.h
+++ b/rtengine/improcfun.h
@@ -333,7 +333,7 @@ public:
float MadRgb(const float * DataList, int datalen);
// spot removal tool
- void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr);
+ void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr);
// pyramid wavelet
void dirpyr_equalizer(float ** src, float ** dst, int srcwidth, int srcheight, float ** l_a, float ** l_b, const double * mult, const double dirpyrThreshold, const double skinprot, float b_l, float t_l, float t_r, int scale); //Emil's directional pyramid wavelet
diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc
index 6934cc8e8..c50306f6f 100644
--- a/rtengine/simpleprocess.cc
+++ b/rtengine/simpleprocess.cc
@@ -785,7 +785,7 @@ private:
// Spot Removal
if (params.spot.enabled && !params.spot.entries.empty ()) {
- ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, tr);
+ ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, nullptr, tr);
}
// at this stage, we can flush the raw data to free up quite an important amount of memory
diff --git a/rtengine/spot.cc b/rtengine/spot.cc
index 2090324dd..b12f5bc40 100644
--- a/rtengine/spot.cc
+++ b/rtengine/spot.cc
@@ -38,34 +38,6 @@ constexpr T skips(T a, T b)
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
- */
-
-#if 1
-
class SpotBox {
public:
@@ -329,6 +301,9 @@ public:
return true;
}
+#define ALGO 1
+
+#if ALGO==1
bool processIntersectionWith(SpotBox &destBox) {
Imagefloat *dstImg = destBox.image;
@@ -337,30 +312,6 @@ public:
return false;
}
- /*
- printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i,
- spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2,
- getWidth(), getHeight(),
- destBox.getWidth(), destBox.getHeight()
- );
- */
-
-#ifndef NDEBUG
- printf("[processIntersectionWith] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n",
- this, getWidth(), getHeight(), image, destBox.getWidth(), destBox.getHeight(),
- &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight());
- printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n",
- spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2,
- destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2);
- printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n",
- imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2,
- destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2);
- printf(" [intr] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n",
- intersectionArea.x1, intersectionArea.y1, intersectionArea.x2, intersectionArea.y2,
- destBox.intersectionArea.x1, destBox.intersectionArea.y1, destBox.intersectionArea.x2, destBox.intersectionArea.y2);
- printf(" radius = %.3f\n", radius);
-#endif
-
int srcImgY = intersectionArea.y1 - imgArea.y1;
int dstImgY = destBox.intersectionArea.y1 - destBox.imgArea.y1;
for (int y = intersectionArea.y1; y <= intersectionArea.y2; ++y) {
@@ -396,358 +347,44 @@ public:
return true;
}
-
- // Copy the intersecting part
- bool copyImgTo(SpotBox &destBox) {
- Imagefloat *destImg = destBox.image;
-
- if (image == nullptr || destImg == nullptr) {
- std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl;
- return false;
- }
-
- /*
- printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i,
- spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2,
- getWidth(), getHeight(),
- destBox.getWidth(), destBox.getHeight()
- );
- */
-
- std::unique_ptr intersection;
-
- if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) {
- return false;
- }
-
- Imagefloat *srcImg = image;
- Imagefloat *dstImg = destBox.image;
-
-#ifndef NDEBUG
- printf("[copyImgTo] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n",
- this, getWidth(), getHeight(), image, srcImg->getWidth(), srcImg->getHeight(),
- &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight());
- printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n",
- spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2,
- destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2);
- printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n",
- imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2,
- destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2);
- printf(" [intr] intersection (%d, %d) -> (%d, %d)\n",
- intersection->x1, intersection->y1, intersection->x2, intersection->y2);
#endif
- int srcImgY = intersection->y1 - imgArea.y1;
- int dstImgY = intersection->y1 - destBox.imgArea.y1;
- for (int y = intersection->y1; y <= intersection->y2; ++y) {
- int srcImgX = intersection->x1 - imgArea.x1;
- int dstImgX = intersection->x1 - destBox.imgArea.x1;
-
- for (int x = intersection->x1; x <= intersection->x2; ++x) {
- dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX);
- dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX);
- dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX);
- ++srcImgX;
- ++dstImgX;
- }
- ++srcImgY;
- ++dstImgY;
- }
-
- return true;
- }
-};
-
-void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr)
-{
- //Get the clipped image areas (src & dst) from the source image
-
- std::vector< std::shared_ptr > srcSpotBoxs;
- std::vector< std::shared_ptr > dstSpotBoxs;
- int fullImgWidth = 0;
- int fullImgHeight = 0;
- imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr);
- SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL);
- SpotBox cropBox(pp.getX(), pp.getY(),
- pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1,
- 0, 0, img, SpotBox::Type::FINAL);
-
- std::set visibleSpots; // list of dest spots intersecting the preview's crop
- int i = 0;
-
- for (auto entry : params->spot.entries) {
- std::shared_ptr srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE));
- std::shared_ptr dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET));
- if ( !srcSpotBox->setIntersectionWith(fullImageBox)
- || !dstSpotBox->setIntersectionWith(fullImageBox)
- || !srcSpotBox->imageIntersects(*dstSpotBox, true))
- {
- continue;
- ++i;
- }
-
- // If spot intersect the preview image, add it to the visible spots
- if (dstSpotBox->spotIntersects(cropBox)) {
-#ifndef NDEBUG
- printf("Spot visible insere: %d\n", i);
-#endif
- visibleSpots.insert(i);
- }
- ++i;
-
- // Source area
- PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1,
- srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip());
- int w = 0;
- int h = 0;
- imgsrc->getSize(spp, w, h);
- *srcSpotBox /= pp.getSkip();
- srcSpotBox->allocImage();
- Imagefloat *_image_ = srcSpotBox->getImage();
- for (int y = 0; y < (int)_image_->getHeight(); ++y) {
- for (int x = 0; x < (int)_image_->getWidth(); ++x) {
- _image_->r(y, x) = 60000.f;
- _image_->g(y, x) = 500.f;
- _image_->b(y, x) = 500.f;
- }
- }
-
-#ifndef NDEBUG
- printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight());
-#endif
- imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw);
-#ifndef NDEBUG
- printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight());
-#endif
- assert(srcSpotBox->checkImageSize());
- //printf(" / src size: %d,%d", currSrcSpot->getWidth(), currSrcSpot->getHeight());
+#if ALGO==2
+ bool processIntersectionWith(SpotBox &destBox) {
+ /* The following disabled code has been taken from Gimp 2.8.10 and converted
+ * for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014.
+ * It has not been tested, at all, an can (does) contain bugs. Feel free to
+ * replace the actual working code by this one, if results are better.
+ */
- // Destination area
- spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(),
- dstSpotBox->getImageHeight(), pp.getSkip());
- *dstSpotBox /= pp.getSkip();
- dstSpotBox->allocImage();
- _image_ = dstSpotBox->getImage();
- for (int y = 0; y < (int)_image_->getHeight(); ++y) {
- for (int x = 0; x < (int)_image_->getWidth(); ++x) {
- _image_->r(y, x) = 500.f;
- _image_->g(y, x) = 500.f;
- _image_->b(y, x) = 60000.f;
- }
- }
-#ifndef NDEBUG
- printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight());
-#endif
- imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw);
-#ifndef NDEBUG
- printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight());
-#endif
- assert(dstSpotBox->checkImageSize());
- //printf(" / src size: %d,%d", currDstSpot->getWidth(), currDstSpot->getHeight());
-
- // Update the intersectionArea between src and dest
- if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) {
- srcSpotBoxs.push_back(srcSpotBox);
- dstSpotBoxs.push_back(dstSpotBox);
-
-#ifndef NDEBUG
- printf("Taille de l'image pour spot #%d: src(%d, %d) / dst(%d, %d)\n", i-1,
- srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(),
- dstSpotBox->getImageWidth(), dstSpotBox->getImageHeight()
- );
-#endif
- }
-
- }
-
- // Construct list of upstream dependancies
-
- std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots
-#ifndef NDEBUG
- printf("Required spots: ");
-#endif
- for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) {
- int spotNbr = *i;
-#ifndef NDEBUG
- printf("%d ", spotNbr);
-#endif
- requiredSpots.insert(spotNbr);
- if (spotNbr > 0) {
- for (int j = spotNbr - 1; j >= 0; --j) {
- if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) {
- requiredSpots.insert(spotNbr);
-#ifndef NDEBUG
- printf("%d ", spotNbr);
-#endif
- }
- }
- }
- }
-#ifndef NDEBUG
- printf("\n");
-#endif
-
- // Process spots and copy them downstream
-
- for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) {
- // Process
-#ifndef NDEBUG
- printf("========>>> Processing spot #%d\n", *i);
-#endif
- srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i));
-
- // Propagate
- std::set positiveSpots; // For DEBUG purpose only !
- auto j = i;
- ++j;
- while (j != requiredSpots.end()) {
- bool intersectionFound = false;
- int i_ = *i;
- int j_ = *j;
-#ifndef NDEBUG
- printf("Propagating to SRC #%d ?\n", j_);
-#endif
- intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_));
-#ifndef NDEBUG
- printf("Propagating to DSTC #%d ?\n", j_);
-#endif
- intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_));
- if (intersectionFound) {
- positiveSpots.insert(j_);
- }
- ++j;
- }
-#ifndef NDEBUG
- printf("||| DEST spot #%d propagated to : ", *i);
- for (auto g : positiveSpots) {
- printf("%d ", g);
- }
- printf("\n");
-#endif
- }
-
- // Copy the dest spot to the preview image
-#ifndef NDEBUG
- printf("cropBox (%d, %d) -> (%d, %d)",
- cropBox.imgArea.x1, cropBox.imgArea.y1,
- cropBox.imgArea.x2, cropBox.imgArea.y2);
-#endif
- cropBox /= pp.getSkip();
-#ifndef NDEBUG
- printf(" -> [/skip] (%d, %d) -> (%d, %d)",
- cropBox.imgArea.x1, cropBox.imgArea.y1,
- cropBox.imgArea.x2, cropBox.imgArea.y2);
-#endif
- cropBox.tuneImageSize();
- cropBox.intersectionArea = cropBox.imgArea;
-#ifndef NDEBUG
- printf(" -> [/tuned] (%d, %d) -> (%d, %d)\n",
- cropBox.imgArea.x1, cropBox.imgArea.y1,
- cropBox.imgArea.x2, cropBox.imgArea.y2);
-#endif
-
- int f = 0;
- for (auto i : visibleSpots) {
- f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0;
- }
-#ifndef NDEBUG
- printf("Nombre de copie finale : %d\n", f);
-#endif
-
- /*
- printf("#--: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n\n",
- cropBox.spotArea.x1, cropBox.spotArea.y1, cropBox.spotArea.x2, cropBox.spotArea.y2,
- cropBox.getWidth(), cropBox.getHeight(),
- cropBox.image->getWidth(), cropBox.image->getHeight()
- );
- */
-}
-
-#endif
+ /* 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
+ */
-
-
-
-#if 0
-void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr)
-{
-
- Alpha mask;
-
-
- for (const auto entry : entries) {
- float featherRadius = entry.getFeatherRadius();
- int scaledFeatherRadius = featherRadius / pp.getSkip ();
-
- SpotBox srcBox(entry, SpotBox::Type::SOURCE);
- srcBox.translate(-pp.getX(), -pp.getY());
- srcBox /= float (pp.getSkip());
-
- SpotBox dstBox(entry, SpotBox::Type::TARGET);
- dstBox.translate(-pp.getX(), -pp.getY());
- dstBox /= float (pp.getSkip());
-
- //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.getSkip() != 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 inside the image bounds, even partially
- if (src_XMin < 0 || src_XMax >= img->getWidth() || src_YMin < 0 || src_YMax >= img->getHeight()) {
-#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->getWidth(), src_YMin, src_YMax, img->getHeight());
- }
-
-#endif
- continue;
- }
-
- // skipping entries where the dest circle is completely outside the image bounds
- /*
- if (dst_XMin >= img->getWidth() || dst_XMax <= 0 || dst_YMin >= img->getHeight() || 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->getWidth(), dst_XMax, dst_YMin, img->getHeight(), dst_YMax);
- }
-
-#endif
- continue;
- }
- */
+ /* Original Algorithm Design:
+ *
+ * T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning
+ * http://www.tgeorgiev.net/Photoshop_Healing.pdf
+ */
// ----------------- Core function -----------------
-#if 0
int scaledPPX = pp.getX() / pp.skip;
int scaledPPY = pp.getY() / pp.skip;
int scaledPPW = pp.getWidth() / pp.skip + (pp.getWidth() % pp.getSkip() > 0);
@@ -810,7 +447,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s
// 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
@@ -829,8 +465,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s
// 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);
@@ -893,8 +527,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s
*/
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);
@@ -956,16 +588,8 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s
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->getHeight()) {
@@ -977,25 +601,189 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s
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;
- /*
+ 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) = 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;
- */
+ }
+ }
+ }
+#endif
+
+ // Copy the intersecting part
+ bool copyImgTo(SpotBox &destBox) {
+ Imagefloat *destImg = destBox.image;
+
+ if (image == nullptr || destImg == nullptr) {
+ std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl;
+ return false;
+ }
+
+ std::unique_ptr intersection;
+
+ if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) {
+ return false;
+ }
+
+ Imagefloat *srcImg = image;
+ Imagefloat *dstImg = destBox.image;
+
+ int srcImgY = intersection->y1 - imgArea.y1;
+ int dstImgY = intersection->y1 - destBox.imgArea.y1;
+ for (int y = intersection->y1; y <= intersection->y2; ++y) {
+ int srcImgX = intersection->x1 - imgArea.x1;
+ int dstImgX = intersection->x1 - destBox.imgArea.x1;
+
+ for (int x = intersection->x1; x <= intersection->x2; ++x) {
+ dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX);
+ dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX);
+ dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX);
+ ++srcImgX;
+ ++dstImgX;
+ }
+ ++srcImgY;
+ ++dstImgY;
+ }
+
+ return true;
+ }
+};
+
+void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr)
+{
+ //Get the clipped image areas (src & dst) from the source image
+
+ std::vector< std::shared_ptr > srcSpotBoxs;
+ std::vector< std::shared_ptr > dstSpotBoxs;
+ int fullImgWidth = 0;
+ int fullImgHeight = 0;
+ imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr);
+ SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL);
+ SpotBox cropBox(pp.getX(), pp.getY(),
+ pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1,
+ 0, 0, img, SpotBox::Type::FINAL);
+
+ std::set visibleSpots; // list of dest spots intersecting the preview's crop
+ int i = 0;
+
+ for (auto entry : params->spot.entries) {
+ std::shared_ptr srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE));
+ std::shared_ptr dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET));
+ if ( !srcSpotBox->setIntersectionWith(fullImageBox)
+ || !dstSpotBox->setIntersectionWith(fullImageBox)
+ || !srcSpotBox->imageIntersects(*dstSpotBox, true))
+ {
+ continue;
+ ++i;
+ }
+
+ // If spot intersect the preview image, add it to the visible spots
+ if (dstSpotBox->spotIntersects(cropBox)) {
+ visibleSpots.insert(i);
+ }
+ ++i;
+
+ // Source area
+ PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1,
+ srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip());
+ int w = 0;
+ int h = 0;
+ imgsrc->getSize(spp, w, h);
+ *srcSpotBox /= pp.getSkip();
+ srcSpotBox->allocImage();
+ Imagefloat *srcImage = srcSpotBox->getImage();
+ for (int y = 0; y < (int)srcImage->getHeight(); ++y) {
+ for (int x = 0; x < (int)srcImage->getWidth(); ++x) {
+ srcImage->r(y, x) = 60000.f;
+ srcImage->g(y, x) = 500.f;
+ srcImage->b(y, x) = 500.f;
}
}
+ imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw);
+ if (cmp) {
+ imgsrc->convertColorSpace(srcImage, *cmp, currWB);
+ }
+ assert(srcSpotBox->checkImageSize());
+
+
+ // Destination area
+ spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(),
+ dstSpotBox->getImageHeight(), pp.getSkip());
+ *dstSpotBox /= pp.getSkip();
+ dstSpotBox->allocImage();
+ Imagefloat *dstImage = dstSpotBox->getImage();
+ for (int y = 0; y < (int)dstImage->getHeight(); ++y) {
+ for (int x = 0; x < (int)dstImage->getWidth(); ++x) {
+ dstImage->r(y, x) = 500.f;
+ dstImage->g(y, x) = 500.f;
+ dstImage->b(y, x) = 60000.f;
+ }
+ }
+ imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw);
+ if (cmp) {
+ imgsrc->convertColorSpace(dstImage, *cmp, currWB);
+ }
+ assert(dstSpotBox->checkImageSize());
+
+ // Update the intersectionArea between src and dest
+ if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) {
+ srcSpotBoxs.push_back(srcSpotBox);
+ dstSpotBoxs.push_back(dstSpotBox);
+ }
+
+ }
+
+ // Construct list of upstream dependancies
+
+ std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots
+ for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) {
+ int spotNbr = *i;
+ requiredSpots.insert(spotNbr);
+ if (spotNbr > 0) {
+ for (int j = spotNbr - 1; j >= 0; --j) {
+ if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) {
+ requiredSpots.insert(spotNbr);
+ }
+ }
+ }
+ }
+
+ // Process spots and copy them downstream
+
+ for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) {
+ // Process
+ srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i));
+
+ // Propagate
+ std::set positiveSpots; // For DEBUG purpose only !
+ auto j = i;
+ ++j;
+ while (j != requiredSpots.end()) {
+ bool intersectionFound = false;
+ int i_ = *i;
+ int j_ = *j;
+ intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_));
+ intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_));
+ if (intersectionFound) {
+ positiveSpots.insert(j_);
+ }
+ ++j;
+ }
+ }
+
+ // Copy the dest spot to the preview image
+ cropBox /= pp.getSkip();
+ cropBox.tuneImageSize();
+ cropBox.intersectionArea = cropBox.imgArea;
+
+ int f = 0;
+ for (auto i : visibleSpots) {
+ f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0;
}
}
-#endif
}