From 56dafcf8c195eceb548d0b45cd41ee2aeada7af4 Mon Sep 17 00:00:00 2001 From: Hombre Date: Sat, 23 Apr 2016 00:43:48 +0200 Subject: [PATCH] Spot Removal tool It is not working yet, but the GUI is (almost) done. See issue #2239. --- rtdata/images/Dark/actions/spot-active.png | Bin 0 -> 330 bytes rtdata/images/Dark/actions/spot-normal.png | Bin 0 -> 397 bytes rtdata/images/Dark/actions/spot-prelight.png | Bin 0 -> 322 bytes rtdata/images/Light/actions/spot-active.png | Bin 0 -> 330 bytes rtdata/images/Light/actions/spot-normal.png | Bin 0 -> 397 bytes rtdata/images/Light/actions/spot-prelight.png | Bin 0 -> 322 bytes rtdata/languages/Francais | 6 +- rtdata/languages/default | 6 +- rtengine/CMakeLists.txt | 2 +- rtengine/alpha.cc | 75 ++ rtengine/alpha.h | 77 ++ rtengine/dcrop.cc | 61 +- rtengine/dcrop.h | 1 + rtengine/iimage.h | 53 ++ rtengine/image16.cc | 16 +- rtengine/image16.h | 3 +- rtengine/image8.cc | 2 +- rtengine/image8.h | 2 +- rtengine/imagedimensions.cc | 2 +- rtengine/imagedimensions.h | 60 +- rtengine/imagefloat.cc | 16 +- rtengine/imagefloat.h | 3 +- rtengine/imageio.h | 2 +- rtengine/imagesource.h | 2 +- rtengine/improccoordinator.cc | 56 +- rtengine/improccoordinator.h | 2 + rtengine/improcfun.h | 3 + rtengine/procevents.h | 3 +- rtengine/procparams.cc | 65 +- rtengine/procparams.h | 50 ++ rtengine/rawimagesource.cc | 2 +- rtengine/rawimagesource.h | 2 +- rtengine/refreshmap.cc | 19 +- rtengine/refreshmap.h | 40 +- rtengine/simpleprocess.cc | 4 + rtengine/spot.cc | 370 ++++++++++ rtengine/stdimagesource.cc | 2 +- rtengine/stdimagesource.h | 2 +- rtgui/CMakeLists.txt | 2 +- rtgui/paramsedited.cc | 14 + rtgui/paramsedited.h | 8 + rtgui/spot.cc | 690 ++++++++++++++++++ rtgui/spot.h | 101 +++ rtgui/thumbimageupdater.cc | 4 +- rtgui/toolpanelcoord.cc | 3 + rtgui/toolpanelcoord.h | 2 + tools/source_icons/scalable/spot-active.file | 1 + tools/source_icons/scalable/spot-active.svg | 81 ++ tools/source_icons/scalable/spot-normal.file | 1 + tools/source_icons/scalable/spot-normal.svg | 70 ++ .../source_icons/scalable/spot-prelight.file | 1 + tools/source_icons/scalable/spot-prelight.svg | 74 ++ 52 files changed, 1955 insertions(+), 106 deletions(-) create mode 100644 rtdata/images/Dark/actions/spot-active.png create mode 100644 rtdata/images/Dark/actions/spot-normal.png create mode 100644 rtdata/images/Dark/actions/spot-prelight.png create mode 100644 rtdata/images/Light/actions/spot-active.png create mode 100644 rtdata/images/Light/actions/spot-normal.png create mode 100644 rtdata/images/Light/actions/spot-prelight.png create mode 100644 rtengine/alpha.cc create mode 100644 rtengine/alpha.h create mode 100644 rtengine/spot.cc create mode 100644 rtgui/spot.cc create mode 100644 rtgui/spot.h create mode 100644 tools/source_icons/scalable/spot-active.file create mode 100644 tools/source_icons/scalable/spot-active.svg create mode 100644 tools/source_icons/scalable/spot-normal.file create mode 100644 tools/source_icons/scalable/spot-normal.svg create mode 100644 tools/source_icons/scalable/spot-prelight.file create mode 100644 tools/source_icons/scalable/spot-prelight.svg diff --git a/rtdata/images/Dark/actions/spot-active.png b/rtdata/images/Dark/actions/spot-active.png new file mode 100644 index 0000000000000000000000000000000000000000..56bd35f477f0c8dd54944e913feba472c6dffcd3 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmSQK*5Dp-y;YjHK@;M7UB8wRq z_$5J@am9+;)<8kY64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1T!d%8G= zXiS_sVI$We1A#Vs?}>bTyCe#Xf;MUG_{lQqotXT_cf7Hm8NOLQlAHACNy6@o)~d@N z|9_YLe|OELS@l|y!uqAP`>uSgG-61(b}KgXOre$oM2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4UJd?&7I?d*?GOH2cTId+?aw?X59h&POBO zMBkm^U{XCf+~?lj@_T3QoIJJY!m)(Sl1XiaB5xK=OAXw!^=D+V!HpcFn8$LKaUX^D z2(BwQ!Yg-bk%t10k&SX(iQDW9fXN)5SZG>g6bqL2L% z^=rBxO_qi%l@HNent3`!c9!CuhZlNe9|h0Y)>nK$*Cz3{)NZTf0Mp|K#r!L6LW^&U zzp(6$D`-iZGhgvP=T0ky#S#0H>)ZD8_g#4NqN3As+N&1ZL&sA#1-V!L&wd{Hh2?uk p=y!$E-lFH{zvNzcu>bx~?r>$%*9+IT{09adgQu&X%Q~loCII#VsOSIy literal 0 HcmV?d00001 diff --git a/rtdata/images/Dark/actions/spot-prelight.png b/rtdata/images/Dark/actions/spot-prelight.png new file mode 100644 index 0000000000000000000000000000000000000000..cb9e555bd57a120ea0c4e10dd6559ab06b31766b GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmSQK*5Dp-y;YjHK@;M7UB8wRq z_$5J@am9+;)<8kY64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U{d%8G= zXiQu>VIx_{dC9j-C+I3D|Xj;Jk_>J-xMWxuuiunq6jeg2W=DoeZ z(WngYGT N;OXk;vd$@?2>{bTyCe#Xf;MUG_{lQqotXT_cf7Hm8NOLQlAHACNy6@o)~d@N z|9_YLe|OELS@l|y!uqAP`>uSgG-61(b}KgXOre$oM2?p zUk71ECym(^Ktah8*NBqf{Irtt#G+J&^73-M%)IR4UJd?&7I?d*?GOH2cTId+?aw?X59h&POBO zMBkm^U{XCf+~?lj@_T3QoIJJY!m)(Sl1XiaB5xK=OAXw!^=D+V!HpcFn8$LKaUX^D z2(BwQ!Yg-bk%t10k&SX(iQDW9fXN)5SZG>g6bqL2L% z^=rBxO_qi%l@HNent3`!c9!CuhZlNe9|h0Y)>nK$*Cz3{)NZTf0Mp|K#r!L6LW^&U zzp(6$D`-iZGhgvP=T0ky#S#0H>)ZD8_g#4NqN3As+N&1ZL&sA#1-V!L&wd{Hh2?uk p=y!$E-lFH{zvNzcu>bx~?r>$%*9+IT{09adgQu&X%Q~loCII#VsOSIy literal 0 HcmV?d00001 diff --git a/rtdata/images/Light/actions/spot-prelight.png b/rtdata/images/Light/actions/spot-prelight.png new file mode 100644 index 0000000000000000000000000000000000000000..cb9e555bd57a120ea0c4e10dd6559ab06b31766b GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmSQK*5Dp-y;YjHK@;M7UB8wRq z_$5J@am9+;)<8kY64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1U{d%8G= zXiQu>VIx_{dC9j-C+I3D|Xj;Jk_>J-xMWxuuiunq6jeg2W=DoeZ z(WngYGT N;OXk;vd$@?2>{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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + +