From 56dafcf8c195eceb548d0b45cd41ee2aeada7af4 Mon Sep 17 00:00:00 2001 From: Hombre Date: Sat, 23 Apr 2016 00:43:48 +0200 Subject: [PATCH 01/34] 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 + + + + + + + + + + + From 81932ba2d63c73fe15e7c53c5feeb3784b2cf7c3 Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Tue, 22 Aug 2017 00:15:56 +0200 Subject: [PATCH 02/34] Some files 'astylized' + slight cleanup --- rtengine/alpha.cc | 27 ++++++++++++++++++++++++--- rtengine/alpha.h | 25 +++---------------------- rtengine/spot.cc | 10 ++++++++++ rtgui/spot.cc | 29 ++++++++++++++++------------- 4 files changed, 53 insertions(+), 38 deletions(-) diff --git a/rtengine/alpha.cc b/rtengine/alpha.cc index e562350fc..8d1fd6b0e 100644 --- a/rtengine/alpha.cc +++ b/rtengine/alpha.cc @@ -27,7 +27,7 @@ Alpha::Alpha () {} Alpha::Alpha (int width, int height) { if (width > 0 && height > 0) { - surface = Cairo::ImageSurface::create(Cairo::FORMAT_A8, width, height); + surface = Cairo::ImageSurface::create (Cairo::FORMAT_A8, width, height); } } @@ -37,7 +37,7 @@ Alpha::~Alpha () { } */ -void Alpha::setSize(int width, int height) +void Alpha::setSize (int width, int height) { if (width > 0 && height > 0) { if (surface) { @@ -48,7 +48,7 @@ void Alpha::setSize(int width, int height) } } - surface = Cairo::ImageSurface::create(Cairo::FORMAT_A8, width, height); + surface = Cairo::ImageSurface::create (Cairo::FORMAT_A8, width, height); } else if (surface) { surface.clear(); } @@ -72,4 +72,25 @@ int Alpha::getHeight() return -1; } + +Cairo::RefPtr Alpha::getSurface () +{ + return surface; // to be used in bitmap edition +} + +unsigned char Alpha::operator () (unsigned row, unsigned col) const +{ + return * (surface->get_data () + row * surface->get_width () + col); +} + +unsigned char& Alpha::operator () (unsigned row, unsigned col) +{ + return * (surface->get_data () + row * surface->get_width () + col); +} + +unsigned char* Alpha::operator () (unsigned row) const +{ + return surface->get_data () + row * surface->get_width (); +} + } diff --git a/rtengine/alpha.h b/rtengine/alpha.h index 57a0af343..90f32265e 100644 --- a/rtengine/alpha.h +++ b/rtengine/alpha.h @@ -38,11 +38,11 @@ public: Alpha (int width, int height); //~Alpha (); - void setSize(int width, int height); + void setSize (int width, int height); int getWidth(); int getHeight(); - const Cairo::RefPtr getSurface (); + Cairo::RefPtr getSurface (); // TODO: to make the editing faster, we should add an iterator class @@ -50,28 +50,9 @@ public: 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; + 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/spot.cc b/rtengine/spot.cc index 87f174e72..858a1fd44 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -79,9 +79,11 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector // scaled spot is too small, we do not preview it if (scaledFeatherRadius < 2 && pp.getSkip() != 1) { #ifndef NDEBUG + if (options.rtSettings.verbose) { printf ("Skipping spot located at %d x %d, too small for the preview zoom rate\n", entry.sourcePos.x, entry.sourcePos.y); } + #endif continue; } @@ -89,9 +91,11 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector // 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 } @@ -99,11 +103,13 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector // skipping entries where the source circle isn't completely inside the image bounds if (src_XMin < 0 || src_XMax >= img->getWidth() || src_YMin < 0 || src_YMax >= img->getHeight()) { #ifndef NDEBUG + if (options.rtSettings.verbose) { printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity); printf ("%d < 0 || %d >= %d || %d < 0 || %d >= %d\n", src_XMin, src_XMax, img->getWidth(), src_YMin, src_YMax, img->getHeight()); } + #endif continue; } @@ -111,11 +117,13 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector // skipping entries where the dest circle is completely outside the image bounds if (dst_XMin >= img->getWidth() || dst_XMax <= 0 || dst_YMin >= img->getHeight() || dst_YMax <= 0) { #ifndef NDEBUG + if (options.rtSettings.verbose) { printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity); printf ("%d >= %d || %d <= 0 || %d >= %d || %d <= 0\n", dst_XMin, img->getWidth(), dst_XMax, dst_YMin, img->getHeight(), dst_YMax); } + #endif continue; } @@ -343,10 +351,12 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector if (i2 < 0 || i2 >= img->getHeight()) { continue; } + for (int j = 0, j2 = dst_XMin; j2 < dst_XMax - 1; ++j, ++j2) { if (j2 < 0 || j2 >= img->getWidth()) { 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)) ) ); diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 949f592ee..55480ece5 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -14,7 +14,7 @@ using namespace rtengine::procparams; #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) + 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))); @@ -74,6 +74,7 @@ Spot::~Spot() 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 } @@ -170,11 +171,11 @@ void Spot::enabledChanged () { if (listener) { if (get_inconsistent()) { - listener->panelChanged (EvSpotEnabled, M("GENERAL_UNCHANGED")); + listener->panelChanged (EvSpotEnabled, M ("GENERAL_UNCHANGED")); } else if (getEnabled()) { - listener->panelChanged (EvSpotEnabled, M("GENERAL_ENABLED")); + listener->panelChanged (EvSpotEnabled, M ("GENERAL_ENABLED")); } else { - listener->panelChanged (EvSpotEnabled, M("GENERAL_DISABLED")); + listener->panelChanged (EvSpotEnabled, M ("GENERAL_DISABLED")); } } } @@ -215,7 +216,7 @@ void Spot::createGeometry () int nbrEntry = spots.size(); if (!batchMode) { - countLabel->set_text(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), nbrEntry)); + countLabel->set_text (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), nbrEntry)); } //printf("CreateGeometry(%d)\n", nbrEntry); @@ -242,7 +243,7 @@ void Spot::createGeometry () 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::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; @@ -379,9 +380,11 @@ 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); } @@ -492,11 +495,11 @@ bool Spot::button2Pressed (const int modifierKey) { EditDataProvider* editProvider = getEditProvider(); - if (!editProvider || lastObject == -1 || activeSpot==-1) { + if (!editProvider || lastObject == -1 || activeSpot == -1) { return false; } - if (!(modifierKey & (GDK_SHIFT_MASK|GDK_SHIFT_MASK))) { + if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) { EditSubscriber::action = ES_ACTION_PICKING; } @@ -508,7 +511,7 @@ bool Spot::button3Pressed (const int modifierKey) { EditDataProvider* editProvider = getEditProvider(); - if (!editProvider || lastObject == -1 || activeSpot==-1) { + if (!editProvider || lastObject == -1 || activeSpot == -1) { return false; } @@ -517,7 +520,7 @@ bool Spot::button3Pressed (const int modifierKey) sourceIcon.state = Geometry::DRAGGED; EditSubscriber::action = ES_ACTION_DRAGGING; return true; - } else if (!(modifierKey & (GDK_SHIFT_MASK|GDK_SHIFT_MASK))) { + } else if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) { EditSubscriber::action = ES_ACTION_PICKING; } @@ -597,7 +600,7 @@ bool Spot::drag1 (const int modifierKey) //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); + 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) { @@ -650,9 +653,9 @@ bool Spot::drag3 (const int modifierKey) return modified; } -bool Spot::pick2(const bool picked) +bool Spot::pick2 (const bool picked) { - return pick3(picked); + return pick3 (picked); } bool Spot::pick3 (const bool picked) From e0c04955e3cc20202514c284c368b78cf0a7437c Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 19 Mar 2019 22:29:10 +0100 Subject: [PATCH 03/34] Fix build crash (see #5232), but this branch is still not usable --- rtengine/dcrop.cc | 45 +++++++++++++++++++------------------- rtgui/bayerpreprocess.cc | 1 + rtgui/bayerrawexposure.cc | 1 + rtgui/crophandler.h | 3 ++- rtgui/edit.cc | 44 ++++++++++++++++++------------------- rtgui/edit.h | 34 ++++++++++++++-------------- rtgui/exifpanel.cc | 1 + rtgui/labgrid.cc | 1 + rtgui/lensprofile.cc | 1 + rtgui/preprocess.cc | 1 + rtgui/rawcacorrection.cc | 1 + rtgui/rawexposure.cc | 3 +-- rtgui/sharpenedge.cc | 2 +- rtgui/spot.cc | 14 ++++++------ rtgui/spot.h | 4 ++-- rtgui/toolpanel.h | 2 +- rtgui/xtransrawexposure.cc | 2 +- 17 files changed, 84 insertions(+), 76 deletions(-) diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 0445e64a6..fd86b4e5b 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -746,6 +746,29 @@ void Crop::update(int todo) parent->ipf.ToneMapFattal02(f); } + // Apply Spot removal + 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; + } + // crop back to the size expected by the rest of the pipeline if (need_cropping) { Imagefloat *c = origCrop; @@ -798,28 +821,6 @@ void Crop::update(int todo) transCrop = nullptr; } - 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_TRANSFORM | M_RGBCURVE)) && params.dirpyrequalizer.cbdlMethod == "bef" && params.dirpyrequalizer.enabled && !params.colorappearance.enabled) { const int W = baseCrop->getWidth(); diff --git a/rtgui/bayerpreprocess.cc b/rtgui/bayerpreprocess.cc index 89fd6fcb6..4910b2798 100644 --- a/rtgui/bayerpreprocess.cc +++ b/rtgui/bayerpreprocess.cc @@ -21,6 +21,7 @@ #include "bayerpreprocess.h" #include "eventmapper.h" #include "guiutils.h" +#include "options.h" #include "../rtengine/procparams.h" diff --git a/rtgui/bayerrawexposure.cc b/rtgui/bayerrawexposure.cc index d49486511..86cd9794b 100644 --- a/rtgui/bayerrawexposure.cc +++ b/rtgui/bayerrawexposure.cc @@ -19,6 +19,7 @@ #include "bayerrawexposure.h" #include "guiutils.h" +#include "options.h" #include "../rtengine/procparams.h" diff --git a/rtgui/crophandler.h b/rtgui/crophandler.h index e0b48d348..3be052a52 100644 --- a/rtgui/crophandler.h +++ b/rtgui/crophandler.h @@ -27,10 +27,11 @@ #include "../rtengine/rtengine.h" -#include "edit.h" #include "lockablecolorpicker.h" #include "threadutils.h" +class EditSubscriber; + class CropDisplayHandler { diff --git a/rtgui/edit.cc b/rtgui/edit.cc index c59c2dc04..896e61b68 100644 --- a/rtgui/edit.cc +++ b/rtgui/edit.cc @@ -802,11 +802,11 @@ void OPIcon::drivenPointToRectangle(const rtengine::Coord &pos, bottomRight.y = topLeft.y + H - 1; } -OPIcon::OPIcon(const Cairo::RefPtr &normal, - const Cairo::RefPtr &active, - const Cairo::RefPtr &prelight, - const Cairo::RefPtr &dragged, - const Cairo::RefPtr &insensitive, +OPIcon::OPIcon(const Cairo::RefPtr &normal, + const Cairo::RefPtr &active, + const Cairo::RefPtr &prelight, + const Cairo::RefPtr &dragged, + const Cairo::RefPtr &insensitive, DrivenPoint drivenPoint) : drivenPoint(drivenPoint) { @@ -835,53 +835,53 @@ OPIcon::OPIcon(Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustri Glib::ustring draggedImage, Glib::ustring insensitiveImage, DrivenPoint drivenPoint) : drivenPoint(drivenPoint) { if (!normalImage.empty()) { - normalImg->setImage(normalImage); + normalImg = RTImage::createImgSurfFromFile(normalImage); } if (!prelightImage.empty()) { - prelightImg->setImage(prelightImage); + prelightImg = RTImage::createImgSurfFromFile(prelightImage); } if (!activeImage.empty()) { - activeImg->setImage(activeImage); + activeImg = RTImage::createImgSurfFromFile(activeImage); } if (!draggedImage.empty()) { - draggedImg->setImage(draggedImage); + draggedImg = RTImage::createImgSurfFromFile(draggedImage); } if (!insensitiveImage.empty()) { - insensitiveImg->setImage(insensitiveImage); + insensitiveImg = RTImage::createImgSurfFromFile(insensitiveImage); } } -const Cairo::RefPtr OPIcon::getNormalImg() +const Cairo::RefPtr OPIcon::getNormalImg() { return normalImg; } -const Cairo::RefPtr OPIcon::getPrelightImg() +const Cairo::RefPtr OPIcon::getPrelightImg() { return prelightImg; } -const Cairo::RefPtr OPIcon::getActiveImg() +const Cairo::RefPtr OPIcon::getActiveImg() { return activeImg; } -const Cairo::RefPtr OPIcon::getDraggedImg() +const Cairo::RefPtr OPIcon::getDraggedImg() { return draggedImg; } -const Cairo::RefPtr OPIcon::getInsensitiveImg() +const Cairo::RefPtr OPIcon::getInsensitiveImg() { return insensitiveImg; } -void OPIcon::drawImage(Cairo::RefPtr &img, +void OPIcon::drawImage(Cairo::RefPtr &img, Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { - int imgW = img->getWidth(); - int imgH = img->getHeight(); + int imgW = img->get_width(); + int imgH = img->get_height(); rtengine::Coord pos; @@ -896,19 +896,19 @@ void OPIcon::drawImage(Cairo::RefPtr &img, rtengine::Coord tl, br; // Coordinate of the rectangle in the CropBuffer coordinate system drivenPointToRectangle(pos, tl, br, imgW, imgH); - cr->set_source(img->get(), tl.x, tl.y); + cr->set_source(img, tl.x, tl.y); cr->set_line_width(0.); cr->rectangle(tl.x, tl.y, imgW, imgH); cr->fill(); } -void OPIcon::drawMOImage(Cairo::RefPtr &img, Cairo::RefPtr &cr, +void OPIcon::drawMOImage(Cairo::RefPtr &img, Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { // test of F_HOVERABLE has already been done - int imgW = img->getWidth(); - int imgH = img->getHeight(); + int imgW = img->get_width(); + int imgH = img->get_height(); rtengine::Coord pos; diff --git a/rtgui/edit.h b/rtgui/edit.h index 786339745..f690cf80b 100644 --- a/rtgui/edit.h +++ b/rtgui/edit.h @@ -407,34 +407,34 @@ class OPIcon : public Geometry // OP stands for "On Preview" { private: - Cairo::RefPtr normalImg; - Cairo::RefPtr prelightImg; - Cairo::RefPtr activeImg; - Cairo::RefPtr draggedImg; - Cairo::RefPtr insensitiveImg; + Cairo::RefPtr normalImg; + Cairo::RefPtr prelightImg; + Cairo::RefPtr activeImg; + Cairo::RefPtr draggedImg; + Cairo::RefPtr insensitiveImg; static void updateImages(); void changeImage(Glib::ustring &newImage); - void drawImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); - void drawMOImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); + void drawImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); + void drawMOImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); void drivenPointToRectangle(const rtengine::Coord &pos, rtengine::Coord &topLeft, rtengine::Coord &bottomRight, int W, int H); public: DrivenPoint drivenPoint; rtengine::Coord position; - OPIcon (const Cairo::RefPtr &normal, - const Cairo::RefPtr &active, - const Cairo::RefPtr &prelight = {}, - const Cairo::RefPtr &dragged = {}, - const Cairo::RefPtr &insensitive = {}, + OPIcon (const Cairo::RefPtr &normal, + const Cairo::RefPtr &active, + const Cairo::RefPtr &prelight = {}, + const Cairo::RefPtr &dragged = {}, + const Cairo::RefPtr &insensitive = {}, DrivenPoint drivenPoint = DP_CENTERCENTER); OPIcon (Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustring prelightImage = "", Glib::ustring draggedImage = "", Glib::ustring insensitiveImage = "", DrivenPoint drivenPoint = DP_CENTERCENTER); - const Cairo::RefPtr getNormalImg(); - const Cairo::RefPtr getPrelightImg(); - const Cairo::RefPtr getActiveImg(); - const Cairo::RefPtr getDraggedImg(); - const Cairo::RefPtr getInsensitiveImg(); + const Cairo::RefPtr getNormalImg(); + const Cairo::RefPtr getPrelightImg(); + const Cairo::RefPtr getActiveImg(); + const Cairo::RefPtr getDraggedImg(); + const Cairo::RefPtr getInsensitiveImg(); void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; diff --git a/rtgui/exifpanel.cc b/rtgui/exifpanel.cc index 4ba3f6c44..c5036798e 100644 --- a/rtgui/exifpanel.cc +++ b/rtgui/exifpanel.cc @@ -20,6 +20,7 @@ #include "guiutils.h" #include "rtimage.h" +#include "options.h" #include "../rtengine/procparams.h" diff --git a/rtgui/labgrid.cc b/rtgui/labgrid.cc index 81d4ee170..bc99f8ca6 100644 --- a/rtgui/labgrid.cc +++ b/rtgui/labgrid.cc @@ -37,6 +37,7 @@ */ #include "labgrid.h" +#include "options.h" using rtengine::Color; diff --git a/rtgui/lensprofile.cc b/rtgui/lensprofile.cc index 85dcb992a..0dd3cb1b7 100644 --- a/rtgui/lensprofile.cc +++ b/rtgui/lensprofile.cc @@ -26,6 +26,7 @@ #include "guiutils.h" #include "rtimage.h" +#include "options.h" #include "../rtengine/lcp.h" #include "../rtengine/procparams.h" diff --git a/rtgui/preprocess.cc b/rtgui/preprocess.cc index 0d8933a98..65646dea3 100644 --- a/rtgui/preprocess.cc +++ b/rtgui/preprocess.cc @@ -21,6 +21,7 @@ #include "preprocess.h" #include "guiutils.h" +#include "options.h" #include "../rtengine/procparams.h" diff --git a/rtgui/rawcacorrection.cc b/rtgui/rawcacorrection.cc index 57b8ff4ac..06780559d 100644 --- a/rtgui/rawcacorrection.cc +++ b/rtgui/rawcacorrection.cc @@ -21,6 +21,7 @@ #include "eventmapper.h" #include "guiutils.h" #include "rtimage.h" +#include "options.h" #include "../rtengine/procparams.h" diff --git a/rtgui/rawexposure.cc b/rtgui/rawexposure.cc index 6f08e64c7..2129c9616 100644 --- a/rtgui/rawexposure.cc +++ b/rtgui/rawexposure.cc @@ -19,9 +19,8 @@ #include #include "rawexposure.h" - #include "guiutils.h" - +#include "options.h" #include "../rtengine/procparams.h" using namespace rtengine; diff --git a/rtgui/sharpenedge.cc b/rtgui/sharpenedge.cc index b6528e4c9..679b898a6 100644 --- a/rtgui/sharpenedge.cc +++ b/rtgui/sharpenedge.cc @@ -22,7 +22,7 @@ #include "sharpenedge.h" #include "guiutils.h" - +#include "options.h" #include "../rtengine/procparams.h" using namespace rtengine; diff --git a/rtgui/spot.cc b/rtgui/spot.cc index bc3c8709f..1c9199c99 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -15,16 +15,16 @@ using namespace rtengine::procparams; #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) + sourceIcon ("spot-normal.png", "spot-active.png", "spot-active.png", "spot-prelight.png", "", Geometry::DP_CENTERCENTER), editedCheckBox (nullptr) { 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"))); + edit->add (*Gtk::manage (new RTImage ("edit-point.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->add (*Gtk::manage (new RTImage ("undo-small.png"))); reset->set_relief (Gtk::RELIEF_NONE); reset->set_border_width (0); reset->signal_clicked().connect ( sigc::mem_fun (*this, &Spot::resetPressed) ); @@ -150,7 +150,7 @@ void Spot::setBatchMode (bool batchMode) if (!editedCheckBox) { removeIfThere (labelBox, countLabel, false); - countLabel = NULL; + countLabel = nullptr; 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); @@ -198,7 +198,7 @@ void Spot::editToggled () Geometry* Spot::getVisibleGeometryFromMO (int MOID) { if (MOID == -1) { - return NULL; + return nullptr; } if (MOID == 0) { @@ -244,7 +244,7 @@ void Spot::createGeometry () 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::mouseOverGeometry.at (i) = EditSubscriber::visibleGeometry.at (j) = new OPIcon (normalImg, activeImg, prelightImg, Cairo::RefPtr (nullptr), Cairo::RefPtr (nullptr), Geometry::DP_CENTERCENTER); EditSubscriber::visibleGeometry.at (j)->setActive (true); EditSubscriber::visibleGeometry.at (j)->datum = Geometry::IMAGE; EditSubscriber::visibleGeometry.at (j)->state = Geometry::NORMAL; @@ -349,7 +349,7 @@ OPIcon *Spot::getActiveSpotIcon() return static_cast (EditSubscriber::visibleGeometry.at (activeSpot)); } - return NULL; + return nullptr; } void Spot::addNewEntry() diff --git a/rtgui/spot.h b/rtgui/spot.h index d62ab7b2f..acce47bec 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -81,8 +81,8 @@ public: Spot (); ~Spot (); - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = NULL); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = NULL); + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); void enabledChanged (); diff --git a/rtgui/toolpanel.h b/rtgui/toolpanel.h index 521d52949..f48e3d0d4 100644 --- a/rtgui/toolpanel.h +++ b/rtgui/toolpanel.h @@ -25,10 +25,10 @@ #include "guiutils.h" #include "multilangmgr.h" #include "paramsedited.h" -#include "edit.h" class ToolPanel; class FoldableToolPanel; +class EditDataProvider; class ToolPanelListener { diff --git a/rtgui/xtransrawexposure.cc b/rtgui/xtransrawexposure.cc index b58a6e72a..a01e8e20a 100644 --- a/rtgui/xtransrawexposure.cc +++ b/rtgui/xtransrawexposure.cc @@ -21,7 +21,7 @@ #include "xtransrawexposure.h" #include "guiutils.h" - +#include "options.h" #include "../rtengine/procparams.h" using namespace rtengine; From 5b0cea2be3580858904c8ed2112b30efa357560e Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 26 Mar 2019 22:07:23 +0100 Subject: [PATCH 04/34] Fix crash when starting RT (see #5230) --- rtgui/editwidgets.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rtgui/editwidgets.cc b/rtgui/editwidgets.cc index 667b8cb37..129d81bc7 100644 --- a/rtgui/editwidgets.cc +++ b/rtgui/editwidgets.cc @@ -731,23 +731,23 @@ OPIcon::OPIcon(Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustri Glib::ustring draggedImage, Glib::ustring insensitiveImage, DrivenPoint drivenPoint) : drivenPoint(drivenPoint) { if (!normalImage.empty()) { - normalImg->setImage(normalImage); + normalImg = Cairo::RefPtr(new RTSurface(normalImage)); } if (!prelightImage.empty()) { - prelightImg->setImage(prelightImage); + prelightImg = Cairo::RefPtr(new RTSurface(prelightImage)); } if (!activeImage.empty()) { - activeImg->setImage(activeImage); + activeImg = Cairo::RefPtr(new RTSurface(activeImage)); } if (!draggedImage.empty()) { - draggedImg->setImage(draggedImage); + draggedImg = Cairo::RefPtr(new RTSurface(draggedImage)); } if (!insensitiveImage.empty()) { - insensitiveImg->setImage(insensitiveImage); + insensitiveImg = Cairo::RefPtr(new RTSurface(insensitiveImage)); } } From 765c20d64a2f8ea90de75b81ffeee81c72bdc664 Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 26 Mar 2019 22:11:49 +0100 Subject: [PATCH 05/34] Line endings of new `edit` files converted to Unix (see #5230) --- rtgui/editcallbacks.cc | 554 ++++++------ rtgui/editcallbacks.h | 390 ++++----- rtgui/editcoordsys.h | 122 +-- rtgui/editwidgets.cc | 1870 ++++++++++++++++++++-------------------- rtgui/editwidgets.h | 1084 +++++++++++------------ 5 files changed, 2010 insertions(+), 2010 deletions(-) diff --git a/rtgui/editcallbacks.cc b/rtgui/editcallbacks.cc index 02eb02a5b..d51d672a5 100644 --- a/rtgui/editcallbacks.cc +++ b/rtgui/editcallbacks.cc @@ -1,277 +1,277 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Jean-Christophe FRISCH - * - * 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 "editcallbacks.h" - -EditSubscriber::EditSubscriber (EditType editType) : - ID(EUID_None), - editingType(editType), - bufferType(BT_SINGLEPLANE_FLOAT), - provider(nullptr), - action(EditSubscriber::Action::NONE) -{} - -void EditSubscriber::setEditProvider(EditDataProvider *provider) -{ - this->provider = provider; -} - -void EditSubscriber::setEditID(EditUniqueID ID, BufferType buffType) -{ - this->ID = ID; - bufferType = buffType; -} - -bool EditSubscriber::isCurrentSubscriber() const -{ - //if (provider && provider->getCurrSubscriber()) - // return provider->getCurrSubscriber()->getEditID() == ID; - - if (provider) { - return provider->getCurrSubscriber() == this; - } - - return false; -} - -void EditSubscriber::subscribe() -{ - if (provider) { - provider->subscribe(this); - } -} - -void EditSubscriber::unsubscribe() -{ - if (provider) { - provider->unsubscribe(); - } -} - -void EditSubscriber::switchOffEditMode() -{ - unsubscribe(); -} - -EditUniqueID EditSubscriber::getEditID() const -{ - return ID; -} - -EditType EditSubscriber::getEditingType() const -{ - return editingType; -} - -BufferType EditSubscriber::getPipetteBufferType() const -{ - return bufferType; -} - -bool EditSubscriber::isDragging() const -{ - return action == EditSubscriber::Action::DRAGGING; -} - -bool EditSubscriber::isPicking() const -{ - return action == EditSubscriber::Action::PICKING; -} - -//-------------------------------------------------------------------------------------------------- - - -EditDataProvider::EditDataProvider() : - currSubscriber(nullptr), - object(0), - pipetteVal1(0.f), - pipetteVal2(0.f), - pipetteVal3(0.f), - posScreen(-1, -1), - posImage(-1, -1), - deltaScreen(0, 0), - deltaImage(0, 0), - deltaPrevScreen(0, 0), - deltaPrevImage(0, 0) -{} - -void EditDataProvider::subscribe(EditSubscriber *subscriber) -{ - if (currSubscriber) { - currSubscriber->switchOffEditMode(); - } - - currSubscriber = subscriber; -} - -void EditDataProvider::unsubscribe() -{ - currSubscriber = nullptr; -} - -void EditDataProvider::switchOffEditMode() -{ - if (currSubscriber) { - currSubscriber->switchOffEditMode (); - } -} - -int EditDataProvider::getObject() const -{ - return object; -} - -void EditDataProvider::setObject(int newObject) -{ - object = newObject; -} - -float EditDataProvider::getPipetteVal1() const -{ - return pipetteVal1; -} - -float EditDataProvider::getPipetteVal2() const -{ - return pipetteVal2; -} - -float EditDataProvider::getPipetteVal3() const -{ - return pipetteVal3; -} - -void EditDataProvider::setPipetteVal1(float newVal) -{ - pipetteVal1 = newVal; -} - -void EditDataProvider::setPipetteVal2(float newVal) -{ - pipetteVal2 = newVal; -} - -void EditDataProvider::setPipetteVal3(float newVal) -{ - pipetteVal3 = newVal; -} - -CursorShape EditDataProvider::getCursor(int objectID) const -{ - if (currSubscriber) { - currSubscriber->getCursor(objectID); - } - - return CSHandOpen; -} - -EditSubscriber* EditDataProvider::getCurrSubscriber() const -{ - return currSubscriber; -} - -EditDataProvider* EditSubscriber::getEditProvider() -{ - return provider; -} - -CursorShape EditSubscriber::getCursor(int objectID) const -{ - return CSHandOpen; -} - -bool EditSubscriber::mouseOver(int modifierKey) -{ - return false; -} - -bool EditSubscriber::button1Pressed(int modifierKey) -{ - return false; -} - -bool EditSubscriber::button1Released() -{ - return false; -} - -bool EditSubscriber::button2Pressed(int modifierKey) -{ - return false; -} - -bool EditSubscriber::button2Released() -{ - return false; -} - -bool EditSubscriber::button3Pressed(int modifierKey) -{ - return false; -} - -bool EditSubscriber::button3Released() -{ - return false; -} - -bool EditSubscriber::drag1(int modifierKey) -{ - return false; -} - -bool EditSubscriber::drag2(int modifierKey) -{ - return false; -} - -bool EditSubscriber::drag3(int modifierKey) -{ - return false; -} - -bool EditSubscriber::pick1(bool picked) -{ - return false; -} - -bool EditSubscriber::pick2(bool picked) -{ - return false; -} - -bool EditSubscriber::pick3(bool picked) -{ - return false; -} - -const std::vector& EditSubscriber::getVisibleGeometry() -{ - return visibleGeometry; -} - -const std::vector& EditSubscriber::getMouseOverGeometry() -{ - return mouseOverGeometry; -} - -int EditDataProvider::getPipetteRectSize() const -{ - return 8; // TODO: make a GUI -} +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 "editcallbacks.h" + +EditSubscriber::EditSubscriber (EditType editType) : + ID(EUID_None), + editingType(editType), + bufferType(BT_SINGLEPLANE_FLOAT), + provider(nullptr), + action(EditSubscriber::Action::NONE) +{} + +void EditSubscriber::setEditProvider(EditDataProvider *provider) +{ + this->provider = provider; +} + +void EditSubscriber::setEditID(EditUniqueID ID, BufferType buffType) +{ + this->ID = ID; + bufferType = buffType; +} + +bool EditSubscriber::isCurrentSubscriber() const +{ + //if (provider && provider->getCurrSubscriber()) + // return provider->getCurrSubscriber()->getEditID() == ID; + + if (provider) { + return provider->getCurrSubscriber() == this; + } + + return false; +} + +void EditSubscriber::subscribe() +{ + if (provider) { + provider->subscribe(this); + } +} + +void EditSubscriber::unsubscribe() +{ + if (provider) { + provider->unsubscribe(); + } +} + +void EditSubscriber::switchOffEditMode() +{ + unsubscribe(); +} + +EditUniqueID EditSubscriber::getEditID() const +{ + return ID; +} + +EditType EditSubscriber::getEditingType() const +{ + return editingType; +} + +BufferType EditSubscriber::getPipetteBufferType() const +{ + return bufferType; +} + +bool EditSubscriber::isDragging() const +{ + return action == EditSubscriber::Action::DRAGGING; +} + +bool EditSubscriber::isPicking() const +{ + return action == EditSubscriber::Action::PICKING; +} + +//-------------------------------------------------------------------------------------------------- + + +EditDataProvider::EditDataProvider() : + currSubscriber(nullptr), + object(0), + pipetteVal1(0.f), + pipetteVal2(0.f), + pipetteVal3(0.f), + posScreen(-1, -1), + posImage(-1, -1), + deltaScreen(0, 0), + deltaImage(0, 0), + deltaPrevScreen(0, 0), + deltaPrevImage(0, 0) +{} + +void EditDataProvider::subscribe(EditSubscriber *subscriber) +{ + if (currSubscriber) { + currSubscriber->switchOffEditMode(); + } + + currSubscriber = subscriber; +} + +void EditDataProvider::unsubscribe() +{ + currSubscriber = nullptr; +} + +void EditDataProvider::switchOffEditMode() +{ + if (currSubscriber) { + currSubscriber->switchOffEditMode (); + } +} + +int EditDataProvider::getObject() const +{ + return object; +} + +void EditDataProvider::setObject(int newObject) +{ + object = newObject; +} + +float EditDataProvider::getPipetteVal1() const +{ + return pipetteVal1; +} + +float EditDataProvider::getPipetteVal2() const +{ + return pipetteVal2; +} + +float EditDataProvider::getPipetteVal3() const +{ + return pipetteVal3; +} + +void EditDataProvider::setPipetteVal1(float newVal) +{ + pipetteVal1 = newVal; +} + +void EditDataProvider::setPipetteVal2(float newVal) +{ + pipetteVal2 = newVal; +} + +void EditDataProvider::setPipetteVal3(float newVal) +{ + pipetteVal3 = newVal; +} + +CursorShape EditDataProvider::getCursor(int objectID) const +{ + if (currSubscriber) { + currSubscriber->getCursor(objectID); + } + + return CSHandOpen; +} + +EditSubscriber* EditDataProvider::getCurrSubscriber() const +{ + return currSubscriber; +} + +EditDataProvider* EditSubscriber::getEditProvider() +{ + return provider; +} + +CursorShape EditSubscriber::getCursor(int objectID) const +{ + return CSHandOpen; +} + +bool EditSubscriber::mouseOver(int modifierKey) +{ + return false; +} + +bool EditSubscriber::button1Pressed(int modifierKey) +{ + return false; +} + +bool EditSubscriber::button1Released() +{ + return false; +} + +bool EditSubscriber::button2Pressed(int modifierKey) +{ + return false; +} + +bool EditSubscriber::button2Released() +{ + return false; +} + +bool EditSubscriber::button3Pressed(int modifierKey) +{ + return false; +} + +bool EditSubscriber::button3Released() +{ + return false; +} + +bool EditSubscriber::drag1(int modifierKey) +{ + return false; +} + +bool EditSubscriber::drag2(int modifierKey) +{ + return false; +} + +bool EditSubscriber::drag3(int modifierKey) +{ + return false; +} + +bool EditSubscriber::pick1(bool picked) +{ + return false; +} + +bool EditSubscriber::pick2(bool picked) +{ + return false; +} + +bool EditSubscriber::pick3(bool picked) +{ + return false; +} + +const std::vector& EditSubscriber::getVisibleGeometry() +{ + return visibleGeometry; +} + +const std::vector& EditSubscriber::getMouseOverGeometry() +{ + return mouseOverGeometry; +} + +int EditDataProvider::getPipetteRectSize() const +{ + return 8; // TODO: make a GUI +} diff --git a/rtgui/editcallbacks.h b/rtgui/editcallbacks.h index 842182d6d..c4003f9ca 100644 --- a/rtgui/editcallbacks.h +++ b/rtgui/editcallbacks.h @@ -1,195 +1,195 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Jean-Christophe FRISCH - * - * 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 . - */ -#pragma once - -#include "editid.h" -#include "cursormanager.h" -#include "../rtengine/coord.h" - -class Geometry; -class EditDataProvider; - -/** @file - * See editwidgets.h for documentation - */ - -/// @brief Method for client tools needing Edit information -class EditSubscriber -{ - -public: - -private: - EditUniqueID ID; /// this will be used in improcfun to locate the data that has to be stored in the buffer; it must be unique in RT - EditType editingType; - BufferType bufferType; - EditDataProvider *provider; - -protected: - std::vector visibleGeometry; /// displayed geometry - std::vector mouseOverGeometry; /// mouseOver geometry, drawn in a hidden buffer - enum class Action { - NONE, /// - DRAGGING, /// set action to this value in the buttonPressed event to start dragging and ask for drag event - PICKING /// set action to this value in the buttonPressed event whenever the user is picking something through a single click. In this case, the pickX events will be called INSTEAD of buttonXReleased ! - }; - - Action action; /// object mode only, ignored in Pipette mode - -public: - explicit EditSubscriber (EditType editType); - virtual ~EditSubscriber () = default; - - void setEditProvider(EditDataProvider *provider); - EditDataProvider* getEditProvider (); - void setEditID(EditUniqueID ID, BufferType buffType); - bool isCurrentSubscriber() const; - virtual void subscribe(); - virtual void unsubscribe(); - virtual void switchOffEditMode(); /// Occurs when the user want to stop the editing mode - EditUniqueID getEditID() const; - EditType getEditingType() const; - BufferType getPipetteBufferType() const; - bool isDragging() const; /// Returns true if something is being dragged and drag events has to be sent (object mode only) - bool isPicking() const; /// Returns true if something is being picked - - /** @brief Get the cursor to be displayed when above handles - @param objectID object currently "hovered" */ - virtual CursorShape getCursor (int objectID) const; - - /** @brief Triggered when the mouse is moving over an object - This method is also triggered when the cursor is moving over the image in ET_PIPETTE mode - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool mouseOver (int modifierKey); - - /** @brief Triggered when mouse button 1 is pressed, together with the CTRL modifier key if the subscriber is of type ET_PIPETTE - Once the key is pressed, RT will enter in drag1 mode on subsequent mouse movements - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button1Pressed (int modifierKey); - - /** @brief Triggered when mouse button 1 is released - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button1Released (); - - /** @brief Triggered when mouse button 2 is pressed (middle button) - Once the key is pressed, RT will enter in drag2 mode on subsequent mouse movements - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button2Pressed (int modifierKey); - - /** @brief Triggered when mouse button 2 is released (middle button) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button2Released (); - - /** @brief Triggered when mouse button 3 is pressed (right button) - Once the key is pressed, RT will enter in drag3 mode on subsequent mouse movements - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button3Pressed (int modifierKey); - - /** @brief Triggered when mouse button 3 is released (right button) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool button3Released (); - - /** @brief Triggered when the user is moving while holding down mouse button 1 - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool drag1 (int modifierKey); - - /** @brief Triggered when the user is moving while holding down mouse button 2 - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool drag2 (int modifierKey); - - /** @brief Triggered when the user is moving while holding down mouse button 3 - @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) - @return true if the preview has to be redrawn, false otherwise */ - virtual bool drag3 (int modifierKey); - - /** @brief Triggered when the user is releasing mouse button 1 while in action==ES_ACTION_PICKING mode - No modifier key is provided, since having a different modifier key than on button press will set picked to false. - @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. - If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. - @return true if the preview has to be redrawn, false otherwise */ - virtual bool pick1 (bool picked); - - /** @brief Triggered when the user is releasing mouse button 2 while in action==ES_ACTION_PICKING mode - @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. - If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. - @return true if the preview has to be redrawn, false otherwise */ - virtual bool pick2 (bool picked); - - /** @brief Triggered when the user is releasing mouse button 3 while in action==ES_ACTION_PICKING mode - @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. - If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. - @return true if the preview has to be redrawn, false otherwise */ - virtual bool pick3 (bool picked); - - /** @brief Get the geometry to be shown to the user */ - const std::vector& getVisibleGeometry (); - - /** @brief Get the geometry to be drawn in the "mouse over" channel, hidden from the user */ - const std::vector& getMouseOverGeometry (); -}; - -/** @brief Class to handle the furniture of data to the subscribers. - * - * It is admitted that only one Subscriber can ask data at a time. If the Subscriber is of type ET_PIPETTE, it will have to - * trigger the usual event so that the image will be reprocessed to compute the buffer of the current subscriber. - */ -class EditDataProvider -{ - -private: - EditSubscriber *currSubscriber; - int object; /// ET_OBJECTS mode: Object detected under the cursor, 0 otherwise; ET_PIPETTE mode: 1 if above the image, 0 otherwise - float pipetteVal1; /// Current pipette values - float pipetteVal2; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, will be set to 0 - float pipetteVal3; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, will be set to 0 - -public: - - rtengine::Coord posScreen; /// Location of the mouse button press, in preview image space - rtengine::Coord posImage; /// Location of the mouse button press, in the full image space - rtengine::Coord deltaScreen; /// Delta relative to posScreen - rtengine::Coord deltaImage; /// Delta relative to posImage - rtengine::Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space - rtengine::Coord deltaPrevImage; /// Delta relative to the previous mouse location, in the full image space - - EditDataProvider(); - virtual ~EditDataProvider() = default; - - virtual void subscribe(EditSubscriber *subscriber); - virtual void unsubscribe(); /// Occurs when the subscriber has been switched off first - virtual void switchOffEditMode (); /// Occurs when the user want to stop the editing mode - int getObject() const; - void setObject(int newObject); - float getPipetteVal1() const; - float getPipetteVal2() const; - float getPipetteVal3() const; - void setPipetteVal1(float newVal); - void setPipetteVal2(float newVal); - void setPipetteVal3(float newVal); - virtual CursorShape getCursor(int objectID) const; - int getPipetteRectSize () const; - EditSubscriber* getCurrSubscriber() const; - virtual void getImageSize (int &w, int&h) = 0; -}; +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 . + */ +#pragma once + +#include "editid.h" +#include "cursormanager.h" +#include "../rtengine/coord.h" + +class Geometry; +class EditDataProvider; + +/** @file + * See editwidgets.h for documentation + */ + +/// @brief Method for client tools needing Edit information +class EditSubscriber +{ + +public: + +private: + EditUniqueID ID; /// this will be used in improcfun to locate the data that has to be stored in the buffer; it must be unique in RT + EditType editingType; + BufferType bufferType; + EditDataProvider *provider; + +protected: + std::vector visibleGeometry; /// displayed geometry + std::vector mouseOverGeometry; /// mouseOver geometry, drawn in a hidden buffer + enum class Action { + NONE, /// + DRAGGING, /// set action to this value in the buttonPressed event to start dragging and ask for drag event + PICKING /// set action to this value in the buttonPressed event whenever the user is picking something through a single click. In this case, the pickX events will be called INSTEAD of buttonXReleased ! + }; + + Action action; /// object mode only, ignored in Pipette mode + +public: + explicit EditSubscriber (EditType editType); + virtual ~EditSubscriber () = default; + + void setEditProvider(EditDataProvider *provider); + EditDataProvider* getEditProvider (); + void setEditID(EditUniqueID ID, BufferType buffType); + bool isCurrentSubscriber() const; + virtual void subscribe(); + virtual void unsubscribe(); + virtual void switchOffEditMode(); /// Occurs when the user want to stop the editing mode + EditUniqueID getEditID() const; + EditType getEditingType() const; + BufferType getPipetteBufferType() const; + bool isDragging() const; /// Returns true if something is being dragged and drag events has to be sent (object mode only) + bool isPicking() const; /// Returns true if something is being picked + + /** @brief Get the cursor to be displayed when above handles + @param objectID object currently "hovered" */ + virtual CursorShape getCursor (int objectID) const; + + /** @brief Triggered when the mouse is moving over an object + This method is also triggered when the cursor is moving over the image in ET_PIPETTE mode + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool mouseOver (int modifierKey); + + /** @brief Triggered when mouse button 1 is pressed, together with the CTRL modifier key if the subscriber is of type ET_PIPETTE + Once the key is pressed, RT will enter in drag1 mode on subsequent mouse movements + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button1Pressed (int modifierKey); + + /** @brief Triggered when mouse button 1 is released + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button1Released (); + + /** @brief Triggered when mouse button 2 is pressed (middle button) + Once the key is pressed, RT will enter in drag2 mode on subsequent mouse movements + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button2Pressed (int modifierKey); + + /** @brief Triggered when mouse button 2 is released (middle button) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button2Released (); + + /** @brief Triggered when mouse button 3 is pressed (right button) + Once the key is pressed, RT will enter in drag3 mode on subsequent mouse movements + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button3Pressed (int modifierKey); + + /** @brief Triggered when mouse button 3 is released (right button) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool button3Released (); + + /** @brief Triggered when the user is moving while holding down mouse button 1 + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool drag1 (int modifierKey); + + /** @brief Triggered when the user is moving while holding down mouse button 2 + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool drag2 (int modifierKey); + + /** @brief Triggered when the user is moving while holding down mouse button 3 + @param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...) + @return true if the preview has to be redrawn, false otherwise */ + virtual bool drag3 (int modifierKey); + + /** @brief Triggered when the user is releasing mouse button 1 while in action==ES_ACTION_PICKING mode + No modifier key is provided, since having a different modifier key than on button press will set picked to false. + @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. + If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. + @return true if the preview has to be redrawn, false otherwise */ + virtual bool pick1 (bool picked); + + /** @brief Triggered when the user is releasing mouse button 2 while in action==ES_ACTION_PICKING mode + @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. + If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. + @return true if the preview has to be redrawn, false otherwise */ + virtual bool pick2 (bool picked); + + /** @brief Triggered when the user is releasing mouse button 3 while in action==ES_ACTION_PICKING mode + @param picked True if the cursor is still above the the same object than on button pressed and with the same modifier keys. + If false, the user moved the cursor away or the modifier key is different, so the element is considered as NOT selected. + @return true if the preview has to be redrawn, false otherwise */ + virtual bool pick3 (bool picked); + + /** @brief Get the geometry to be shown to the user */ + const std::vector& getVisibleGeometry (); + + /** @brief Get the geometry to be drawn in the "mouse over" channel, hidden from the user */ + const std::vector& getMouseOverGeometry (); +}; + +/** @brief Class to handle the furniture of data to the subscribers. + * + * It is admitted that only one Subscriber can ask data at a time. If the Subscriber is of type ET_PIPETTE, it will have to + * trigger the usual event so that the image will be reprocessed to compute the buffer of the current subscriber. + */ +class EditDataProvider +{ + +private: + EditSubscriber *currSubscriber; + int object; /// ET_OBJECTS mode: Object detected under the cursor, 0 otherwise; ET_PIPETTE mode: 1 if above the image, 0 otherwise + float pipetteVal1; /// Current pipette values + float pipetteVal2; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, will be set to 0 + float pipetteVal3; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, will be set to 0 + +public: + + rtengine::Coord posScreen; /// Location of the mouse button press, in preview image space + rtengine::Coord posImage; /// Location of the mouse button press, in the full image space + rtengine::Coord deltaScreen; /// Delta relative to posScreen + rtengine::Coord deltaImage; /// Delta relative to posImage + rtengine::Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space + rtengine::Coord deltaPrevImage; /// Delta relative to the previous mouse location, in the full image space + + EditDataProvider(); + virtual ~EditDataProvider() = default; + + virtual void subscribe(EditSubscriber *subscriber); + virtual void unsubscribe(); /// Occurs when the subscriber has been switched off first + virtual void switchOffEditMode (); /// Occurs when the user want to stop the editing mode + int getObject() const; + void setObject(int newObject); + float getPipetteVal1() const; + float getPipetteVal2() const; + float getPipetteVal3() const; + void setPipetteVal1(float newVal); + void setPipetteVal2(float newVal); + void setPipetteVal3(float newVal); + virtual CursorShape getCursor(int objectID) const; + int getPipetteRectSize () const; + EditSubscriber* getCurrSubscriber() const; + virtual void getImageSize (int &w, int&h) = 0; +}; diff --git a/rtgui/editcoordsys.h b/rtgui/editcoordsys.h index 1af0f5e37..829225e3f 100644 --- a/rtgui/editcoordsys.h +++ b/rtgui/editcoordsys.h @@ -1,61 +1,61 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Jean-Christophe FRISCH - * - * 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 . - */ - -/** @file - * See editwidgets.h for documentation - */ - - /** @brief Coordinate system where the widgets will be drawn - * - * The EditCoordSystem is used to define a screen and an image coordinate system. - */ -#pragma once - - - class EditCoordSystem - { - public: - virtual ~EditCoordSystem() {} - - /// Convert the widget's DrawingArea (i.e. preview area) coords to the edit buffer coords - virtual void screenCoordToCropBuffer (int phyx, int phyy, int& cropx, int& cropy) = 0; - /// Convert the widget's DrawingArea (i.e. preview area) coords to the full image coords - virtual void screenCoordToImage (int phyx, int phyy, int& imgx, int& imgy) = 0; - /// Convert the image coords to the widget's DrawingArea (i.e. preview area) coords - virtual void imageCoordToScreen (int imgx, int imgy, int& phyx, int& phyy) = 0; - /// Convert the image coords to the crop's canvas coords (full image + padding) - virtual void imageCoordToCropCanvas (int imgx, int imgy, int& phyx, int& phyy) = 0; - /// Convert the image coords to the edit buffer coords (includes borders) - virtual void imageCoordToCropBuffer (int imgx, int imgy, int& phyx, int& phyy) = 0; - /// Convert the image coords to the displayed image coords (no borders here) - virtual void imageCoordToCropImage (int imgx, int imgy, int& phyx, int& phyy) = 0; - /// Convert a size value from the preview's scale to the image's scale - virtual int scaleValueToImage (int value) = 0; - /// Convert a size value from the preview's scale to the image's scale - virtual float scaleValueToImage (float value) = 0; - /// Convert a size value from the preview's scale to the image's scale - virtual double scaleValueToImage (double value) = 0; - /// Convert a size value from the image's scale to the preview's scale - virtual int scaleValueToCanvas (int value) = 0; - /// Convert a size value from the image's scale to the preview's scale - virtual float scaleValueToCanvas (float value) = 0; - /// Convert a size value from the image's scale to the preview's scale - virtual double scaleValueToCanvas (double value) = 0; - }; - +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 . + */ + +/** @file + * See editwidgets.h for documentation + */ + + /** @brief Coordinate system where the widgets will be drawn + * + * The EditCoordSystem is used to define a screen and an image coordinate system. + */ +#pragma once + + + class EditCoordSystem + { + public: + virtual ~EditCoordSystem() {} + + /// Convert the widget's DrawingArea (i.e. preview area) coords to the edit buffer coords + virtual void screenCoordToCropBuffer (int phyx, int phyy, int& cropx, int& cropy) = 0; + /// Convert the widget's DrawingArea (i.e. preview area) coords to the full image coords + virtual void screenCoordToImage (int phyx, int phyy, int& imgx, int& imgy) = 0; + /// Convert the image coords to the widget's DrawingArea (i.e. preview area) coords + virtual void imageCoordToScreen (int imgx, int imgy, int& phyx, int& phyy) = 0; + /// Convert the image coords to the crop's canvas coords (full image + padding) + virtual void imageCoordToCropCanvas (int imgx, int imgy, int& phyx, int& phyy) = 0; + /// Convert the image coords to the edit buffer coords (includes borders) + virtual void imageCoordToCropBuffer (int imgx, int imgy, int& phyx, int& phyy) = 0; + /// Convert the image coords to the displayed image coords (no borders here) + virtual void imageCoordToCropImage (int imgx, int imgy, int& phyx, int& phyy) = 0; + /// Convert a size value from the preview's scale to the image's scale + virtual int scaleValueToImage (int value) = 0; + /// Convert a size value from the preview's scale to the image's scale + virtual float scaleValueToImage (float value) = 0; + /// Convert a size value from the preview's scale to the image's scale + virtual double scaleValueToImage (double value) = 0; + /// Convert a size value from the image's scale to the preview's scale + virtual int scaleValueToCanvas (int value) = 0; + /// Convert a size value from the image's scale to the preview's scale + virtual float scaleValueToCanvas (float value) = 0; + /// Convert a size value from the image's scale to the preview's scale + virtual double scaleValueToCanvas (double value) = 0; + }; + diff --git a/rtgui/editwidgets.cc b/rtgui/editwidgets.cc index 129d81bc7..16cd67cd4 100644 --- a/rtgui/editwidgets.cc +++ b/rtgui/editwidgets.cc @@ -1,935 +1,935 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Jean-Christophe FRISCH - * - * 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 "editwidgets.h" -#include "editbuffer.h" -#include "editcallbacks.h" -#include "../rtengine/rt_math.h" - -RGBColor Geometry::getInnerLineColor () -{ - RGBColor color; - - if (flags & F_AUTO_COLOR) { - if (state == NORMAL) { - color.setColor (1., 1., 1.); // White - } else if (state == ACTIVE) { - color.setColor (1., 1., 0.); // Yellow - } else if (state == PRELIGHT) { - color.setColor (1., 100. / 255., 0.); // Orange - } else if (state == DRAGGED) { - color.setColor (1., 0., 0.); // Red - } - } else { - color = innerLineColor; - } - - return color; -} - -RGBColor Geometry::getOuterLineColor () -{ - RGBColor color; - - if (flags & F_AUTO_COLOR) { - /* - if (state == NORMAL) { color.setColor (0., 0., 0.); } // Black - else if (state == PRELIGHT) { color.setColor (0., 0., 0.); } // Black - else if (state == DRAGGED) { color.setColor (1., 0., 0.); } // Black - */ - color.setColor (0., 0., 0.); // Black - } else { - color = outerLineColor; - } - - return color; -} - -#ifdef GUIVERSION - -void Circle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getOuterLineColor(); - } else { - color = outerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); - - rtengine::Coord center_ = center; - double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (center.x, center.y, center_.x, center_.y); - } else if (datum == CLICKED_POINT) { - center_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); - cr->stroke(); - } -} - -void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_VISIBLE) { - if (state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getInnerLineColor(); - } else { - color = innerLineColor; - } - - cr->set_source_rgb(color.getR(), color.getG(), color.getB()); - } - - cr->set_line_width( innerLineWidth ); - - rtengine::Coord center_ = center; - double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (center.x, center.y, center_.x, center_.y); - } else if (datum == CLICKED_POINT) { - center_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (filled && state != INSENSITIVE) { - cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); - - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else if (innerLineWidth > 0.) { - cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); - - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } - } - } -} - -void Circle::drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_HOVERABLE) { - cr->set_line_width( getMouseOverLineWidth() ); - rtengine::Coord center_ = center; - double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); - - if (datum == IMAGE) { - coordSystem.imageCoordToCropCanvas (center.x, center.y, center_.x, center_.y); - } else if (datum == CLICKED_POINT) { - center_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - // setting the color to the objet's ID - if (objectBuffer->getObjectMode() == OM_255) { - cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); - } else { - cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); - } - cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0, 2.*rtengine::RT_PI); - - if (filled) { - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else { - cr->stroke(); - } - } -} - -void Line::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getOuterLineColor(); - } else { - color = outerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); - - rtengine::Coord begin_ = begin; - rtengine::Coord end_ = end; - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (begin.x, begin.y, begin_.x, begin_.y); - coordSystem.imageCoordToScreen (end.x, end.y, end_.x, end_.y); - } else if (datum == CLICKED_POINT) { - begin_ += objectBuffer->getDataProvider()->posScreen; - end_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - cr->move_to(begin_.x + 0.5, begin_.y + 0.5); - cr->line_to(end_.x + 0.5, end_.y + 0.5); - cr->stroke(); - } -} - -void Line::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && innerLineWidth > 0.) { - if (state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getInnerLineColor(); - } else { - color = innerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - } - - cr->set_line_width(innerLineWidth); - - rtengine::Coord begin_ = begin; - rtengine::Coord end_ = end; - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (begin.x, begin.y, begin_.x, begin_.y); - coordSystem.imageCoordToScreen (end.x, end.y, end_.x, end_.y); - } else if (datum == CLICKED_POINT) { - begin_ += objectBuffer->getDataProvider()->posScreen; - end_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - cr->move_to(begin_.x + 0.5, begin_.y + 0.5); - cr->line_to(end_.x + 0.5, end_.y + 0.5); - - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } - } -} - -void Line::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_HOVERABLE) { - cr->set_line_width( getMouseOverLineWidth() ); - rtengine::Coord begin_ = begin; - rtengine::Coord end_ = end; - - if (datum == IMAGE) { - coordSystem.imageCoordToCropCanvas (begin.x, begin.y, begin_.x, begin_.y); - coordSystem.imageCoordToCropCanvas (end.x, end.y, end_.x, end_.y); - } else if (datum == CLICKED_POINT) { - begin_ += objectBuffer->getDataProvider()->posScreen; - end_ += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - // setting the color to the objet's ID - if (objectBuffer->getObjectMode() == OM_255) { - cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); - } else { - cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); - } - cr->move_to(begin_.x + 0.5, begin_.y + 0.5); - cr->line_to(end_.x + 0.5, end_.y + 0.5); - cr->stroke(); - } -} - -void Polyline::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && state != INSENSITIVE && points.size() > 1) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getOuterLineColor(); - } else { - color = outerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); - - rtengine::Coord currPos; - - for (unsigned int i = 0; i < points.size(); ++i) { - currPos = points.at(i); - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); - } else if (datum == CLICKED_POINT) { - currPos += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (!i) { - cr->move_to(currPos.x + 0.5, currPos.y + 0.5); - } else { - cr->line_to(currPos.x + 0.5, currPos.y + 0.5); - } - } - - if (filled) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->stroke(); - } - } -} - -void Polyline::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && points.size() > 1) { - if (state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getInnerLineColor(); - } else { - color = innerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - } - - cr->set_line_width( innerLineWidth ); - - if (filled && state != INSENSITIVE) { - rtengine::Coord currPos; - - for (unsigned int i = 0; i < points.size(); ++i) { - currPos = points.at(i); - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); - } else if (datum == CLICKED_POINT) { - currPos += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (!i) { - cr->move_to(currPos.x + 0.5, currPos.y + 0.5); - } else { - cr->line_to(currPos.x + 0.5, currPos.y + 0.5); - } - } - - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else if (innerLineWidth > 0.) { - rtengine::Coord currPos; - - for (unsigned int i = 0; i < points.size(); ++i) { - currPos = points.at(i); - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); - } else if (datum == CLICKED_POINT) { - currPos += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (!i) { - cr->move_to(currPos.x + 0.5, currPos.y + 0.5); - } else { - cr->line_to(currPos.x + 0.5, currPos.y + 0.5); - } - } - - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } - } - } -} - -void Polyline::drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_HOVERABLE) && points.size() > 1) { - rtengine::Coord currPos; - - // setting the color to the objet's ID - if (objectBuffer->getObjectMode() == OM_255) { - cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); - } else { - cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); - } - - for (unsigned int i = 0; i < points.size(); ++i) { - cr->set_line_width( getMouseOverLineWidth() ); - currPos = points.at(i); - - if (datum == IMAGE) { - coordSystem.imageCoordToCropCanvas (points.at(i).x, points.at(i).y, currPos.x, currPos.y); - } else if (datum == CLICKED_POINT) { - currPos += objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (!i) { - cr->move_to(currPos.x + 0.5, currPos.y + 0.5); - } else { - cr->line_to(currPos.x + 0.5, currPos.y + 0.5); - } - } - - if (filled) { - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else { - cr->stroke(); - } - } -} - -void Rectangle::setXYWH(int left, int top, int width, int height) -{ - topLeft.set(left, top); - bottomRight.set(left + width, top + height); -} - -void Rectangle::setXYXY(int left, int top, int right, int bottom) -{ - topLeft.set(left, top); - bottomRight.set(right, bottom); -} - -void Rectangle::setXYWH(rtengine::Coord topLeft, rtengine::Coord widthHeight) -{ - this->topLeft = topLeft; - this->bottomRight = topLeft + widthHeight; -} - -void Rectangle::setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight) -{ - this->topLeft = topLeft; - this->bottomRight = bottomRight; -} - -void Rectangle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if ((flags & F_VISIBLE) && state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getOuterLineColor(); - } else { - color = outerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); - - rtengine::Coord tl, br; - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (topLeft.x, topLeft.y, tl.x, tl.y); - } else if (datum == CLICKED_POINT) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (bottomRight.x, bottomRight.y, br.x, br.y); - } else if (datum == CLICKED_POINT) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); - - if (filled) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->stroke(); - } - } -} - -void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_VISIBLE) { - if (state != INSENSITIVE) { - RGBColor color; - - if (flags & F_AUTO_COLOR) { - color = getInnerLineColor(); - } else { - color = innerLineColor; - } - - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - } - - cr->set_line_width( innerLineWidth ); - - rtengine::Coord tl, br; - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (topLeft.x, topLeft.y, tl.x, tl.y); - } else if (datum == CLICKED_POINT) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen (bottomRight.x, bottomRight.y, br.x, br.y); - } else if (datum == CLICKED_POINT) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (filled && state != INSENSITIVE) { - cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); - - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else if (innerLineWidth > 0.) { - cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); - - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } - } - } -} - -void Rectangle::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_HOVERABLE) { - cr->set_line_width( getMouseOverLineWidth() ); - - rtengine::Coord tl, br; - - if (datum == IMAGE) { - coordSystem.imageCoordToCropCanvas (topLeft.x, topLeft.y, tl.x, tl.y); - } else if (datum == CLICKED_POINT) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - if (datum == IMAGE) { - coordSystem.imageCoordToCropCanvas (bottomRight.x, bottomRight.y, br.x, br.y); - } else if (datum == CLICKED_POINT) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) { - br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; - } - - // setting the color to the objet's ID - if (objectBuffer->getObjectMode() == OM_255) { - cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); - } else { - cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); - } - cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); - - if (filled) { - if (innerLineWidth > 0.) { - cr->fill_preserve(); - cr->stroke(); - } else { - cr->fill(); - } - } else { - cr->stroke(); - } - } -} - -void OPIcon::drivenPointToRectangle(const rtengine::Coord &pos, - rtengine::Coord &topLeft, rtengine::Coord &bottomRight, int W, int H) -{ - switch (drivenPoint) { - case (DP_CENTERCENTER): - topLeft.x = pos.x - W / 2; - topLeft.y = pos.y - H / 2; - break; - - case (DP_TOPLEFT): - topLeft.x = pos.x; - topLeft.y = pos.y; - break; - - case (DP_TOPCENTER): - topLeft.x = pos.x - W / 2; - topLeft.y = pos.y; - break; - - case (DP_TOPRIGHT): - topLeft.x = pos.x - W; - topLeft.y = pos.y; - break; - - case (DP_CENTERRIGHT): - topLeft.x = pos.x - W; - topLeft.y = pos.y - H / 2; - break; - - case (DP_BOTTOMRIGHT): - topLeft.x = pos.x - W; - topLeft.y = pos.y - H; - break; - - case (DP_BOTTOMCENTER): - topLeft.x = pos.x - W / 2; - topLeft.y = pos.y - H; - break; - - case (DP_BOTTOMLEFT): - topLeft.x = pos.x; - topLeft.y = pos.y - H; - break; - - case (DP_CENTERLEFT): - topLeft.x = pos.x; - topLeft.y = pos.y - H / 2; - break; - } - - bottomRight.x = topLeft.x + W - 1; - bottomRight.y = topLeft.y + H - 1; -} - -OPIcon::OPIcon(const Cairo::RefPtr &normal, - const Cairo::RefPtr &active, - const Cairo::RefPtr &prelight, - const Cairo::RefPtr &dragged, - const Cairo::RefPtr &insensitive, - DrivenPoint drivenPoint) : - drivenPoint(drivenPoint) -{ - if (normal) { - normalImg = normal; - } - - if (prelight) { - prelightImg = prelight; - } - - if (active) { - activeImg = active; - } - - if (dragged) { - draggedImg = dragged; - } - - if (insensitive) { - insensitiveImg = insensitive; - } -} - -OPIcon::OPIcon(Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustring prelightImage, - Glib::ustring draggedImage, Glib::ustring insensitiveImage, DrivenPoint drivenPoint) : drivenPoint(drivenPoint) -{ - if (!normalImage.empty()) { - normalImg = Cairo::RefPtr(new RTSurface(normalImage)); - } - - if (!prelightImage.empty()) { - prelightImg = Cairo::RefPtr(new RTSurface(prelightImage)); - } - - if (!activeImage.empty()) { - activeImg = Cairo::RefPtr(new RTSurface(activeImage)); - } - - if (!draggedImage.empty()) { - draggedImg = Cairo::RefPtr(new RTSurface(draggedImage)); - } - - if (!insensitiveImage.empty()) { - insensitiveImg = Cairo::RefPtr(new RTSurface(insensitiveImage)); - } -} - -const Cairo::RefPtr OPIcon::getNormalImg() -{ - return normalImg; -} -const Cairo::RefPtr OPIcon::getPrelightImg() -{ - return prelightImg; -} -const Cairo::RefPtr OPIcon::getActiveImg() -{ - return activeImg; -} -const Cairo::RefPtr OPIcon::getDraggedImg() -{ - return draggedImg; -} -const Cairo::RefPtr OPIcon::getInsensitiveImg() -{ - return insensitiveImg; -} - -void OPIcon::drawImage(Cairo::RefPtr &img, - Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, - EditCoordSystem &coordSystem) -{ - int imgW = img->getWidth(); - int imgH = img->getHeight(); - - rtengine::Coord pos; - - if (datum == IMAGE) { - coordSystem.imageCoordToScreen(position.x, position.y, pos.x, pos.y); - } else if (datum == CLICKED_POINT) { - pos = position + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) - pos = position + objectBuffer->getDataProvider()->posScreen - + objectBuffer->getDataProvider()->deltaScreen; - - rtengine::Coord tl, br; // Coordinate of the rectangle in the CropBuffer coordinate system - drivenPointToRectangle(pos, tl, br, imgW, imgH); - - cr->set_source(img->get(), tl.x, tl.y); - cr->set_line_width(0.); - cr->rectangle(tl.x, tl.y, imgW, imgH); - cr->fill(); -} - -void OPIcon::drawMOImage(Cairo::RefPtr &img, Cairo::RefPtr &cr, - unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - // test of F_HOVERABLE has already been done - - int imgW = img->getWidth(); - int imgH = img->getHeight(); - - rtengine::Coord pos; - - if (datum == IMAGE) - coordSystem.imageCoordToCropCanvas (position.x, position.y, pos.x, pos.y); - else if (datum == CLICKED_POINT) { - pos = position + objectBuffer->getDataProvider()->posScreen; - } else if (datum == CURSOR) - pos = position + objectBuffer->getDataProvider()->posScreen - + objectBuffer->getDataProvider()->deltaScreen; - - rtengine::Coord tl, br; // Coordinate of the rectangle in the CropBuffer coordinate system - drivenPointToRectangle(pos, tl, br, imgW, imgH); - - // drawing the lower byte's value - if (objectBuffer->getObjectMode() == OM_255) { - cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); - } else { - cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); - } - cr->set_line_width(0.); - cr->rectangle(tl.x, tl.y, imgW, imgH); - cr->fill(); -} - -void OPIcon::drawOuterGeometry(Cairo::RefPtr &cr, - ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) {} - -void OPIcon::drawInnerGeometry(Cairo::RefPtr &cr, - ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_VISIBLE) { - // Here we will handle fall-back solutions - - State tmpState = state; // can be updated through the successive test - - if (tmpState == INSENSITIVE) { - if (!insensitiveImg) { - tmpState = NORMAL; - } else { - OPIcon::drawImage(insensitiveImg, cr, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == DRAGGED) { - if (!draggedImg) { - tmpState = ACTIVE; - } else { - OPIcon::drawImage(draggedImg, cr, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == ACTIVE) { - if (!activeImg) { - tmpState = PRELIGHT; - } else { - OPIcon::drawImage(activeImg, cr, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == PRELIGHT) { - if (!prelightImg) { - tmpState = NORMAL; - } else { - OPIcon::drawImage(prelightImg, cr, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == NORMAL && normalImg) { - OPIcon::drawImage(normalImg, cr, objectBuffer, coordSystem); - } - } -} - -void OPIcon::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, - ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) -{ - if (flags & F_HOVERABLE) { - // Here we will handle fallback solutions - State tmpState = state; - - if (tmpState == INSENSITIVE) { - if (!insensitiveImg) { - tmpState = NORMAL; - } else { - OPIcon::drawMOImage(insensitiveImg, cr, id, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == DRAGGED) { - if (!draggedImg) { - tmpState = ACTIVE; - } else { - OPIcon::drawMOImage(draggedImg, cr, id, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == ACTIVE) { - if (!activeImg) { - tmpState = PRELIGHT; - } else { - OPIcon::drawMOImage(activeImg, cr, id, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == PRELIGHT) { - if (!prelightImg) { - tmpState = NORMAL; - } else { - OPIcon::drawMOImage(prelightImg, cr, id, objectBuffer, coordSystem); - return; - } - } - - if (tmpState == NORMAL && normalImg) { - OPIcon::drawMOImage(normalImg, cr, id, objectBuffer, coordSystem); - } - } -} - -#endif +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 "editwidgets.h" +#include "editbuffer.h" +#include "editcallbacks.h" +#include "../rtengine/rt_math.h" + +RGBColor Geometry::getInnerLineColor () +{ + RGBColor color; + + if (flags & F_AUTO_COLOR) { + if (state == NORMAL) { + color.setColor (1., 1., 1.); // White + } else if (state == ACTIVE) { + color.setColor (1., 1., 0.); // Yellow + } else if (state == PRELIGHT) { + color.setColor (1., 100. / 255., 0.); // Orange + } else if (state == DRAGGED) { + color.setColor (1., 0., 0.); // Red + } + } else { + color = innerLineColor; + } + + return color; +} + +RGBColor Geometry::getOuterLineColor () +{ + RGBColor color; + + if (flags & F_AUTO_COLOR) { + /* + if (state == NORMAL) { color.setColor (0., 0., 0.); } // Black + else if (state == PRELIGHT) { color.setColor (0., 0., 0.); } // Black + else if (state == DRAGGED) { color.setColor (1., 0., 0.); } // Black + */ + color.setColor (0., 0., 0.); // Black + } else { + color = outerLineColor; + } + + return color; +} + +#ifdef GUIVERSION + +void Circle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getOuterLineColor(); + } else { + color = outerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_line_width( getOuterLineWidth() ); + + rtengine::Coord center_ = center; + double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (center.x, center.y, center_.x, center_.y); + } else if (datum == CLICKED_POINT) { + center_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); + cr->stroke(); + } +} + +void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_VISIBLE) { + if (state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getInnerLineColor(); + } else { + color = innerLineColor; + } + + cr->set_source_rgb(color.getR(), color.getG(), color.getB()); + } + + cr->set_line_width( innerLineWidth ); + + rtengine::Coord center_ = center; + double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (center.x, center.y, center_.x, center_.y); + } else if (datum == CLICKED_POINT) { + center_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (filled && state != INSENSITIVE) { + cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); + + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else if (innerLineWidth > 0.) { + cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); + + if (state == INSENSITIVE) { + std::valarray ds(1); + ds[0] = 4; + cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); + cr->stroke_preserve(); + cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); + cr->set_dash(ds, 0); + cr->stroke(); + ds.resize(0); + cr->set_dash(ds, 0); + } else { + cr->stroke(); + } + } + } +} + +void Circle::drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_HOVERABLE) { + cr->set_line_width( getMouseOverLineWidth() ); + rtengine::Coord center_ = center; + double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); + + if (datum == IMAGE) { + coordSystem.imageCoordToCropCanvas (center.x, center.y, center_.x, center_.y); + } else if (datum == CLICKED_POINT) { + center_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + // setting the color to the objet's ID + if (objectBuffer->getObjectMode() == OM_255) { + cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); + } else { + cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); + } + cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0, 2.*rtengine::RT_PI); + + if (filled) { + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else { + cr->stroke(); + } + } +} + +void Line::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getOuterLineColor(); + } else { + color = outerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_line_width( getOuterLineWidth() ); + + rtengine::Coord begin_ = begin; + rtengine::Coord end_ = end; + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (begin.x, begin.y, begin_.x, begin_.y); + coordSystem.imageCoordToScreen (end.x, end.y, end_.x, end_.y); + } else if (datum == CLICKED_POINT) { + begin_ += objectBuffer->getDataProvider()->posScreen; + end_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + cr->move_to(begin_.x + 0.5, begin_.y + 0.5); + cr->line_to(end_.x + 0.5, end_.y + 0.5); + cr->stroke(); + } +} + +void Line::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && innerLineWidth > 0.) { + if (state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getInnerLineColor(); + } else { + color = innerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + } + + cr->set_line_width(innerLineWidth); + + rtengine::Coord begin_ = begin; + rtengine::Coord end_ = end; + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (begin.x, begin.y, begin_.x, begin_.y); + coordSystem.imageCoordToScreen (end.x, end.y, end_.x, end_.y); + } else if (datum == CLICKED_POINT) { + begin_ += objectBuffer->getDataProvider()->posScreen; + end_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + cr->move_to(begin_.x + 0.5, begin_.y + 0.5); + cr->line_to(end_.x + 0.5, end_.y + 0.5); + + if (state == INSENSITIVE) { + std::valarray ds(1); + ds[0] = 4; + cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); + cr->stroke_preserve(); + cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); + cr->set_dash(ds, 0); + cr->stroke(); + ds.resize(0); + cr->set_dash(ds, 0); + } else { + cr->stroke(); + } + } +} + +void Line::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_HOVERABLE) { + cr->set_line_width( getMouseOverLineWidth() ); + rtengine::Coord begin_ = begin; + rtengine::Coord end_ = end; + + if (datum == IMAGE) { + coordSystem.imageCoordToCropCanvas (begin.x, begin.y, begin_.x, begin_.y); + coordSystem.imageCoordToCropCanvas (end.x, end.y, end_.x, end_.y); + } else if (datum == CLICKED_POINT) { + begin_ += objectBuffer->getDataProvider()->posScreen; + end_ += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + begin_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + // setting the color to the objet's ID + if (objectBuffer->getObjectMode() == OM_255) { + cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); + } else { + cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); + } + cr->move_to(begin_.x + 0.5, begin_.y + 0.5); + cr->line_to(end_.x + 0.5, end_.y + 0.5); + cr->stroke(); + } +} + +void Polyline::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && state != INSENSITIVE && points.size() > 1) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getOuterLineColor(); + } else { + color = outerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_line_width( getOuterLineWidth() ); + + rtengine::Coord currPos; + + for (unsigned int i = 0; i < points.size(); ++i) { + currPos = points.at(i); + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); + } else if (datum == CLICKED_POINT) { + currPos += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (!i) { + cr->move_to(currPos.x + 0.5, currPos.y + 0.5); + } else { + cr->line_to(currPos.x + 0.5, currPos.y + 0.5); + } + } + + if (filled) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->stroke(); + } + } +} + +void Polyline::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && points.size() > 1) { + if (state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getInnerLineColor(); + } else { + color = innerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + } + + cr->set_line_width( innerLineWidth ); + + if (filled && state != INSENSITIVE) { + rtengine::Coord currPos; + + for (unsigned int i = 0; i < points.size(); ++i) { + currPos = points.at(i); + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); + } else if (datum == CLICKED_POINT) { + currPos += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (!i) { + cr->move_to(currPos.x + 0.5, currPos.y + 0.5); + } else { + cr->line_to(currPos.x + 0.5, currPos.y + 0.5); + } + } + + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else if (innerLineWidth > 0.) { + rtengine::Coord currPos; + + for (unsigned int i = 0; i < points.size(); ++i) { + currPos = points.at(i); + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (points.at(i).x, points.at(i).y, currPos.x, currPos.y); + } else if (datum == CLICKED_POINT) { + currPos += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (!i) { + cr->move_to(currPos.x + 0.5, currPos.y + 0.5); + } else { + cr->line_to(currPos.x + 0.5, currPos.y + 0.5); + } + } + + if (state == INSENSITIVE) { + std::valarray ds(1); + ds[0] = 4; + cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); + cr->stroke_preserve(); + cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); + cr->set_dash(ds, 0); + cr->stroke(); + ds.resize(0); + cr->set_dash(ds, 0); + } else { + cr->stroke(); + } + } + } +} + +void Polyline::drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_HOVERABLE) && points.size() > 1) { + rtengine::Coord currPos; + + // setting the color to the objet's ID + if (objectBuffer->getObjectMode() == OM_255) { + cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); + } else { + cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); + } + + for (unsigned int i = 0; i < points.size(); ++i) { + cr->set_line_width( getMouseOverLineWidth() ); + currPos = points.at(i); + + if (datum == IMAGE) { + coordSystem.imageCoordToCropCanvas (points.at(i).x, points.at(i).y, currPos.x, currPos.y); + } else if (datum == CLICKED_POINT) { + currPos += objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + currPos += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (!i) { + cr->move_to(currPos.x + 0.5, currPos.y + 0.5); + } else { + cr->line_to(currPos.x + 0.5, currPos.y + 0.5); + } + } + + if (filled) { + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else { + cr->stroke(); + } + } +} + +void Rectangle::setXYWH(int left, int top, int width, int height) +{ + topLeft.set(left, top); + bottomRight.set(left + width, top + height); +} + +void Rectangle::setXYXY(int left, int top, int right, int bottom) +{ + topLeft.set(left, top); + bottomRight.set(right, bottom); +} + +void Rectangle::setXYWH(rtengine::Coord topLeft, rtengine::Coord widthHeight) +{ + this->topLeft = topLeft; + this->bottomRight = topLeft + widthHeight; +} + +void Rectangle::setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight) +{ + this->topLeft = topLeft; + this->bottomRight = bottomRight; +} + +void Rectangle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if ((flags & F_VISIBLE) && state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getOuterLineColor(); + } else { + color = outerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_line_width( getOuterLineWidth() ); + + rtengine::Coord tl, br; + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (topLeft.x, topLeft.y, tl.x, tl.y); + } else if (datum == CLICKED_POINT) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (bottomRight.x, bottomRight.y, br.x, br.y); + } else if (datum == CLICKED_POINT) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); + + if (filled) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->stroke(); + } + } +} + +void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_VISIBLE) { + if (state != INSENSITIVE) { + RGBColor color; + + if (flags & F_AUTO_COLOR) { + color = getInnerLineColor(); + } else { + color = innerLineColor; + } + + cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + } + + cr->set_line_width( innerLineWidth ); + + rtengine::Coord tl, br; + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (topLeft.x, topLeft.y, tl.x, tl.y); + } else if (datum == CLICKED_POINT) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen (bottomRight.x, bottomRight.y, br.x, br.y); + } else if (datum == CLICKED_POINT) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (filled && state != INSENSITIVE) { + cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); + + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else if (innerLineWidth > 0.) { + cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); + + if (state == INSENSITIVE) { + std::valarray ds(1); + ds[0] = 4; + cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); + cr->stroke_preserve(); + cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); + cr->set_dash(ds, 0); + cr->stroke(); + ds.resize(0); + cr->set_dash(ds, 0); + } else { + cr->stroke(); + } + } + } +} + +void Rectangle::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_HOVERABLE) { + cr->set_line_width( getMouseOverLineWidth() ); + + rtengine::Coord tl, br; + + if (datum == IMAGE) { + coordSystem.imageCoordToCropCanvas (topLeft.x, topLeft.y, tl.x, tl.y); + } else if (datum == CLICKED_POINT) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + tl = topLeft + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + if (datum == IMAGE) { + coordSystem.imageCoordToCropCanvas (bottomRight.x, bottomRight.y, br.x, br.y); + } else if (datum == CLICKED_POINT) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) { + br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; + } + + // setting the color to the objet's ID + if (objectBuffer->getObjectMode() == OM_255) { + cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); + } else { + cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); + } + cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); + + if (filled) { + if (innerLineWidth > 0.) { + cr->fill_preserve(); + cr->stroke(); + } else { + cr->fill(); + } + } else { + cr->stroke(); + } + } +} + +void OPIcon::drivenPointToRectangle(const rtengine::Coord &pos, + rtengine::Coord &topLeft, rtengine::Coord &bottomRight, int W, int H) +{ + switch (drivenPoint) { + case (DP_CENTERCENTER): + topLeft.x = pos.x - W / 2; + topLeft.y = pos.y - H / 2; + break; + + case (DP_TOPLEFT): + topLeft.x = pos.x; + topLeft.y = pos.y; + break; + + case (DP_TOPCENTER): + topLeft.x = pos.x - W / 2; + topLeft.y = pos.y; + break; + + case (DP_TOPRIGHT): + topLeft.x = pos.x - W; + topLeft.y = pos.y; + break; + + case (DP_CENTERRIGHT): + topLeft.x = pos.x - W; + topLeft.y = pos.y - H / 2; + break; + + case (DP_BOTTOMRIGHT): + topLeft.x = pos.x - W; + topLeft.y = pos.y - H; + break; + + case (DP_BOTTOMCENTER): + topLeft.x = pos.x - W / 2; + topLeft.y = pos.y - H; + break; + + case (DP_BOTTOMLEFT): + topLeft.x = pos.x; + topLeft.y = pos.y - H; + break; + + case (DP_CENTERLEFT): + topLeft.x = pos.x; + topLeft.y = pos.y - H / 2; + break; + } + + bottomRight.x = topLeft.x + W - 1; + bottomRight.y = topLeft.y + H - 1; +} + +OPIcon::OPIcon(const Cairo::RefPtr &normal, + const Cairo::RefPtr &active, + const Cairo::RefPtr &prelight, + const Cairo::RefPtr &dragged, + const Cairo::RefPtr &insensitive, + DrivenPoint drivenPoint) : + drivenPoint(drivenPoint) +{ + if (normal) { + normalImg = normal; + } + + if (prelight) { + prelightImg = prelight; + } + + if (active) { + activeImg = active; + } + + if (dragged) { + draggedImg = dragged; + } + + if (insensitive) { + insensitiveImg = insensitive; + } +} + +OPIcon::OPIcon(Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustring prelightImage, + Glib::ustring draggedImage, Glib::ustring insensitiveImage, DrivenPoint drivenPoint) : drivenPoint(drivenPoint) +{ + if (!normalImage.empty()) { + normalImg = Cairo::RefPtr(new RTSurface(normalImage)); + } + + if (!prelightImage.empty()) { + prelightImg = Cairo::RefPtr(new RTSurface(prelightImage)); + } + + if (!activeImage.empty()) { + activeImg = Cairo::RefPtr(new RTSurface(activeImage)); + } + + if (!draggedImage.empty()) { + draggedImg = Cairo::RefPtr(new RTSurface(draggedImage)); + } + + if (!insensitiveImage.empty()) { + insensitiveImg = Cairo::RefPtr(new RTSurface(insensitiveImage)); + } +} + +const Cairo::RefPtr OPIcon::getNormalImg() +{ + return normalImg; +} +const Cairo::RefPtr OPIcon::getPrelightImg() +{ + return prelightImg; +} +const Cairo::RefPtr OPIcon::getActiveImg() +{ + return activeImg; +} +const Cairo::RefPtr OPIcon::getDraggedImg() +{ + return draggedImg; +} +const Cairo::RefPtr OPIcon::getInsensitiveImg() +{ + return insensitiveImg; +} + +void OPIcon::drawImage(Cairo::RefPtr &img, + Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, + EditCoordSystem &coordSystem) +{ + int imgW = img->getWidth(); + int imgH = img->getHeight(); + + rtengine::Coord pos; + + if (datum == IMAGE) { + coordSystem.imageCoordToScreen(position.x, position.y, pos.x, pos.y); + } else if (datum == CLICKED_POINT) { + pos = position + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) + pos = position + objectBuffer->getDataProvider()->posScreen + + objectBuffer->getDataProvider()->deltaScreen; + + rtengine::Coord tl, br; // Coordinate of the rectangle in the CropBuffer coordinate system + drivenPointToRectangle(pos, tl, br, imgW, imgH); + + cr->set_source(img->get(), tl.x, tl.y); + cr->set_line_width(0.); + cr->rectangle(tl.x, tl.y, imgW, imgH); + cr->fill(); +} + +void OPIcon::drawMOImage(Cairo::RefPtr &img, Cairo::RefPtr &cr, + unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + // test of F_HOVERABLE has already been done + + int imgW = img->getWidth(); + int imgH = img->getHeight(); + + rtengine::Coord pos; + + if (datum == IMAGE) + coordSystem.imageCoordToCropCanvas (position.x, position.y, pos.x, pos.y); + else if (datum == CLICKED_POINT) { + pos = position + objectBuffer->getDataProvider()->posScreen; + } else if (datum == CURSOR) + pos = position + objectBuffer->getDataProvider()->posScreen + + objectBuffer->getDataProvider()->deltaScreen; + + rtengine::Coord tl, br; // Coordinate of the rectangle in the CropBuffer coordinate system + drivenPointToRectangle(pos, tl, br, imgW, imgH); + + // drawing the lower byte's value + if (objectBuffer->getObjectMode() == OM_255) { + cr->set_source_rgba (0., 0., 0., ((id + 1) & 0xFF) / 255.); + } else { + cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); + } + cr->set_line_width(0.); + cr->rectangle(tl.x, tl.y, imgW, imgH); + cr->fill(); +} + +void OPIcon::drawOuterGeometry(Cairo::RefPtr &cr, + ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) {} + +void OPIcon::drawInnerGeometry(Cairo::RefPtr &cr, + ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_VISIBLE) { + // Here we will handle fall-back solutions + + State tmpState = state; // can be updated through the successive test + + if (tmpState == INSENSITIVE) { + if (!insensitiveImg) { + tmpState = NORMAL; + } else { + OPIcon::drawImage(insensitiveImg, cr, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == DRAGGED) { + if (!draggedImg) { + tmpState = ACTIVE; + } else { + OPIcon::drawImage(draggedImg, cr, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == ACTIVE) { + if (!activeImg) { + tmpState = PRELIGHT; + } else { + OPIcon::drawImage(activeImg, cr, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == PRELIGHT) { + if (!prelightImg) { + tmpState = NORMAL; + } else { + OPIcon::drawImage(prelightImg, cr, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == NORMAL && normalImg) { + OPIcon::drawImage(normalImg, cr, objectBuffer, coordSystem); + } + } +} + +void OPIcon::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, + ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) +{ + if (flags & F_HOVERABLE) { + // Here we will handle fallback solutions + State tmpState = state; + + if (tmpState == INSENSITIVE) { + if (!insensitiveImg) { + tmpState = NORMAL; + } else { + OPIcon::drawMOImage(insensitiveImg, cr, id, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == DRAGGED) { + if (!draggedImg) { + tmpState = ACTIVE; + } else { + OPIcon::drawMOImage(draggedImg, cr, id, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == ACTIVE) { + if (!activeImg) { + tmpState = PRELIGHT; + } else { + OPIcon::drawMOImage(activeImg, cr, id, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == PRELIGHT) { + if (!prelightImg) { + tmpState = NORMAL; + } else { + OPIcon::drawMOImage(prelightImg, cr, id, objectBuffer, coordSystem); + return; + } + } + + if (tmpState == NORMAL && normalImg) { + OPIcon::drawMOImage(normalImg, cr, id, objectBuffer, coordSystem); + } + } +} + +#endif diff --git a/rtgui/editwidgets.h b/rtgui/editwidgets.h index ec935291e..1ae185f7a 100644 --- a/rtgui/editwidgets.h +++ b/rtgui/editwidgets.h @@ -1,542 +1,542 @@ -/* - * This file is part of RawTherapee. - * - * Copyright (c) 2019 Jean-Christophe FRISCH - * - * 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 . - */ -#pragma once - -#ifdef GUIVERSION - -#include "rtsurface.h" -#include "editbuffer.h" -#include "editcoordsys.h" -#include "../rtengine/coord.h" - -class ObjectMOBuffer; - -/** @file - * - * The Edit mechanism is designed to let tools (subscribers) communicate with the preview area (provider). - * Subscribers will be tools that need to create some graphics in the preview area, to let the user interact - * with it in a more user friendly way. - * - * Do not confuse with _local_ editing, which is another topic implemented in another class. The Edit feature - * is also not supported in batch editing from the File Browser. - * - * Edit tool can be of 2 types: pipette editing and object editing. - * - * ## Pipette edition - * - * By using this class, a pipette mechanism can be handled on the preview. - * - * Each pipette Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator - * or other mechanism act as appropriated. They are all defined in rtgui/editid.h. A buffer type has to be given - * too, to know which kind of buffer to allocate (see EditSubscriber::BufferType). - * - * Only the first mouse button can be used to manipulate the pipette on the Preview, that's why the developer has - * to implement at least the following 4 methods: - * - mouseOver - * - button1Pressed - * - drag1 - * - button1Released - * - * Actually, only curves does use this class, and everything is handled for curve implementor (as much as possible). - * See the curve's class documentation to see how to implement the curve's pipette feature. - * - * ### Event handling - * - * The mouseOver method is called on each mouse movement, excepted when dragging a point. This method can then access - * the pipetteVal array values, which contain the mean of the pixel read in the buffer, or -1 if the cursor is outside - * of the image. In this case, EditDataProvider::object is also set to 0 (and 1 if over the image). - * - * When the user will click on the left mouse button while pressing the CTRL key, button1Pressed will be called. - * Setting "dragging" to true (or false) is not required for the pipette type editing. - * - * The drag1 method will be called on all subsequent mouse move. The pipetteVal[3] array will already be filled with - * the mean of the read values under the cursor (actually a fixed square of 8px). If the BufferType is BT_SINGLEPLANE_FLOAT, - * only the first array value will be filled. - * - * Then the button1Released will be called to stop the dragging. - * - * ## Object edition - * - * By using this class, objects can be drawn and manipulated on the preview. - * - * The developer has to handle the buttonPress, buttonRelease, drag and mouseOver methods that he needs. There - * are buttonPress, buttonRelease and drag methods dedicated to each mouse button, for better flexibility - * (e.g.button2Press, button2Release, drag2 will handle event when mouse button 2 is used first). RT actually - * does not handle multiple mouse button event (e.g. button1 + button2), only one at a time. The first button pressed - * set the mechanism, all other combined button press are ignored. - * - * The developer also have to fill 2 display list with object of the Geometry subclass. Each geometric shape - * _can_ be used in one or the other, or both list at a time. - * - * The first list (visibleGeometry) is used to be displayed on the preview. The developer will have to set their state - * manually (see Geometry::State), but the display shape, line width and color can be handled automatically, or with - * specific values. To be displayed, the F_VISIBLE flag has to be set through the setActive or setVisible methods. - * - * The second list (mouseOverGeometry) is used in a backbuffer, the color used to draw the shape being the id of the - * mouseOverGeometry. As an example, you could create a line to be shown in the preview, but create 2 filled Circle object - * to be used as mouseOver detection, one on each end of the line. The association between both shape (visible and mouseOver) - * is handled by the developer. To be displayed on this backbuffer, the F_HOVERABLE flag has to be set through the - * setActive or setHoverable methods. For overlapping mouse over geometry, the priority is set by the order in the list : - * the last item is detected first (think of it like a stack piled up). - * - * - * ### Event handling - * - * RT will draw in the back buffer all mouseOverGeometry set by the developer once the Edit button is pressed, and handle - * the events automatically. - * - * RT will call the mouseOver method on each mouse movement where no mouse button is pressed. - * - * On mouse button press over a mouseOverGeometry (that has F_HOVERABLE set), it will call the button press method corresponding - * to the button (e.g. button1Pressed for mouse button 1), with the modifier key as parameter. Any other mouse button pressed at - * the same time will be ignored. It's up to the developer to decide whether this action is starting a 'drag' or 'pick' action, - * by setting the 'action' parameter to the appropriated value. - * - * If the user sets action to ES_ACTION_DRAGGING, RT will then send drag1 events (to stay with our button 1 pressed example) on each - * mouse movement. It's up to the developer of the tool to handle the dragging. The EditProvider class will help you in this by - * handling the actual position in various coordinate system and ways. - * - * When the user will release the mouse button, RT will call the button1Release event (in our example). The developer have - * then to set action to ES_ACTION_NONE. - * - * If the user sets action to ES_ACTION_PICKING, RT will keep in memory the mouseOver object that was selected when pressing the mouse - * (e.g. button 1), as well as the modifier keys. - * - * The element is said to be picked when the mouse button is released over the same mouse over object and with the same active - * modifier keys. In this case, the corresponding picked event (e.g. picked1 in our example) and the 'picked' flag will be true. - * If any of those condition is false, picked1 will still be be called to terminate the initiated picking action, but 'picked' - * will be false. This is necessary because the user may want to update the geometry if the picking is aborted. The developer have - * then to set action to ES_ACTION_NONE. - * - * Picking an on-screen element correspond to single-clicking on it. No double click is supported so far. - * - * Each of these methods have to returns a boolean value saying that the preview has to be refreshed or not (i.e. the displayed - * geometry). - * - * ## Other general internal implementation notes - * - * When a tool is being constructed, unique IDs are affected to the EditSubscribers of the Pipette type. - * Then the EditorPanel class will ask all ToolPanel to register the 'after' preview ImageArea object as data provider. - * The Subscribers have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber - * register itself to the DataProvider, then an event is thrown through the standard ToolPanelListener::panelChanged - * method to update the preview with new graphics to be displayed. If a previous Edit button was active, it will be deactivated - * (the Edit buttons are mutually exclusive). For the Pipette type, a buffer will be created and has to be populated - * by the developer in rtengine's pipeline. The unique pipette ID will be used to know where to fill the buffer, as each pipette - * will need different data, corresponding to the state of the image right before the tool that needs pipette values. E.g for - * the HSV tool, the Hue and Saturation and Value curves are applied on the current state of the image. That's why the pipette - * of the H, S and V curve will share the same data of this "current state", otherwise the read value would be wrong. - * - * When the Edit process stops, the Subscriber is removed from the DataProvider, so buffers can be freed up. - * A new ToolPanelListener::panelChanged event is also thrown to update the preview again, without the tool's - * graphical objects. The Edit button is also toggled off (by the user or programmatically). - * - * It means that each Edit buttons toggled on will start an update of the preview which might or might not create - * a new History entry, depending on the ProcEvent used. - * - */ - -class RGBColor -{ - double r; - double g; - double b; - -public: - RGBColor (); - explicit RGBColor (double r, double g, double b); - explicit RGBColor (char r, char g, char b); - - void setColor (double r, double g, double b); - void setColor (char r, char g, char b); - - double getR (); - double getG (); - double getB (); -}; - -class RGBAColor : public RGBColor -{ - double a; - -public: - RGBAColor (); - explicit RGBAColor (double r, double g, double b, double a); - explicit RGBAColor (char r, char g, char b, char a); - - void setColor (double r, double g, double b, double a); - void setColor (char r, char g, char b, char a); - - double getA (); -}; - -/// @brief Displayable and MouseOver geometry base class -class Geometry -{ -public: - /// @brief Graphical state of the element - enum State { - NORMAL, /// Default state - ACTIVE, /// Focused state - PRELIGHT, /// Hovered state - DRAGGED, /// When being dragged - INSENSITIVE /// Displayed but insensitive - }; - - /// @brief Coordinate space and origin of the point - enum Datum { - IMAGE, /// Image coordinate system with image's top left corner as origin - CLICKED_POINT, /// Screen coordinate system with clicked point as origin - CURSOR /// Screen coordinate system with actual cursor position as origin - }; - enum Flags { - F_VISIBLE = 1 << 0, /// true if the geometry have to be drawn on the visible layer - F_HOVERABLE = 1 << 1, /// true if the geometry have to be drawn on the "mouse over" layer - F_AUTO_COLOR = 1 << 2, /// true if the color depend on the state value, not the color field above - }; - - /// @brief Key point of the image's rectangle that is used to locate the icon copy to the target point: - enum DrivenPoint { - DP_CENTERCENTER, - DP_TOPLEFT, - DP_TOPCENTER, - DP_TOPRIGHT, - DP_CENTERRIGHT, - DP_BOTTOMRIGHT, - DP_BOTTOMCENTER, - DP_BOTTOMLEFT, - DP_CENTERLEFT - }; - -protected: - RGBColor innerLineColor; - RGBColor outerLineColor; - short flags; - -public: - float innerLineWidth; // ...outerLineWidth = innerLineWidth+2 - Datum datum; - State state; // set by the Subscriber - - Geometry (); - virtual ~Geometry() {} - - void setInnerLineColor (double r, double g, double b); - void setInnerLineColor (char r, char g, char b); - RGBColor getInnerLineColor (); - void setOuterLineColor (double r, double g, double b); - void setOuterLineColor (char r, char g, char b); - RGBColor getOuterLineColor (); - double getOuterLineWidth (); - double getMouseOverLineWidth (); - void setAutoColor (bool aColor); - bool isVisible (); - void setVisible (bool visible); - bool isHoverable (); - void setHoverable (bool visible); - - - // setActive will enable/disable the visible and hoverable flags in one shot! - void setActive (bool active); - - virtual void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; - virtual void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; - virtual void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; -}; - -class Circle : public Geometry -{ -public: - rtengine::Coord center; - int radius; - bool filled; - bool radiusInImageSpace; /// If true, the radius depend on the image scale; if false, it is a fixed 'screen' size - - Circle (); - Circle (rtengine::Coord& center, int radius, bool filled = false, bool radiusInImageSpace = false); - Circle (int centerX, int centerY, int radius, bool filled = false, bool radiusInImageSpace = false); - - void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; -}; - -class Line : public Geometry -{ -public: - rtengine::Coord begin; - rtengine::Coord end; - - Line (); - Line (rtengine::Coord& begin, rtengine::Coord& end); - Line (int beginX, int beginY, int endX, int endY); - - void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; -}; - -class Polyline : public Geometry -{ -public: - std::vector points; - bool filled; - - Polyline (); - - void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; -}; - -class Rectangle : public Geometry -{ -public: - rtengine::Coord topLeft; - rtengine::Coord bottomRight; - bool filled; - - Rectangle (); - - void setXYWH(int left, int top, int width, int height); - void setXYXY(int left, int top, int right, int bottom); - void setXYWH(rtengine::Coord topLeft, rtengine::Coord widthHeight); - void setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight); - void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; -}; - -class OPIcon : public Geometry // OP stands for "On Preview" -{ - -private: - Cairo::RefPtr normalImg; - Cairo::RefPtr prelightImg; - Cairo::RefPtr activeImg; - Cairo::RefPtr draggedImg; - Cairo::RefPtr insensitiveImg; - - static void updateImages(); - void changeImage(Glib::ustring &newImage); - void drawImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); - void drawMOImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); - void drivenPointToRectangle(const rtengine::Coord &pos, rtengine::Coord &topLeft, rtengine::Coord &bottomRight, int W, int H); - -public: - DrivenPoint drivenPoint; - rtengine::Coord position; - - OPIcon (const Cairo::RefPtr &normal, - const Cairo::RefPtr &active, - const Cairo::RefPtr &prelight = {}, - const Cairo::RefPtr &dragged = {}, - const Cairo::RefPtr &insensitive = {}, - DrivenPoint drivenPoint = DP_CENTERCENTER); - OPIcon (Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustring prelightImage = "", Glib::ustring draggedImage = "", Glib::ustring insensitiveImage = "", DrivenPoint drivenPoint = DP_CENTERCENTER); - const Cairo::RefPtr getNormalImg(); - const Cairo::RefPtr getPrelightImg(); - const Cairo::RefPtr getActiveImg(); - const Cairo::RefPtr getDraggedImg(); - const Cairo::RefPtr getInsensitiveImg(); - void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; - void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; -}; - -class OPAdjuster : public Geometry // OP stands for "On Preview" -{ - -}; - -inline void RGBColor::setColor (double r, double g, double b) { - this->r = r; - this->g = g; - this->b = b; -} - -inline void RGBColor::setColor (char r, char g, char b) { - this->r = double (r) / 255.; - this->g = double (g) / 255.; - this->b = double (b) / 255.; -} - -inline double RGBColor::getR () { - return r; -} - -inline double RGBColor::getG () { - return g; -} - -inline double RGBColor::getB () { - return b; -} - -inline void RGBAColor::setColor (double r, double g, double b, double a) { - RGBColor::setColor (r, g, b); - this->a = a; -} - -inline void RGBAColor::setColor (char r, char g, char b, char a) { - RGBColor::setColor (r, g, b); - this->a = double (a) / 255.; -} - -inline double RGBAColor::getA () { - return a; -} - -inline void Geometry::setInnerLineColor (double r, double g, double b) { - innerLineColor.setColor (r, g, b); - flags &= ~F_AUTO_COLOR; -} - -inline void Geometry::setInnerLineColor (char r, char g, char b) { - innerLineColor.setColor (r, g, b); - flags &= ~F_AUTO_COLOR; -} - -inline void Geometry::setOuterLineColor (double r, double g, double b) { - outerLineColor.setColor (r, g, b); - flags &= ~F_AUTO_COLOR; -} - -inline double Geometry::getOuterLineWidth () { - return double (innerLineWidth) + 2.; -} - -inline void Geometry::setOuterLineColor (char r, char g, char b) { - outerLineColor.setColor (r, g, b); - flags &= ~F_AUTO_COLOR; -} - -inline double Geometry::getMouseOverLineWidth () { - return getOuterLineWidth () + 2.; -} - -inline void Geometry::setAutoColor (bool aColor) { - if (aColor) { - flags |= F_AUTO_COLOR; - } else { - flags &= ~F_AUTO_COLOR; - } -} - -inline bool Geometry::isVisible () { - return flags & F_VISIBLE; -} - -inline void Geometry::setVisible (bool visible) { - if (visible) { - flags |= F_VISIBLE; - } else { - flags &= ~F_VISIBLE; - } -} - -inline bool Geometry::isHoverable () { - return flags & F_HOVERABLE; -} - -inline void Geometry::setHoverable (bool hoverable) { - if (hoverable) { - flags |= F_HOVERABLE; - } else { - flags &= ~F_HOVERABLE; - } -} - -inline void Geometry::setActive (bool active) { - if (active) { - flags |= (F_VISIBLE | F_HOVERABLE); - } else { - flags &= ~(F_VISIBLE | F_HOVERABLE); - } -} - -inline Geometry::Geometry () : - innerLineColor (char (255), char (255), char (255)), outerLineColor ( - char (0), char (0), char (0)), flags ( - F_VISIBLE | F_HOVERABLE | F_AUTO_COLOR), innerLineWidth (1.5f), datum ( - IMAGE), state (NORMAL) { -} - -inline RGBAColor::RGBAColor () : - RGBColor (0., 0., 0.), a (0.) { -} - -inline RGBColor::RGBColor () : - r (0.), g (0.), b (0.) { -} - -inline RGBColor::RGBColor (double r, double g, double b) : - r (r), g (g), b (b) { -} - -inline RGBColor::RGBColor (char r, char g, char b) : - r (double (r) / 255.), g (double (g) / 255.), b (double (b) / 255.) { -} - -inline RGBAColor::RGBAColor (double r, double g, double b, double a) : - RGBColor (r, g, b), a (a) { -} - -inline RGBAColor::RGBAColor (char r, char g, char b, char a) : - RGBColor (r, g, b), a (double (a) / 255.) { -} - -inline Circle::Circle () : - center (100, 100), radius (10), filled (false), radiusInImageSpace ( - false) { -} - -inline Rectangle::Rectangle () : - topLeft (0, 0), bottomRight (10, 10), filled (false) { -} - -inline Polyline::Polyline () : - filled (false) { -} - -inline Line::Line () : - begin (10, 10), end (100, 100) { -} - -inline Circle::Circle (rtengine::Coord& center, int radius, bool filled, - bool radiusInImageSpace) : - center (center), radius (radius), filled (filled), radiusInImageSpace ( - radiusInImageSpace) { -} - -inline Circle::Circle (int centerX, int centerY, int radius, bool filled, - bool radiusInImageSpace) : - center (centerX, centerY), radius (radius), filled (filled), radiusInImageSpace ( - radiusInImageSpace) { -} - -inline Line::Line (rtengine::Coord& begin, rtengine::Coord& end) : - begin (begin), end (end) { -} - -inline Line::Line (int beginX, int beginY, int endX, int endY) : - begin (beginX, beginY), end (endX, endY) { -} - -#endif - +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 . + */ +#pragma once + +#ifdef GUIVERSION + +#include "rtsurface.h" +#include "editbuffer.h" +#include "editcoordsys.h" +#include "../rtengine/coord.h" + +class ObjectMOBuffer; + +/** @file + * + * The Edit mechanism is designed to let tools (subscribers) communicate with the preview area (provider). + * Subscribers will be tools that need to create some graphics in the preview area, to let the user interact + * with it in a more user friendly way. + * + * Do not confuse with _local_ editing, which is another topic implemented in another class. The Edit feature + * is also not supported in batch editing from the File Browser. + * + * Edit tool can be of 2 types: pipette editing and object editing. + * + * ## Pipette edition + * + * By using this class, a pipette mechanism can be handled on the preview. + * + * Each pipette Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator + * or other mechanism act as appropriated. They are all defined in rtgui/editid.h. A buffer type has to be given + * too, to know which kind of buffer to allocate (see EditSubscriber::BufferType). + * + * Only the first mouse button can be used to manipulate the pipette on the Preview, that's why the developer has + * to implement at least the following 4 methods: + * - mouseOver + * - button1Pressed + * - drag1 + * - button1Released + * + * Actually, only curves does use this class, and everything is handled for curve implementor (as much as possible). + * See the curve's class documentation to see how to implement the curve's pipette feature. + * + * ### Event handling + * + * The mouseOver method is called on each mouse movement, excepted when dragging a point. This method can then access + * the pipetteVal array values, which contain the mean of the pixel read in the buffer, or -1 if the cursor is outside + * of the image. In this case, EditDataProvider::object is also set to 0 (and 1 if over the image). + * + * When the user will click on the left mouse button while pressing the CTRL key, button1Pressed will be called. + * Setting "dragging" to true (or false) is not required for the pipette type editing. + * + * The drag1 method will be called on all subsequent mouse move. The pipetteVal[3] array will already be filled with + * the mean of the read values under the cursor (actually a fixed square of 8px). If the BufferType is BT_SINGLEPLANE_FLOAT, + * only the first array value will be filled. + * + * Then the button1Released will be called to stop the dragging. + * + * ## Object edition + * + * By using this class, objects can be drawn and manipulated on the preview. + * + * The developer has to handle the buttonPress, buttonRelease, drag and mouseOver methods that he needs. There + * are buttonPress, buttonRelease and drag methods dedicated to each mouse button, for better flexibility + * (e.g.button2Press, button2Release, drag2 will handle event when mouse button 2 is used first). RT actually + * does not handle multiple mouse button event (e.g. button1 + button2), only one at a time. The first button pressed + * set the mechanism, all other combined button press are ignored. + * + * The developer also have to fill 2 display list with object of the Geometry subclass. Each geometric shape + * _can_ be used in one or the other, or both list at a time. + * + * The first list (visibleGeometry) is used to be displayed on the preview. The developer will have to set their state + * manually (see Geometry::State), but the display shape, line width and color can be handled automatically, or with + * specific values. To be displayed, the F_VISIBLE flag has to be set through the setActive or setVisible methods. + * + * The second list (mouseOverGeometry) is used in a backbuffer, the color used to draw the shape being the id of the + * mouseOverGeometry. As an example, you could create a line to be shown in the preview, but create 2 filled Circle object + * to be used as mouseOver detection, one on each end of the line. The association between both shape (visible and mouseOver) + * is handled by the developer. To be displayed on this backbuffer, the F_HOVERABLE flag has to be set through the + * setActive or setHoverable methods. For overlapping mouse over geometry, the priority is set by the order in the list : + * the last item is detected first (think of it like a stack piled up). + * + * + * ### Event handling + * + * RT will draw in the back buffer all mouseOverGeometry set by the developer once the Edit button is pressed, and handle + * the events automatically. + * + * RT will call the mouseOver method on each mouse movement where no mouse button is pressed. + * + * On mouse button press over a mouseOverGeometry (that has F_HOVERABLE set), it will call the button press method corresponding + * to the button (e.g. button1Pressed for mouse button 1), with the modifier key as parameter. Any other mouse button pressed at + * the same time will be ignored. It's up to the developer to decide whether this action is starting a 'drag' or 'pick' action, + * by setting the 'action' parameter to the appropriated value. + * + * If the user sets action to ES_ACTION_DRAGGING, RT will then send drag1 events (to stay with our button 1 pressed example) on each + * mouse movement. It's up to the developer of the tool to handle the dragging. The EditProvider class will help you in this by + * handling the actual position in various coordinate system and ways. + * + * When the user will release the mouse button, RT will call the button1Release event (in our example). The developer have + * then to set action to ES_ACTION_NONE. + * + * If the user sets action to ES_ACTION_PICKING, RT will keep in memory the mouseOver object that was selected when pressing the mouse + * (e.g. button 1), as well as the modifier keys. + * + * The element is said to be picked when the mouse button is released over the same mouse over object and with the same active + * modifier keys. In this case, the corresponding picked event (e.g. picked1 in our example) and the 'picked' flag will be true. + * If any of those condition is false, picked1 will still be be called to terminate the initiated picking action, but 'picked' + * will be false. This is necessary because the user may want to update the geometry if the picking is aborted. The developer have + * then to set action to ES_ACTION_NONE. + * + * Picking an on-screen element correspond to single-clicking on it. No double click is supported so far. + * + * Each of these methods have to returns a boolean value saying that the preview has to be refreshed or not (i.e. the displayed + * geometry). + * + * ## Other general internal implementation notes + * + * When a tool is being constructed, unique IDs are affected to the EditSubscribers of the Pipette type. + * Then the EditorPanel class will ask all ToolPanel to register the 'after' preview ImageArea object as data provider. + * The Subscribers have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber + * register itself to the DataProvider, then an event is thrown through the standard ToolPanelListener::panelChanged + * method to update the preview with new graphics to be displayed. If a previous Edit button was active, it will be deactivated + * (the Edit buttons are mutually exclusive). For the Pipette type, a buffer will be created and has to be populated + * by the developer in rtengine's pipeline. The unique pipette ID will be used to know where to fill the buffer, as each pipette + * will need different data, corresponding to the state of the image right before the tool that needs pipette values. E.g for + * the HSV tool, the Hue and Saturation and Value curves are applied on the current state of the image. That's why the pipette + * of the H, S and V curve will share the same data of this "current state", otherwise the read value would be wrong. + * + * When the Edit process stops, the Subscriber is removed from the DataProvider, so buffers can be freed up. + * A new ToolPanelListener::panelChanged event is also thrown to update the preview again, without the tool's + * graphical objects. The Edit button is also toggled off (by the user or programmatically). + * + * It means that each Edit buttons toggled on will start an update of the preview which might or might not create + * a new History entry, depending on the ProcEvent used. + * + */ + +class RGBColor +{ + double r; + double g; + double b; + +public: + RGBColor (); + explicit RGBColor (double r, double g, double b); + explicit RGBColor (char r, char g, char b); + + void setColor (double r, double g, double b); + void setColor (char r, char g, char b); + + double getR (); + double getG (); + double getB (); +}; + +class RGBAColor : public RGBColor +{ + double a; + +public: + RGBAColor (); + explicit RGBAColor (double r, double g, double b, double a); + explicit RGBAColor (char r, char g, char b, char a); + + void setColor (double r, double g, double b, double a); + void setColor (char r, char g, char b, char a); + + double getA (); +}; + +/// @brief Displayable and MouseOver geometry base class +class Geometry +{ +public: + /// @brief Graphical state of the element + enum State { + NORMAL, /// Default state + ACTIVE, /// Focused state + PRELIGHT, /// Hovered state + DRAGGED, /// When being dragged + INSENSITIVE /// Displayed but insensitive + }; + + /// @brief Coordinate space and origin of the point + enum Datum { + IMAGE, /// Image coordinate system with image's top left corner as origin + CLICKED_POINT, /// Screen coordinate system with clicked point as origin + CURSOR /// Screen coordinate system with actual cursor position as origin + }; + enum Flags { + F_VISIBLE = 1 << 0, /// true if the geometry have to be drawn on the visible layer + F_HOVERABLE = 1 << 1, /// true if the geometry have to be drawn on the "mouse over" layer + F_AUTO_COLOR = 1 << 2, /// true if the color depend on the state value, not the color field above + }; + + /// @brief Key point of the image's rectangle that is used to locate the icon copy to the target point: + enum DrivenPoint { + DP_CENTERCENTER, + DP_TOPLEFT, + DP_TOPCENTER, + DP_TOPRIGHT, + DP_CENTERRIGHT, + DP_BOTTOMRIGHT, + DP_BOTTOMCENTER, + DP_BOTTOMLEFT, + DP_CENTERLEFT + }; + +protected: + RGBColor innerLineColor; + RGBColor outerLineColor; + short flags; + +public: + float innerLineWidth; // ...outerLineWidth = innerLineWidth+2 + Datum datum; + State state; // set by the Subscriber + + Geometry (); + virtual ~Geometry() {} + + void setInnerLineColor (double r, double g, double b); + void setInnerLineColor (char r, char g, char b); + RGBColor getInnerLineColor (); + void setOuterLineColor (double r, double g, double b); + void setOuterLineColor (char r, char g, char b); + RGBColor getOuterLineColor (); + double getOuterLineWidth (); + double getMouseOverLineWidth (); + void setAutoColor (bool aColor); + bool isVisible (); + void setVisible (bool visible); + bool isHoverable (); + void setHoverable (bool visible); + + + // setActive will enable/disable the visible and hoverable flags in one shot! + void setActive (bool active); + + virtual void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; + virtual void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; + virtual void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) = 0; +}; + +class Circle : public Geometry +{ +public: + rtengine::Coord center; + int radius; + bool filled; + bool radiusInImageSpace; /// If true, the radius depend on the image scale; if false, it is a fixed 'screen' size + + Circle (); + Circle (rtengine::Coord& center, int radius, bool filled = false, bool radiusInImageSpace = false); + Circle (int centerX, int centerY, int radius, bool filled = false, bool radiusInImageSpace = false); + + void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; +}; + +class Line : public Geometry +{ +public: + rtengine::Coord begin; + rtengine::Coord end; + + Line (); + Line (rtengine::Coord& begin, rtengine::Coord& end); + Line (int beginX, int beginY, int endX, int endY); + + void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; +}; + +class Polyline : public Geometry +{ +public: + std::vector points; + bool filled; + + Polyline (); + + void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; +}; + +class Rectangle : public Geometry +{ +public: + rtengine::Coord topLeft; + rtengine::Coord bottomRight; + bool filled; + + Rectangle (); + + void setXYWH(int left, int top, int width, int height); + void setXYXY(int left, int top, int right, int bottom); + void setXYWH(rtengine::Coord topLeft, rtengine::Coord widthHeight); + void setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight); + void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; +}; + +class OPIcon : public Geometry // OP stands for "On Preview" +{ + +private: + Cairo::RefPtr normalImg; + Cairo::RefPtr prelightImg; + Cairo::RefPtr activeImg; + Cairo::RefPtr draggedImg; + Cairo::RefPtr insensitiveImg; + + static void updateImages(); + void changeImage(Glib::ustring &newImage); + void drawImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); + void drawMOImage (Cairo::RefPtr &img, Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem); + void drivenPointToRectangle(const rtengine::Coord &pos, rtengine::Coord &topLeft, rtengine::Coord &bottomRight, int W, int H); + +public: + DrivenPoint drivenPoint; + rtengine::Coord position; + + OPIcon (const Cairo::RefPtr &normal, + const Cairo::RefPtr &active, + const Cairo::RefPtr &prelight = {}, + const Cairo::RefPtr &dragged = {}, + const Cairo::RefPtr &insensitive = {}, + DrivenPoint drivenPoint = DP_CENTERCENTER); + OPIcon (Glib::ustring normalImage, Glib::ustring activeImage, Glib::ustring prelightImage = "", Glib::ustring draggedImage = "", Glib::ustring insensitiveImage = "", DrivenPoint drivenPoint = DP_CENTERCENTER); + const Cairo::RefPtr getNormalImg(); + const Cairo::RefPtr getPrelightImg(); + const Cairo::RefPtr getActiveImg(); + const Cairo::RefPtr getDraggedImg(); + const Cairo::RefPtr getInsensitiveImg(); + void drawOuterGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawInnerGeometry (Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; + void drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) override; +}; + +class OPAdjuster : public Geometry // OP stands for "On Preview" +{ + +}; + +inline void RGBColor::setColor (double r, double g, double b) { + this->r = r; + this->g = g; + this->b = b; +} + +inline void RGBColor::setColor (char r, char g, char b) { + this->r = double (r) / 255.; + this->g = double (g) / 255.; + this->b = double (b) / 255.; +} + +inline double RGBColor::getR () { + return r; +} + +inline double RGBColor::getG () { + return g; +} + +inline double RGBColor::getB () { + return b; +} + +inline void RGBAColor::setColor (double r, double g, double b, double a) { + RGBColor::setColor (r, g, b); + this->a = a; +} + +inline void RGBAColor::setColor (char r, char g, char b, char a) { + RGBColor::setColor (r, g, b); + this->a = double (a) / 255.; +} + +inline double RGBAColor::getA () { + return a; +} + +inline void Geometry::setInnerLineColor (double r, double g, double b) { + innerLineColor.setColor (r, g, b); + flags &= ~F_AUTO_COLOR; +} + +inline void Geometry::setInnerLineColor (char r, char g, char b) { + innerLineColor.setColor (r, g, b); + flags &= ~F_AUTO_COLOR; +} + +inline void Geometry::setOuterLineColor (double r, double g, double b) { + outerLineColor.setColor (r, g, b); + flags &= ~F_AUTO_COLOR; +} + +inline double Geometry::getOuterLineWidth () { + return double (innerLineWidth) + 2.; +} + +inline void Geometry::setOuterLineColor (char r, char g, char b) { + outerLineColor.setColor (r, g, b); + flags &= ~F_AUTO_COLOR; +} + +inline double Geometry::getMouseOverLineWidth () { + return getOuterLineWidth () + 2.; +} + +inline void Geometry::setAutoColor (bool aColor) { + if (aColor) { + flags |= F_AUTO_COLOR; + } else { + flags &= ~F_AUTO_COLOR; + } +} + +inline bool Geometry::isVisible () { + return flags & F_VISIBLE; +} + +inline void Geometry::setVisible (bool visible) { + if (visible) { + flags |= F_VISIBLE; + } else { + flags &= ~F_VISIBLE; + } +} + +inline bool Geometry::isHoverable () { + return flags & F_HOVERABLE; +} + +inline void Geometry::setHoverable (bool hoverable) { + if (hoverable) { + flags |= F_HOVERABLE; + } else { + flags &= ~F_HOVERABLE; + } +} + +inline void Geometry::setActive (bool active) { + if (active) { + flags |= (F_VISIBLE | F_HOVERABLE); + } else { + flags &= ~(F_VISIBLE | F_HOVERABLE); + } +} + +inline Geometry::Geometry () : + innerLineColor (char (255), char (255), char (255)), outerLineColor ( + char (0), char (0), char (0)), flags ( + F_VISIBLE | F_HOVERABLE | F_AUTO_COLOR), innerLineWidth (1.5f), datum ( + IMAGE), state (NORMAL) { +} + +inline RGBAColor::RGBAColor () : + RGBColor (0., 0., 0.), a (0.) { +} + +inline RGBColor::RGBColor () : + r (0.), g (0.), b (0.) { +} + +inline RGBColor::RGBColor (double r, double g, double b) : + r (r), g (g), b (b) { +} + +inline RGBColor::RGBColor (char r, char g, char b) : + r (double (r) / 255.), g (double (g) / 255.), b (double (b) / 255.) { +} + +inline RGBAColor::RGBAColor (double r, double g, double b, double a) : + RGBColor (r, g, b), a (a) { +} + +inline RGBAColor::RGBAColor (char r, char g, char b, char a) : + RGBColor (r, g, b), a (double (a) / 255.) { +} + +inline Circle::Circle () : + center (100, 100), radius (10), filled (false), radiusInImageSpace ( + false) { +} + +inline Rectangle::Rectangle () : + topLeft (0, 0), bottomRight (10, 10), filled (false) { +} + +inline Polyline::Polyline () : + filled (false) { +} + +inline Line::Line () : + begin (10, 10), end (100, 100) { +} + +inline Circle::Circle (rtengine::Coord& center, int radius, bool filled, + bool radiusInImageSpace) : + center (center), radius (radius), filled (filled), radiusInImageSpace ( + radiusInImageSpace) { +} + +inline Circle::Circle (int centerX, int centerY, int radius, bool filled, + bool radiusInImageSpace) : + center (centerX, centerY), radius (radius), filled (filled), radiusInImageSpace ( + radiusInImageSpace) { +} + +inline Line::Line (rtengine::Coord& begin, rtengine::Coord& end) : + begin (begin), end (end) { +} + +inline Line::Line (int beginX, int beginY, int endX, int endY) : + begin (beginX, beginY), end (endX, endY) { +} + +#endif + From 4d43dd7e09da6df3350f097e1f06563a89a42a43 Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 1 Aug 2019 01:50:47 +0200 Subject: [PATCH 06/34] Bugfixing crash when reprocessing the preview --- rtengine/improccoordinator.cc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 63d6127b9..f27fafad6 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -525,16 +525,20 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) progress ("Spot Removal...", 100 * readyphase / numofphases); - if ((todo & M_SPOT) && params->spot.enabled && !params->spot.entries.empty ()) { - // First update the image with spot healing - ipf.removeSpots (oprevi, params->spot.entries, previewProps); + if (params->spot.enabled && !params->spot.entries.empty ()) { + if ((todo & M_SPOT)) { + // First update the image with spot healing + ipf.removeSpots (oprevi, params->spot.entries, previewProps); - // Then create fork the image to cache the data - if (spot_prev == nullptr) { - spot_prev = new Imagefloat (pW, pH); + // Then fork the image to cache the data + if (spot_prev == nullptr) { + spot_prev = new Imagefloat (pW, pH); + } + oprevi->copyData (spot_prev); + } + if (spot_prev) { + oprevi = spot_prev; } - - oprevi->copyData (spot_prev); } else { if (spot_prev) { delete spot_prev; @@ -1125,7 +1129,7 @@ void ImProcCoordinator::setScale(int prevscale) pH = nH; orig_prev = new Imagefloat(pW, pH); - spot_prev = oprevi = orig_prev; + 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 From 7d310e688c9837d31c262678df3c71a278ce11f0 Mon Sep 17 00:00:00 2001 From: Hombre Date: Sun, 4 Aug 2019 16:07:35 +0200 Subject: [PATCH 07/34] Adding a new spot adjust editing mode in rtengine / preview window --- rtdata/languages/Francais | 4 +- rtdata/languages/default | 15 ++-- rtengine/dcrop.cc | 56 +++++++++------ rtengine/improccoordinator.cc | 128 +++++++++++++++++++++++----------- rtengine/improccoordinator.h | 5 +- rtengine/procevents.h | 4 +- rtengine/refreshmap.cc | 6 +- rtengine/refreshmap.h | 21 +++--- rtgui/spot.cc | 32 ++++++--- rtgui/spot.h | 8 +++ rtgui/toolpanel.h | 4 ++ rtgui/toolpanelcoord.cc | 13 ++++ rtgui/toolpanelcoord.h | 1 + 13 files changed, 198 insertions(+), 99 deletions(-) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index dfa2e7ff3..8011b9189 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -474,7 +474,7 @@ HISTORY_MSG_232;N&B - Type de courbe 'Avant' HISTORY_MSG_233;N&B - Courbe 'Après' HISTORY_MSG_234;N&B - Type de courbe 'Après' HISTORY_MSG_235;N&B - Mixeur de Canaux - Auto -HISTORY_MSG_236;Retrait de taches - Modif. de points +HISTORY_MSG_236;--inutilisé-- HISTORY_MSG_237;N&B - Mixeur de Canaux HISTORY_MSG_238;FD - Étendu HISTORY_MSG_239;FD - Force @@ -764,6 +764,8 @@ HISTORY_MSG_SHARPENING_CONTRAST;Netteté - Seuil de contraste HISTORY_MSG_SH_COLORSPACE;O/HL - Espace couleur HISTORY_MSG_SOFTLIGHT_ENABLED;Lumière douce HISTORY_MSG_SOFTLIGHT_STRENGTH;Lumière douce - Force +HISTORY_MSG_SPOT;Retrait de taches +HISTORY_MSG_SPOT_ENTRY;Retrait de taches - Modif. de points HISTORY_MSG_TM_FATTAL_ANCHOR;CPD - Ancre HISTORY_NEWSNAPSHOT;Ajouter HISTORY_NEWSNAPSHOT_TOOLTIP;Raccourci: Alt-s diff --git a/rtdata/languages/default b/rtdata/languages/default index e065abd7f..3296a4c84 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -75,6 +75,7 @@ EXIFPANEL_RESET;Reset EXIFPANEL_RESETALL;Reset All EXIFPANEL_RESETALLHINT;Reset all tags to their original values. EXIFPANEL_RESETHINT;Reset the selected tags to their original values. +EXIFPANEL_SHOWALL;Show all EXIFPANEL_SUBDIRECTORY;Subdirectory EXPORT_BYPASS;Processing steps to bypass EXPORT_BYPASS_ALL;Select / Unselect All @@ -283,9 +284,9 @@ HISTORY_MSG_30;RLD - Radius HISTORY_MSG_31;RLD - Amount HISTORY_MSG_32;RLD - Damping HISTORY_MSG_33;RLD - Iterations -HISTORY_MSG_34;LCP distortion correction -HISTORY_MSG_35;LCP vignetting correction -HISTORY_MSG_36;LCP CA correction +HISTORY_MSG_34;Lens Correction - Distortion +HISTORY_MSG_35;Lens Correction - Vignetting +HISTORY_MSG_36;Lens Correction - CA HISTORY_MSG_37;Exposure - Auto levels HISTORY_MSG_38;White Balance - Method HISTORY_MSG_39;WB - Temperature @@ -334,7 +335,7 @@ HISTORY_MSG_81;Resize HISTORY_MSG_82;Profile changed HISTORY_MSG_83;S/H - Sharp mask HISTORY_MSG_84;Perspective correction -HISTORY_MSG_85;LCP +HISTORY_MSG_85;Lens Correction - LCP file HISTORY_MSG_86;RGB Curves - Luminosity mode HISTORY_MSG_87;Impulse Noise Reduction HISTORY_MSG_88;Impulse NR threshold @@ -483,7 +484,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 - CM - Auto -HISTORY_MSG_236;Spot removal - Point modif. +HISTORY_MSG_236;--unused-- HISTORY_MSG_237;B&W - CM HISTORY_MSG_238;GF - Feather HISTORY_MSG_239;GF - Strength @@ -672,7 +673,7 @@ HISTORY_MSG_421;Retinex - Gamma HISTORY_MSG_422;Retinex - Gamma HISTORY_MSG_423;Retinex - Gamma slope HISTORY_MSG_424;Retinex - HL threshold -HISTORY_MSG_425;Spot removal +HISTORY_MSG_425;Retinex - Log base HISTORY_MSG_426;Retinex - Hue equalizer HISTORY_MSG_427;Output rendering intent HISTORY_MSG_428;Monitor rendering intent @@ -775,6 +776,8 @@ HISTORY_MSG_SHARPENING_CONTRAST;Sharpening - Contrast threshold HISTORY_MSG_SH_COLORSPACE;S/H - Colorspace HISTORY_MSG_SOFTLIGHT_ENABLED;Soft light HISTORY_MSG_SOFTLIGHT_STRENGTH;Soft light - Strength +HISTORY_MSG_SPOT;Spot removal +HISTORY_MSG_SPOT_ENTRY;Spot removal - Point modif. HISTORY_MSG_TM_FATTAL_ANCHOR;DRC - Anchor HISTORY_NEWSNAPSHOT;Add HISTORY_NEWSNAPSHOT_TOOLTIP;Shortcut: Alt-s diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 7d4bad381..afd9e3774 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -618,6 +618,12 @@ void Crop::update(int todo) parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); } + if ((todo & M_SPOT) && params.spot.enabled && !params.spot.entries.empty()) { + PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); + //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); + parent->ipf.removeSpots(origCrop, params.spot.entries, pp); + } + DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise; if (params.dirpyrDenoise.Lmethod == "CUR") { @@ -692,6 +698,33 @@ void Crop::update(int todo) // has to be called after setCropSizes! Tools prior to this point can't handle the Edit mechanism, but that shouldn't be a problem. createBuffer(cropw, croph); + if ((todo & M_SPOT_ADJUST) && params.spot.enabled && !params.spot.entries.empty()) { + PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); + parent->ipf.removeSpots(origCrop, params.spot.entries, pp); + } + + // Apply Spot removal + if (todo & (M_SPOT|M_SPOT_ADJUST)) { + if (params.spot.enabled && !params.spot.entries.empty()) { + if(!spotCrop) { + spotCrop = new Imagefloat (cropw, croph); + } + baseCrop->copyData (spotCrop); + PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip); + //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); + parent->ipf.removeSpots (spotCrop, params.spot.entries, pp); + } else { + if (spotCrop) { + delete spotCrop; + spotCrop = nullptr; + } + } + } + + if (spotCrop) { + baseCrop = spotCrop; + } + std::unique_ptr fattalCrop; if ((todo & M_HDR) && (params.fattal.enabled || params.dehaze.enabled)) { @@ -747,29 +780,6 @@ void Crop::update(int todo) parent->ipf.ToneMapFattal02(f); } - // Apply Spot removal - 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; - } - // crop back to the size expected by the rest of the pipeline if (need_cropping) { Imagefloat *c = origCrop; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index f27fafad6..91ba5b60c 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -40,7 +40,7 @@ extern const Settings* settings; ImProcCoordinator::ImProcCoordinator() : orig_prev(nullptr), oprevi(nullptr), - spot_prev (nullptr), + spotprev(nullptr), oprevl(nullptr), nprevl(nullptr), fattal_11_dcrop_cache(nullptr), @@ -50,7 +50,6 @@ ImProcCoordinator::ImProcCoordinator() : imgsrc (nullptr), lastAwbEqual (0.), lastAwbTempBias (0.0), - previewProps(-1, -1, -1, -1, 1), monitorIntent (RI_RELATIVE), softProof(false), gamutCheck(false), @@ -217,7 +216,7 @@ DetailedCrop* ImProcCoordinator::createCrop(::EditDataProvider *editDataProvider void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) { - MyMutex::MyLock processingLock (mProcessing); + MyMutex::MyLock processingLock(mProcessing); constexpr int numofphases = 15; int readyphase = 0; @@ -234,6 +233,41 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } } + if (todo & M_SPOT_ADJUST) { + // TWEAKING THE PROCPARAMS FOR THE SPOT ADJUSTMENT MODE + + // -> using fast demozaicing method + highDetailNeeded = false; + //params->raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); + //params->raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); + + // -> disabling all transform + //params->coarse = CoarseTransformParams(); + params->lensProf = LensProfParams(); + params->cacorrection = CACorrParams(); + params->distortion = DistortionParams(); + params->rotate = RotateParams(); + params->perspective = PerspectiveParams(); + params->vignetting = VignettingParams(); + + // -> disabling standard crop + params->crop.enabled = false; + + // -> disabling time consuming and unnecessary tool + params->sh.enabled = false; + params->blackwhite.enabled = false; + params->dehaze.enabled = false; + params->wavelet.enabled = false; + params->filmSimulation.enabled = false; + params->sharpenEdge.enabled = false; + params->sharpenMicro.enabled = false; + params->sharpening.enabled = false; + params->softlight.enabled = false; + params->gradient.enabled = false; + params->pcvignette.enabled = false; + params->colorappearance.enabled = false; + } + if (((todo & ALL) == ALL) || (todo & M_MONITOR) || panningRelatedChange || (highDetailNeeded && options.prevdemo != PD_Sidecar)) { bwAutoR = bwAutoG = bwAutoB = -9000.f; @@ -451,11 +485,11 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) // Will (re)allocate the preview's buffers setScale(scale); - previewProps.set(0, 0, fw, fh, scale); + PreviewProps pp(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, previewProps, params->toneCurve, params->raw); + imgsrc->getImage(currWB, tr, orig_prev, pp, params->toneCurve, params->raw); denoiseInfoStore.valid = false; //ColorTemp::CAT02 (orig_prev, ¶ms) ; // printf("orig_prevW=%d\n scale=%d",orig_prev->width, scale); @@ -507,47 +541,48 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) readyphase++; + oprevi = orig_prev; + + progress ("Spot Removal...", 100 * readyphase / numofphases); + + if (todo & (M_SPOT|M_SPOT_ADJUST)) { + if (params->spot.enabled && !params->spot.entries.empty()) { + allocCache(spotprev); + orig_prev->copyData (spotprev); + + PreviewProps pp(0, 0, fw, fh, scale); + ipf.removeSpots (spotprev, params->spot.entries, pp); + } else { + if (spotprev) { + delete spotprev; + spotprev = nullptr; + } + } + } + if (spotprev) { + if (oprevi == orig_prev) { + allocCache(oprevi); + } + spotprev->copyData(oprevi); + } + + readyphase++; + if ((todo & M_HDR) && (params->fattal.enabled || params->dehaze.enabled)) { if (fattal_11_dcrop_cache) { delete fattal_11_dcrop_cache; fattal_11_dcrop_cache = nullptr; } - ipf.dehaze(orig_prev); - ipf.ToneMapFattal02(orig_prev); - - if (oprevi != orig_prev) { - delete oprevi; + if (oprevi == orig_prev) { + oprevi = new Imagefloat (pW, pH); + orig_prev->copyData (oprevi); } + + ipf.dehaze(oprevi); + ipf.ToneMapFattal02(oprevi); } - oprevi = orig_prev; - - progress ("Spot Removal...", 100 * readyphase / numofphases); - - if (params->spot.enabled && !params->spot.entries.empty ()) { - if ((todo & M_SPOT)) { - // First update the image with spot healing - ipf.removeSpots (oprevi, params->spot.entries, previewProps); - - // Then fork the image to cache the data - if (spot_prev == nullptr) { - spot_prev = new Imagefloat (pW, pH); - } - oprevi->copyData (spot_prev); - } - if (spot_prev) { - oprevi = spot_prev; - } - } else { - if (spot_prev) { - delete spot_prev; - spot_prev = nullptr; - } - } - - readyphase++; - progress ("Rotate / Distortion...", 100 * readyphase / numofphases); // Remove transformation if unneeded bool needstransform = ipf.needsTransform(); @@ -1063,16 +1098,16 @@ void ImProcCoordinator::freeAll() { if (allocated) { - if (spot_prev && spot_prev != oprevi) { - delete spot_prev; + if (spotprev && spotprev != oprevi) { + delete spotprev; } - spot_prev = nullptr; + spotprev = nullptr; - if (oprevi && oprevi != orig_prev) { + if (orig_prev != oprevi) { delete oprevi; } + oprevi = nullptr; - delete orig_prev; orig_prev = nullptr; delete oprevl; @@ -1099,6 +1134,15 @@ void ImProcCoordinator::freeAll() allocated = false; } +void ImProcCoordinator::allocCache (Imagefloat* &imgfloat) +{ + if (imgfloat == nullptr) { + imgfloat = new Imagefloat(pW, pH); + } else { + imgfloat->allocate(pW, pH); + } +} + /** @brief Handles image buffer (re)allocation and trigger sizeChanged of SizeListener[s] * If the scale change, this method will free all buffers and reallocate ones of the new size. * It will then tell to the SizeListener that size has changed (sizeChanged) diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index a12f5e83e..dac034abf 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -57,7 +57,7 @@ class ImProcCoordinator : public StagedImageProcessor protected: Imagefloat *orig_prev; Imagefloat *oprevi; - Imagefloat *spot_prev; + Imagefloat *spotprev; LabImage *oprevl; LabImage *nprevl; Imagefloat *fattal_11_dcrop_cache; // global cache for ToneMapFattal02 used in 1:1 detail windows (except when denoise is active) @@ -73,8 +73,6 @@ protected: double lastAwbEqual; double lastAwbTempBias; - PreviewProps previewProps; - Glib::ustring monitorProfile; RenderingIntent monitorIntent; bool softProof; @@ -183,6 +181,7 @@ protected: void progress (Glib::ustring str, int pr); void reallocAll (); + void allocCache (Imagefloat* &imgfloat); void updateLRGBHistograms (); void setScale (int prevscale); void updatePreviewImage (int todo, bool panningRelatedChange); diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 1de9437d5..bb6a30038 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -262,7 +262,7 @@ enum ProcEventCode { EvBWAfterCurve = 232, EvBWAfterCurveMode = 233, EvAutoch = 234, - EvSpotEntry = 235, +// EvFixedch=235, -- can be reused -- EvNeutralBW = 236, EvGradientFeather = 237, EvGradientStrength = 238, @@ -451,7 +451,7 @@ enum ProcEventCode { EvLgam = 421, EvLslope = 422, EvLhighl = 423, - EvSpotEnabled = 424, +// EvLbaselog = 424, -- can be reused -- EvRetinexlhcurve = 425, EvOIntent = 426, EvMonitorTransform = 427, diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 22ebbcbb8..d741b1744 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -262,7 +262,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = { RGBCURVE, // EvBWAfterCurve RGBCURVE, // EvBWAfterCurveMode RGBCURVE, // EvAutoch - SPOT, // EvSpotEntry + 0, // --unused-- RGBCURVE, // EvNeutralBW TRANSFORM, // EvGradientFeather TRANSFORM, // EvGradientStrength @@ -451,7 +451,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = { DEMOSAIC, // EvLgam DEMOSAIC, // EvLslope RETINEX, // EvLhighl - SPOT, // EvSpotEnabled + 0, // --unused-- DEMOSAIC, // EvRetinexlhcurve OUTPUTPROFILE, // EvOIntent MONITORTRANSFORM, // EvMonitorTransform: no history message @@ -470,7 +470,7 @@ int refreshmap[rtengine::NUMOFEVENTS] = { RETINEX, // EvRetinexgaintransmission RETINEX, // EvLskal OUTPUTPROFILE, // EvOBPCompens - ALLNORAW, // EvWBtempBias + ALLNORAW, // EvWBtempBias DARKFRAME, // EvRawImageNum 0, // unused 0, // unused diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h index 97945e6e1..114e07b2e 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -22,6 +22,9 @@ #include #include "procevents.h" +// Use M_SPOT_ADJUST to update the rendering for On Preview Adjustment of the Spot tool +#define M_SPOT_ADJUST (1<<19) + // Use M_VOID if you wish to update the proc params without updating the preview at all ! #define M_VOID (1<<18) // Use M_MINUPDATE if you wish to update the preview without modifying the image (think about it like a "refreshPreview") @@ -51,16 +54,16 @@ // 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_HDR|M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_MONITOR) // without HIGHQUAL -#define ALL (M_PREPROC|M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_SPOT|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_HDR|M_SPOT|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_HDR|M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define ALLNORAW (M_INIT|M_LINDENOISE|M_HDR|M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define HDR (M_LINDENOISE|M_HDR|M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define SPOT (M_SPOT|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define FIRST (M_PREPROC|M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_MONITOR) // without HIGHQUAL +#define ALL (M_PREPROC|M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|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_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define FLATFIELD (M_PREPROC|M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define DEMOSAIC (M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define ALLNORAW (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define HDR (M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define SPOTADJUST (M_SPOT_ADJUST|M_HDR|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_HDR|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define AUTOEXP (M_SPOT|M_HDR|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) diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 3d59b9c41..80a21793c 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -7,6 +7,8 @@ #include #include "../rtengine/rt_math.h" #include "guiutils.h" +#include "eventmapper.h" +#include "../rtengine/refreshmap.h" using namespace rtengine; using namespace rtengine::procparams; @@ -64,6 +66,12 @@ Spot::Spot() : FoldableToolPanel (this, "spot", M ("TP_SPOT_LABEL"), true, true) link.datum = Geometry::IMAGE; link.setActive (false); + auto m = ProcEventMapper::getInstance(); + EvSpotEnabled = m->newEvent(ALLNORAW, "TP_SPOT_LABEL"); + EvSpotEnabledOPA = m->newEvent(SPOTADJUST, ""); + EvSpotEntry = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); + EvSpotEntryOPA = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); + show_all(); } @@ -135,7 +143,7 @@ void Spot::resetPressed() updateGeometry(); if (listener) { - listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)); + listener->panelChanged (edit->get_active() ? EvSpotEntryOPA : EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)); } } } @@ -167,16 +175,15 @@ void Spot::editedToggled () } } - void Spot::enabledChanged () { if (listener) { if (get_inconsistent()) { - listener->panelChanged (EvSpotEnabled, M ("GENERAL_UNCHANGED")); + listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_UNCHANGED")); } else if (getEnabled()) { - listener->panelChanged (EvSpotEnabled, M ("GENERAL_ENABLED")); + listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_ENABLED")); } else { - listener->panelChanged (EvSpotEnabled, M ("GENERAL_DISABLED")); + listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_DISABLED")); } } } @@ -188,10 +195,14 @@ void Spot::setEditProvider (EditDataProvider* provider) void Spot::editToggled () { - if (edit->get_active()) { - subscribe(); - } else { - unsubscribe(); + if (listener) { + if (edit->get_active()) { + listener->refreshPreview(EvSpotEnabledOPA); // reprocess the preview w/o creating History entry + subscribe(); + } else { + unsubscribe(); + listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry + } } } @@ -372,7 +383,7 @@ void Spot::addNewEntry() // 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")); + listener->panelChanged (EvSpotEntryOPA, M ("TP_SPOT_ENTRYCHANGED")); } } @@ -690,5 +701,6 @@ void Spot::switchOffEditMode () } EditSubscriber::switchOffEditMode(); // disconnect + listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry } diff --git a/rtgui/spot.h b/rtgui/spot.h index e719070a5..50c196051 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -98,6 +98,14 @@ public: bool pick2 (const bool picked); bool pick3 (const bool picked); void switchOffEditMode (); + + rtengine::ProcEvent EvSpotEnabled; + rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode + rtengine::ProcEvent EvSpotEntry; + rtengine::ProcEvent EvSpotEntryOPA; + + rtengine::ProcEvent evt; + }; #endif diff --git a/rtgui/toolpanel.h b/rtgui/toolpanel.h index 8f6595c08..cbf9a4226 100644 --- a/rtgui/toolpanel.h +++ b/rtgui/toolpanel.h @@ -35,6 +35,10 @@ class ToolPanelListener { public: virtual ~ToolPanelListener() = default; + + /// @brief Ask to refresh the preview not triggered by a parameter change (e.g. 'On Preview' editing). + virtual void refreshPreview(const rtengine::ProcEvent& event) = 0; + /// @brief Used to notify all listeners that a parameters has been effectively changed virtual void panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) = 0; }; diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index 691cfbcc6..c1143b781 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -379,6 +379,19 @@ void ToolPanelCoordinator::imageTypeChanged (bool isRaw, bool isBayer, bool isXt } +void ToolPanelCoordinator::refreshPreview (const rtengine::ProcEvent& event) +{ + if (!ipc) { + return; + } + + ProcParams* params = ipc->beginUpdateParams (); + for (auto toolPanel : toolPanels) { + toolPanel->write (params); + } + + ipc->endUpdateParams (event); // starts the IPC processing +} void ToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) { diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index c380e8475..2347a85ed 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -237,6 +237,7 @@ public: } // toolpanellistener interface + void refreshPreview(const rtengine::ProcEvent& event) override; void panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) override; void imageTypeChanged (bool isRaw, bool isBayer, bool isXtrans, bool isMono = false) override; From 0db64d49a29bd04a1d17ea25681fd92d699bd304 Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 6 Aug 2019 02:04:48 +0200 Subject: [PATCH 08/34] Introducing TweakOperator, for better tool's special mode handling --- rtengine/dcrop.cc | 9 +-- rtengine/improccoordinator.cc | 126 +++++++++++++++++++--------------- rtengine/improccoordinator.h | 11 ++- rtengine/refreshmap.h | 5 +- rtengine/rtengine.h | 16 ++++- rtengine/tweakoperator.h | 45 ++++++++++++ rtgui/batchtoolpanelcoord.cc | 8 +++ rtgui/batchtoolpanelcoord.h | 2 + rtgui/cropwindow.cc | 2 +- rtgui/spot.cc | 34 +++++++++ rtgui/spot.h | 6 +- rtgui/toolpanel.h | 4 ++ rtgui/toolpanelcoord.cc | 14 ++++ rtgui/toolpanelcoord.h | 3 + 14 files changed, 212 insertions(+), 73 deletions(-) create mode 100644 rtengine/tweakoperator.h diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index afd9e3774..8fdc6b5d1 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -142,6 +142,7 @@ void Crop::update(int todo) // give possibility to the listener to modify crop window (as the full image dimensions are already known at this point) int wx, wy, ww, wh, ws; const bool overrideWindow = cropImageListener; + bool spotsDone = false; if (overrideWindow) { cropImageListener->getWindow(wx, wy, ww, wh, ws); @@ -619,6 +620,7 @@ void Crop::update(int todo) } if ((todo & M_SPOT) && params.spot.enabled && !params.spot.entries.empty()) { + spotsDone = true; PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); parent->ipf.removeSpots(origCrop, params.spot.entries, pp); @@ -698,13 +700,8 @@ void Crop::update(int todo) // has to be called after setCropSizes! Tools prior to this point can't handle the Edit mechanism, but that shouldn't be a problem. createBuffer(cropw, croph); - if ((todo & M_SPOT_ADJUST) && params.spot.enabled && !params.spot.entries.empty()) { - PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); - parent->ipf.removeSpots(origCrop, params.spot.entries, pp); - } - // Apply Spot removal - if (todo & (M_SPOT|M_SPOT_ADJUST)) { + if ((todo & M_SPOT) && !spotsDone) { if (params.spot.enabled && !params.spot.entries.empty()) { if(!spotCrop) { spotCrop = new Imagefloat (cropw, croph); diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 91ba5b60c..c5b6952f5 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -25,6 +25,7 @@ #include "improcfun.h" #include "iccstore.h" #include "procparams.h" +#include "tweakoperator.h" #include #include #include @@ -130,6 +131,7 @@ ImProcCoordinator::ImProcCoordinator() : hListener(nullptr), resultValid(false), params(new procparams::ProcParams), + tweakOperator(nullptr), lastOutputProfile("BADFOOD"), lastOutputIntent(RI__COUNT), lastOutputBPC(false), @@ -200,9 +202,32 @@ void ImProcCoordinator::assign(ImageSource* imgsrc) this->imgsrc = imgsrc; } -void ImProcCoordinator::getParams(procparams::ProcParams* dst) +void ImProcCoordinator::getParams(procparams::ProcParams* dst, bool tweaked) { - *dst = *params; + if (!tweaked && paramsBackup.operator bool()) { + *dst = *paramsBackup; + } else { + *dst = *params; + } +} + +void ImProcCoordinator::backupParams() +{ + if (!params) { + return; + } + if (!paramsBackup) { + paramsBackup.reset(new ProcParams()); + } + *paramsBackup = *params; +} + +void ImProcCoordinator::restoreParams() +{ + if (!paramsBackup || !params) { + return; + } + *params = *paramsBackup; } DetailedCrop* ImProcCoordinator::createCrop(::EditDataProvider *editDataProvider, bool isDetailWindow) @@ -233,41 +258,6 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } } - if (todo & M_SPOT_ADJUST) { - // TWEAKING THE PROCPARAMS FOR THE SPOT ADJUSTMENT MODE - - // -> using fast demozaicing method - highDetailNeeded = false; - //params->raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); - //params->raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); - - // -> disabling all transform - //params->coarse = CoarseTransformParams(); - params->lensProf = LensProfParams(); - params->cacorrection = CACorrParams(); - params->distortion = DistortionParams(); - params->rotate = RotateParams(); - params->perspective = PerspectiveParams(); - params->vignetting = VignettingParams(); - - // -> disabling standard crop - params->crop.enabled = false; - - // -> disabling time consuming and unnecessary tool - params->sh.enabled = false; - params->blackwhite.enabled = false; - params->dehaze.enabled = false; - params->wavelet.enabled = false; - params->filmSimulation.enabled = false; - params->sharpenEdge.enabled = false; - params->sharpenMicro.enabled = false; - params->sharpening.enabled = false; - params->softlight.enabled = false; - params->gradient.enabled = false; - params->pcvignette.enabled = false; - params->colorappearance.enabled = false; - } - if (((todo & ALL) == ALL) || (todo & M_MONITOR) || panningRelatedChange || (highDetailNeeded && options.prevdemo != PD_Sidecar)) { bwAutoR = bwAutoG = bwAutoB = -9000.f; @@ -545,7 +535,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) progress ("Spot Removal...", 100 * readyphase / numofphases); - if (todo & (M_SPOT|M_SPOT_ADJUST)) { + if (todo & M_SPOT) { if (params->spot.enabled && !params->spot.entries.empty()) { allocCache(spotprev); orig_prev->copyData (spotprev); @@ -1089,10 +1079,21 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) delete oprevi; oprevi = nullptr; } - - } +void ImProcCoordinator::setTweakOperator (TweakOperator *tOperator) +{ + if (tOperator) { + tweakOperator = tOperator; + } +} + +void ImProcCoordinator::unsetTweakOperator (TweakOperator *tOperator) +{ + if (tOperator && tOperator == tweakOperator) { + tweakOperator = nullptr; + } +} void ImProcCoordinator::freeAll() { @@ -1432,35 +1433,37 @@ void ImProcCoordinator::saveInputICCReference(const Glib::ustring& fname, bool a MyMutex::MyLock lock(mProcessing); int fW, fH; + std::unique_ptr validParams(new ProcParams()); + getParams(validParams.get()); - int tr = getCoarseBitMask(params->coarse); + int tr = getCoarseBitMask(validParams->coarse); imgsrc->getFullSize(fW, fH, tr); PreviewProps pp(0, 0, fW, fH, 1); - ProcParams ppar = *params; + ProcParams ppar = *validParams; ppar.toneCurve.hrenabled = false; ppar.icm.inputProfile = "(none)"; Imagefloat* im = new Imagefloat(fW, fH); imgsrc->preprocess(ppar.raw, ppar.lensProf, ppar.coarse); double dummy = 0.0; imgsrc->demosaic(ppar.raw, false, dummy); - ColorTemp currWB = ColorTemp(params->wb.temperature, params->wb.green, params->wb.equal, params->wb.method); + ColorTemp currWB = ColorTemp(validParams->wb.temperature, validParams->wb.green, validParams->wb.equal, validParams->wb.method); - if (params->wb.method == "Camera") { + if (validParams->wb.method == "Camera") { currWB = imgsrc->getWB(); - } else if (params->wb.method == "Auto") { - if (lastAwbEqual != params->wb.equal || lastAwbTempBias != params->wb.tempBias) { + } else if (validParams->wb.method == "Auto") { + if (lastAwbEqual != validParams->wb.equal || lastAwbTempBias != validParams->wb.tempBias) { double rm, gm, bm; imgsrc->getAutoWBMultipliers(rm, gm, bm); if (rm != -1.) { - autoWB.update(rm, gm, bm, params->wb.equal, params->wb.tempBias); - lastAwbEqual = params->wb.equal; - lastAwbTempBias = params->wb.tempBias; + autoWB.update(rm, gm, bm, validParams->wb.equal, validParams->wb.tempBias); + lastAwbEqual = validParams->wb.equal; + lastAwbTempBias = validParams->wb.tempBias; } else { lastAwbEqual = -1.; lastAwbTempBias = 0.0; - autoWB.useDefaults(params->wb.equal); + autoWB.useDefaults(validParams->wb.equal); } } @@ -1482,12 +1485,12 @@ void ImProcCoordinator::saveInputICCReference(const Glib::ustring& fname, bool a im = trImg; } - if (params->crop.enabled) { - Imagefloat *tmpim = new Imagefloat(params->crop.w, params->crop.h); - int cx = params->crop.x; - int cy = params->crop.y; - int cw = params->crop.w; - int ch = params->crop.h; + if (validParams->crop.enabled) { + Imagefloat *tmpim = new Imagefloat(validParams->crop.w, validParams->crop.h); + int cx = validParams->crop.x; + int cy = validParams->crop.y; + int cw = validParams->crop.w; + int ch = validParams->crop.h; #ifdef _OPENMP #pragma omp parallel for #endif @@ -1518,7 +1521,7 @@ void ImProcCoordinator::saveInputICCReference(const Glib::ustring& fname, bool a } int imw, imh; - double tmpScale = ipf.resizeScale(params.get(), fW, fH, imw, imh); + double tmpScale = ipf.resizeScale(validParams.get(), fW, fH, imw, imh); if (tmpScale != 1.0) { Imagefloat* tempImage = new Imagefloat(imw, imh); @@ -1628,6 +1631,15 @@ void ImProcCoordinator::process() *params = *nextParams; int change = changeSinceLast; changeSinceLast = 0; + + if (tweakOperator) { + // TWEAKING THE PROCPARAMS FOR THE SPOT ADJUSTMENT MODE + backupParams(); + tweakOperator->tweakParams(*params); + } else if (paramsBackup) { + paramsBackup.release(); + } + paramsUpdateMutex.unlock(); // M_VOID means no update, and is a bit higher that the rest diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index dac034abf..e66867d2b 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -37,6 +37,7 @@ namespace rtengine using namespace procparams; class Crop; +class TweakOperator; /** @brief Manages the image processing, espc. of the preview windows * @@ -180,14 +181,20 @@ protected: MyMutex minit; // to gain mutually exclusive access to ... to what exactly? void progress (Glib::ustring str, int pr); + void backupParams(); + void restoreParams(); void reallocAll (); void allocCache (Imagefloat* &imgfloat); void updateLRGBHistograms (); void setScale (int prevscale); void updatePreviewImage (int todo, bool panningRelatedChange); + void setTweakOperator (TweakOperator *tOperator); + void unsetTweakOperator (TweakOperator *tOperator); MyMutex mProcessing; - const std::unique_ptr params; + const std::unique_ptr params; // used for the rendering, can be eventually tweaked + std::unique_ptr paramsBackup; // backup of the untweaked procparams + TweakOperator* tweakOperator; // for optimization purpose, the output profile, output rendering intent and // output BPC will trigger a regeneration of the profile on parameter change only @@ -228,7 +235,7 @@ public: ~ImProcCoordinator () override; void assign (ImageSource* imgsrc); - void getParams (procparams::ProcParams* dst) override; + void getParams (procparams::ProcParams* dst, bool tweaked=false) override; void startProcessing (int changeCode) override; ProcParams* beginUpdateParams () override; diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h index 114e07b2e..1de3bd250 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -22,9 +22,6 @@ #include #include "procevents.h" -// Use M_SPOT_ADJUST to update the rendering for On Preview Adjustment of the Spot tool -#define M_SPOT_ADJUST (1<<19) - // Use M_VOID if you wish to update the proc params without updating the preview at all ! #define M_VOID (1<<18) // Use M_MINUPDATE if you wish to update the preview without modifying the image (think about it like a "refreshPreview") @@ -61,7 +58,7 @@ #define DEMOSAIC (M_RAW|M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define ALLNORAW (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define HDR (M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define SPOTADJUST (M_SPOT_ADJUST|M_HDR|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) +#define SPOTADJUST (M_SPOT|M_HDR|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_SPOT|M_HDR|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) #define RGBCURVE (M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index f772975b0..e821c25c7 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -70,6 +70,7 @@ class IImage8; class IImage16; class IImagefloat; class ImageSource; +class TweakOperator; /** * This class provides functions to obtain exif and IPTC metadata information @@ -454,9 +455,20 @@ public: /** Returns the initial image corresponding to the image processor. * @return the initial image corresponding to the image processor */ virtual InitialImage* getInitialImage () = 0; + /** Set the TweakOperator + * @param tOperator is a pointer to the object that will alter the ProcParams for the rendering */ + virtual void setTweakOperator (TweakOperator *tOperator) = 0; + /** Unset the TweakOperator + * @param tOperator is a pointer to the object that were altering the ProcParams for the rendering + * It will only unset the tweak operator if tOperator is the same than the currently set operator. + * If it doesn't match, the currently set TweakOperator will remain set. */ + virtual void unsetTweakOperator (TweakOperator *tOperator) = 0; /** Returns the current processing parameters. - * @param dst is the location where the image processing parameters are copied (it is assumed that the memory is allocated by the caller) */ - virtual void getParams (procparams::ProcParams* dst) = 0; + * Since the ProcParams can be tweaked by a GUI to operate on the image at a specific stage or with disabled tool, + * you'll have to specify if you want the tweaked version for the current special mode, or the untweaked one. + * @param dst is the location where the image processing parameters are copied (it is assumed that the memory is allocated by the caller) + * @param tweaked is used to chose betwen the tweaked ProcParams (if there is one) or the untweaked one */ + virtual void getParams (procparams::ProcParams* dst, bool tweaked=false) = 0; /** An essential member function. Call this when a setting has been changed. This function returns a pointer to the * processing parameters, that you have to update to reflect the changed situation. When ready, call the paramsUpdateReady * function to start the image update. diff --git a/rtengine/tweakoperator.h b/rtengine/tweakoperator.h new file mode 100644 index 000000000..e75ea89f2 --- /dev/null +++ b/rtengine/tweakoperator.h @@ -0,0 +1,45 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 . + */ +#pragma once + +namespace rtengine +{ + +namespace procparams +{ + +class ProcParams; + +} + +/** This class can let objects alter the collected values of the ProcParams for a specific + * purpose, e.g. displaying a preview image at a specific point in the pipeline or with + * disabled tools. Before starting the rendering, the engine will call the TweakOperator + * (if set) to modify the ProcParams. The untweaked one will still exist as a backup, and + * can be sent back if necessary. */ +class TweakOperator +{ +public: + virtual ~TweakOperator() {} + + /** Callback that will alter the ProcParams before hte image is computed. */ + virtual void tweakParams(procparams::ProcParams& pparams) = 0; +}; + +} diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 1a0eaeb28..c493897f0 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -558,6 +558,14 @@ void BatchToolPanelCoordinator::panelChanged(const rtengine::ProcEvent& event, c } } +void BatchToolPanelCoordinator::setTweakOperator (rtengine::TweakOperator *tOperator) +{ +} + +void BatchToolPanelCoordinator::unsetTweakOperator (rtengine::TweakOperator *tOperator) +{ +} + void BatchToolPanelCoordinator::getAutoWB (double& temp, double& green, double equal, double tempBias) { diff --git a/rtgui/batchtoolpanelcoord.h b/rtgui/batchtoolpanelcoord.h index f5889f967..f3183b746 100644 --- a/rtgui/batchtoolpanelcoord.h +++ b/rtgui/batchtoolpanelcoord.h @@ -55,6 +55,8 @@ public: // toolpanellistener interface void panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) override; + void setTweakOperator (rtengine::TweakOperator *tOperator) override; + void unsetTweakOperator (rtengine::TweakOperator *tOperator) override; // profilechangelistener interface void profileChange( diff --git a/rtgui/cropwindow.cc b/rtgui/cropwindow.cc index b29aa5eeb..ed40700a3 100644 --- a/rtgui/cropwindow.cc +++ b/rtgui/cropwindow.cc @@ -1092,7 +1092,7 @@ void CropWindow::pointerMoved (int bstate, int x, int y) rtengine::StagedImageProcessor* ipc = iarea->getImProcCoordinator(); if(ipc) { procparams::ProcParams params; - ipc->getParams(¶ms); + ipc->getParams(¶ms, true); isRaw = params.raw.bayersensor.method == RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::NONE) || params.raw.xtranssensor.method == RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::NONE); if(isRaw) { ImageSource *isrc = static_cast(ipc->getInitialImage()); diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 80a21793c..9f7830a15 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -197,10 +197,12 @@ void Spot::editToggled () { if (listener) { if (edit->get_active()) { + listener->setTweakOperator(this); listener->refreshPreview(EvSpotEnabledOPA); // reprocess the preview w/o creating History entry subscribe(); } else { unsubscribe(); + listener->unsetTweakOperator(this); listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry } } @@ -701,6 +703,38 @@ void Spot::switchOffEditMode () } EditSubscriber::switchOffEditMode(); // disconnect + listener->unsetTweakOperator(this); listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry } +void Spot::tweakParams(procparams::ProcParams& pparams) +{ + //params->raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST); + //params->raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); + + // -> disabling all transform + //params->coarse = CoarseTransformParams(); + pparams.lensProf = LensProfParams(); + pparams.cacorrection = CACorrParams(); + pparams.distortion = DistortionParams(); + pparams.rotate = RotateParams(); + pparams.perspective = PerspectiveParams(); + pparams.vignetting = VignettingParams(); + + // -> disabling standard crop + pparams.crop.enabled = false; + + // -> disabling time consuming and unnecessary tool + pparams.sh.enabled = false; + pparams.blackwhite.enabled = false; + pparams.dehaze.enabled = false; + pparams.wavelet.enabled = false; + pparams.filmSimulation.enabled = false; + pparams.sharpenEdge.enabled = false; + pparams.sharpenMicro.enabled = false; + pparams.sharpening.enabled = false; + pparams.softlight.enabled = false; + pparams.gradient.enabled = false; + pparams.pcvignette.enabled = false; + pparams.colorappearance.enabled = false; +} diff --git a/rtgui/spot.h b/rtgui/spot.h index 50c196051..b163848c0 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -8,6 +8,7 @@ #include "toolpanel.h" #include "editwidgets.h" #include "../rtengine/procparams.h" +#include "../rtengine/tweakoperator.h" /** * @brief Let the user create/edit/delete points for Spot Removal tool @@ -36,7 +37,7 @@ * (the point will be deleted on button release). */ -class Spot : public ToolParamBlock, public FoldableToolPanel, public EditSubscriber +class Spot : public ToolParamBlock, public FoldableToolPanel, public EditSubscriber, public rtengine::TweakOperator { private: @@ -99,6 +100,9 @@ public: bool pick3 (const bool picked); void switchOffEditMode (); + //TweakOperator interface + void tweakParams(rtengine::procparams::ProcParams& pparams) override; + rtengine::ProcEvent EvSpotEnabled; rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode rtengine::ProcEvent EvSpotEntry; diff --git a/rtgui/toolpanel.h b/rtgui/toolpanel.h index cbf9a4226..f2d5890a9 100644 --- a/rtgui/toolpanel.h +++ b/rtgui/toolpanel.h @@ -40,6 +40,10 @@ public: virtual void refreshPreview(const rtengine::ProcEvent& event) = 0; /// @brief Used to notify all listeners that a parameters has been effectively changed virtual void panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) = 0; + /// @brief Set the TweakOperator to the StagedImageProcessor, to let some tool enter into special modes + virtual void setTweakOperator (rtengine::TweakOperator *tOperator) = 0; + /// @brief Unset the TweakOperator to the StagedImageProcessor + virtual void unsetTweakOperator (rtengine::TweakOperator *tOperator) = 0; }; /// @brief This class control the space around the group of tools inside a tab, as well as the space separating each tool. */ diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc index c1143b781..af6c3e507 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -379,6 +379,20 @@ void ToolPanelCoordinator::imageTypeChanged (bool isRaw, bool isBayer, bool isXt } +void ToolPanelCoordinator::setTweakOperator (rtengine::TweakOperator *tOperator) +{ + if (ipc && tOperator) { + ipc->setTweakOperator(tOperator); + } +} + +void ToolPanelCoordinator::unsetTweakOperator (rtengine::TweakOperator *tOperator) +{ + if (ipc && tOperator) { + ipc->unsetTweakOperator(tOperator); + } +} + void ToolPanelCoordinator::refreshPreview (const rtengine::ProcEvent& event) { if (!ipc) { diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h index 2347a85ed..3789f1610 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -239,7 +239,10 @@ public: // toolpanellistener interface void refreshPreview(const rtengine::ProcEvent& event) override; void panelChanged(const rtengine::ProcEvent& event, const Glib::ustring& descr) override; + void setTweakOperator (rtengine::TweakOperator *tOperator) override; + void unsetTweakOperator (rtengine::TweakOperator *tOperator) override; + // FilmNegProvider interface void imageTypeChanged (bool isRaw, bool isBayer, bool isXtrans, bool isMono = false) override; // void autoContrastChanged (double autoContrast); From 122e0b89bedf3f5c1cb9b3ed971e3b620c78e5a7 Mon Sep 17 00:00:00 2001 From: Hombre Date: Fri, 9 Aug 2019 03:24:46 +0200 Subject: [PATCH 09/34] First functionnal version of Spot-Removal tool Still needs code cleanup, better algorithm and reantrance handling --- rtengine/dcrop.cc | 6 +- rtengine/improccoordinator.cc | 7 +- rtengine/improcfun.h | 2 +- rtengine/procparams.cc | 5 + rtengine/procparams.h | 1 + rtengine/simpleprocess.cc | 2 +- rtengine/spot.cc | 317 ++++++++++++++++++++++++++++++++-- 7 files changed, 313 insertions(+), 27 deletions(-) diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 8fdc6b5d1..72f5c6d72 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -623,7 +623,7 @@ void Crop::update(int todo) spotsDone = true; PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); - parent->ipf.removeSpots(origCrop, params.spot.entries, pp); + parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr); } DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise; @@ -708,8 +708,8 @@ void Crop::update(int todo) } baseCrop->copyData (spotCrop); PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip); - //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); - parent->ipf.removeSpots (spotCrop, params.spot.entries, pp); + int tr = getCoarseBitMask(params.coarse); + parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr); } else { if (spotCrop) { delete spotCrop; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index c5b6952f5..2c49bdef1 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -538,10 +538,9 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) if (todo & M_SPOT) { if (params->spot.enabled && !params->spot.entries.empty()) { allocCache(spotprev); - orig_prev->copyData (spotprev); - + orig_prev->copyData(spotprev); PreviewProps pp(0, 0, fw, fh, scale); - ipf.removeSpots (spotprev, params->spot.entries, pp); + ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, tr); } else { if (spotprev) { delete spotprev; @@ -551,7 +550,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } if (spotprev) { if (oprevi == orig_prev) { - allocCache(oprevi); + oprevi = new Imagefloat(pW, pH); } spotprev->copyData(oprevi); } diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index dc5f6939a..5700a354b 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -333,7 +333,7 @@ public: float MadRgb(const float * DataList, int datalen); // spot removal tool - void removeSpots (Imagefloat* img, const std::vector &entries, const PreviewProps &pp); + void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr); // pyramid wavelet void dirpyr_equalizer(float ** src, float ** dst, int srcwidth, int srcheight, float ** l_a, float ** l_b, const double * mult, const double dirpyrThreshold, const double skinprot, float b_l, float t_l, float t_r, int scale); //Emil's directional pyramid wavelet diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 62fefbb69..1adc9c8e5 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -1551,6 +1551,11 @@ SpotEntry::SpotEntry() : { } +float SpotEntry::getFeatherRadius() const +{ + return radius * (1.f + feather); +} + bool SpotEntry::operator ==(const SpotEntry& other) const { return other.sourcePos == sourcePos && other.targetPos == targetPos && diff --git a/rtengine/procparams.h b/rtengine/procparams.h index 129310660..de04e5636 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -1039,6 +1039,7 @@ struct SpotEntry { float opacity; SpotEntry(); + float getFeatherRadius() const; bool operator ==(const SpotEntry& other) const; bool operator !=(const SpotEntry& other) const; diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index dc3f7c125..38ccb5133 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -927,7 +927,7 @@ private: // Spot Removal if (params.spot.enabled && !params.spot.entries.empty ()) { - ipf.removeSpots (baseImg, params.spot.entries, pp); + ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, tr); } // RGB processing diff --git a/rtengine/spot.cc b/rtengine/spot.cc index f23fb036d..60bfb2f6c 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -20,6 +20,7 @@ #include "improcfun.h" #include "alpha.h" #include "procparams.h" +#include "imagesource.h" namespace rtengine { @@ -49,31 +50,305 @@ namespace rtengine * 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) + +#if 1 + +class SpotBox { + +public: + enum class Type { + SOURCE, + TARGET + }; + +private: + Type type; + +public: + int topLeftX; + int topLeftY; + int bottomRightX; + int bottomRightY; + Imagefloat* img; + + SpotBox (int tl_x, int tl_y, int br_x, int br_y, Type type) : + type(type), + topLeftX(tl_x), + topLeftY(tl_y), + bottomRightX(br_x), + bottomRightY(br_y), + img(nullptr) + {} + + SpotBox (int tl_x, int tl_y, Imagefloat* image, Type type) : + type(type), + topLeftX(tl_x), + topLeftY(tl_y), + bottomRightX(image ? tl_x + image->getWidth() - 1 : 0), + bottomRightY(image ? tl_y + image->getHeight() - 1 : 0), + img(image) + {} + + SpotBox (SpotEntry &spot, Type type) : + type(type), + img(nullptr) + { + float featherRadius = spot.radius * (1.f + spot.feather); + topLeftX = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) - featherRadius); + bottomRightX = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) + featherRadius); + topLeftY = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) - featherRadius); + bottomRightY = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) + featherRadius); + } + + void translate(int dx, int dy) { + topLeftX += dx; + topLeftY += dy; + bottomRightX += dx; + bottomRightY += dy; + } + + void operator /(float v) { + topLeftX = int(topLeftX / v + 0.5f); + topLeftY = int(topLeftY / v + 0.5f); + bottomRightX = int(bottomRightX / v + 0.5f); + bottomRightY = int(bottomRightY / v + 0.5f); + } + + void operator *(float v) { + topLeftX *= v; + topLeftY *= v; + bottomRightX *= v; + bottomRightY *= v; + } + + bool intersects(const SpotBox &other) const { + return (other.topLeftX <= bottomRightX && other.bottomRightX >= topLeftX) + && (other.topLeftY <= bottomRightY && other.bottomRightY >= topLeftY); + } + + int getWidth() { + return bottomRightX - topLeftX + 1; + } + + int getHeight() { + return bottomRightY - topLeftY + 1; + } +}; + + +void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) { + // ---------- Get the image areas (src & dst) from the source image + + printf("\n=======================================================================\n\n"); + + std::vector< std::shared_ptr > srcSpotBoxs; + std::vector< std::shared_ptr > dstSpotBoxs; + for (auto entry : params->spot.entries) { + Coord origin; + int size = int(entry.getFeatherRadius() * 2.f + 0.5f); + int scaledSize = int(entry.getFeatherRadius() * 2.f / float(pp.getSkip()) + 0.5f); + //printf("size: %d - skip: %d -> scaledSize: %d", size, pp.getSkip(), scaledSize); + + // ------ Source area + Imagefloat *currSrcSpot = new Imagefloat(scaledSize, scaledSize); + for (int y = 0; y < currSrcSpot->getHeight(); ++y) { + for (int x = 0; x < currSrcSpot->getWidth(); ++x) { + currSrcSpot->r(y,x) = 0.f; + currSrcSpot->g(y,x) = 0.f; + currSrcSpot->b(y,x) = 0.f; + } + } + entry.sourcePos.get(origin.x, origin.y); + origin.x -= entry.getFeatherRadius(); + origin.y -= entry.getFeatherRadius(); + PreviewProps spp(origin.x, origin.y, size, size, pp.getSkip()); + imgsrc->getImage(currWB, tr, currSrcSpot, spp, params->toneCurve, params->raw); + //printf(" / src size: %d,%d", currSrcSpot->getWidth(), currSrcSpot->getHeight()); + + std::shared_ptr srcSpotBox(new SpotBox(origin.x / pp.getSkip(), origin.y / pp.getSkip(), currSrcSpot, SpotBox::Type::SOURCE)); + srcSpotBoxs.push_back(srcSpotBox); + + // ------ Destination area + Imagefloat *currDstSpot = new Imagefloat(scaledSize, scaledSize); + for (int y = 0; y < currDstSpot->getHeight(); ++y) { + for (int x = 0; x < currDstSpot->getWidth(); ++x) { + currDstSpot->r(y,x) = 0.f; + currDstSpot->g(y,x) = 0.f; + currDstSpot->b(y,x) = 0.f; + } + } + entry.targetPos.get(origin.x, origin.y); + origin.x -= entry.getFeatherRadius(); + origin.y -= entry.getFeatherRadius(); + spp.set(origin.x, origin.y, size, size, pp.getSkip()); + imgsrc->getImage(currWB, tr, currDstSpot, spp, params->toneCurve, params->raw); + //printf(" / dst size: %d,%d\n", currDstSpot->getWidth(), currDstSpot->getHeight()); + + std::shared_ptr dstSpotBox(new SpotBox(origin.x / pp.getSkip(), origin.y / pp.getSkip(), currDstSpot, SpotBox::Type::TARGET)); + + dstSpotBoxs.push_back(dstSpotBox); + } + + // Filter out out of preview Spots + + /* + for (size_t i = entries.size(); i >= 0; ++i) { + float featherRadius = entries.at(i).radius * (1.f + entries.at(i).feather); + + SpotBox srcBox(entries.at(i), SpotBox::Type::SOURCE); + srcBox.translate(-pp.getX(), -pp.getY()); + srcBox /= float (pp.getSkip()); + + SpotBox dstBox(entries.at(i), SpotBox::Type::TARGET); + dstBox.translate(-pp.getX(), -pp.getY()); + dstBox /= float (pp.getSkip()); + + } + */ + + + + // ---------- Copy spots from src to dst + + for (int i = entries.size() - 1; i >= 0; --i) { + // 1. copy src to dst + std::shared_ptr srcSpotBox = srcSpotBoxs.at(i); + std::shared_ptr dstSpotBox = dstSpotBoxs.at(i); + float scaledRadius = float(entries.at(i).radius) / float(pp.getSkip()); + float scaledFeatherRadius = entries.at(i).getFeatherRadius() / float(pp.getSkip()); + Imagefloat *srcImg = srcSpotBox->img; + Imagefloat *dstImg = dstSpotBox->img; + + //printf("#%d: srcSpotBox @ %p - img @ %p / dstSpotBox @ %p - img @ %p\n", i, + // srcSpotBox.get(), srcSpotBox->img, dstSpotBox.get(), dstSpotBox->img ); + + //printf("#%d: srcSpotBox(%d,%d) srcImg(%d,%d) / dstSpotBox(%d,%d) dstImg(%d,%d)\n", i, + // srcSpotBox->getWidth(), srcSpotBox->getHeight(), srcImg->getWidth(), srcImg->getHeight(), + // dstSpotBox->getWidth(), dstSpotBox->getHeight(), dstImg->getWidth(), dstImg->getHeight() + // ); + + for (int y = 0; y < srcSpotBox->getHeight(); ++y) { + float dy = float(y - float(srcSpotBox->getHeight()) / 2.f); + for (int x = 0; x < srcSpotBox->getWidth(); ++x) { + float dx = float(x - float(srcSpotBox->getWidth()) / 2.f); + float r = sqrt(dx * dx + dy * dy); + if (r >= scaledFeatherRadius) { + continue; + } + if (r <= scaledRadius) { + dstImg->r(y, x) = srcImg->r(y, x); + dstImg->g(y, x) = srcImg->g(y, x); + dstImg->b(y, x) = srcImg->b(y, x); + } else { + float opacity = (scaledFeatherRadius - r) / (scaledFeatherRadius - scaledRadius); + dstImg->r(y, x) = (srcImg->r(y, x) - dstImg->r(y, x)) * opacity + dstImg->r(y,x); + dstImg->g(y, x) = (srcImg->g(y, x) - dstImg->g(y, x)) * opacity + dstImg->g(y,x); + dstImg->b(y, x) = (srcImg->b(y, x) - dstImg->b(y, x)) * opacity + dstImg->b(y,x); + } + } + } + //printf("\n\n"); + + // 2. copy dst to later src and dst + + } + + // 3. copy all dst to the finale image + + // Putting the dest image in a SpotBox + SpotBox imgSpotBox(pp.getX() / pp.getSkip(), pp.getY() / pp.getSkip(), img, SpotBox::Type::TARGET); + /* + printf("#--: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n\n", + imgSpotBox.topLeftX, imgSpotBox.topLeftY, imgSpotBox.bottomRightX, imgSpotBox.bottomRightY, + imgSpotBox.getWidth(), imgSpotBox.getHeight(), + imgSpotBox.img->getWidth(), imgSpotBox.img->getHeight() + ); + */ + + for (size_t i = 0; i < entries.size(); ++i) { + // 1. copy src to dst + std::shared_ptr dstSpotBox = dstSpotBoxs.at(i); + Imagefloat *dstImg = dstSpotBox->img; + + /* + printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i, + dstSpotBox->topLeftX, dstSpotBox->topLeftY, dstSpotBox->bottomRightX, dstSpotBox->bottomRightY, + dstSpotBox->getWidth(), dstSpotBox->getHeight(), + dstImg->getWidth(), dstImg->getHeight() + ); + */ + + if (dstSpotBox->intersects(imgSpotBox)) { + int beginX = rtengine::max(dstSpotBox->topLeftX, imgSpotBox.topLeftX); + int endX = rtengine::min(dstSpotBox->bottomRightX, imgSpotBox.bottomRightX); + int beginY = rtengine::max(dstSpotBox->topLeftY, imgSpotBox.topLeftY); + int endY = rtengine::min(dstSpotBox->bottomRightY, imgSpotBox.bottomRightY); + + //printf("--- Intersection: X1:%d, Y1:%d -> X2:%d, Y2:%d\n", beginX, beginY, endX, endY); + + int dstSpotOffsetY = beginY - dstSpotBox->topLeftY; + int imgOffsetY = beginY - imgSpotBox.topLeftY; + + for (int y = beginY; y <= endY; ++y) { + int dstSpotOffsetX = beginX - dstSpotBox->topLeftX; + int imgOffsetX = beginX - imgSpotBox.topLeftX; + + for (int x = beginX; x <= endX; ++x) { + /* + if (y == beginY && x == beginX) { + printf("--- dstSpotOffsetX = beginX - dstSpotBox->topLeftX = %d - %d = %d\n", beginX, dstSpotBox->topLeftX, dstSpotOffsetX); + printf("--- dstSpotOffsetY = beginY - dstSpotBox->topLeftY = %d - %d = %d\n", beginY, dstSpotBox->topLeftY, dstSpotOffsetY); + printf("--- imgOffsetX = beginX - imgSpotBox.topLeftX = %d - %d = %d\n", beginX, imgSpotBox.topLeftX, imgOffsetX); + printf("--- imgOffsetX = beginY - imgSpotBox.topLeftY = %d - %d = %d\n", beginY, imgSpotBox.topLeftY, imgOffsetY); + } + */ + img->r(imgOffsetY, imgOffsetX) = dstImg->r(dstSpotOffsetY, dstSpotOffsetX); + img->g(imgOffsetY, imgOffsetX) = dstImg->g(dstSpotOffsetY, dstSpotOffsetX); + img->b(imgOffsetY, imgOffsetX) = dstImg->b(dstSpotOffsetY, dstSpotOffsetX); + ++imgOffsetX; + ++dstSpotOffsetX; + } + ++imgOffsetY; + ++dstSpotOffsetY; + } + //} else { + // printf("#%llu: No intersection !\n", i); + } + } + + for (auto srcSpotBox : srcSpotBoxs) { + delete srcSpotBox->img; + } + for (auto dstSpotBox : dstSpotBoxs) { + delete dstSpotBox->img; + } +} + +#endif + + + + + +#if 0 +void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) +{ + 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); + float featherRadius = entry.getFeatherRadius(); int scaledFeatherRadius = featherRadius / pp.getSkip (); - int src_XMin = int ((srcX - featherRadius - pp.getX()) / float (pp.getSkip()) + 0.5f); - int src_XMax = int ((srcX + featherRadius - pp.getX()) / float (pp.getSkip()) + 0.5f); - int src_YMin = int ((srcY - featherRadius - pp.getY()) / float (pp.getSkip()) + 0.5f); - int src_YMax = int ((srcY + featherRadius - pp.getY()) / float (pp.getSkip()) + 0.5f); + SpotBox srcBox(entry, SpotBox::Type::SOURCE); + srcBox.translate(-pp.getX(), -pp.getY()); + srcBox /= float (pp.getSkip()); - int dst_XMin = int ((dstX - featherRadius - pp.getX()) / float (pp.getSkip()) + 0.5f); - int dst_XMax = int ((dstX + featherRadius - pp.getX()) / float (pp.getSkip()) + 0.5f); - int dst_YMin = int ((dstY - featherRadius - pp.getY()) / float (pp.getSkip()) + 0.5f); - int dst_YMax = int ((dstY + featherRadius - pp.getY()) / float (pp.getSkip()) + 0.5f); + SpotBox dstBox(entry, SpotBox::Type::TARGET); + dstBox.translate(-pp.getX(), -pp.getY()); + dstBox /= float (pp.getSkip()); //printf(" -> X: %04d > %04d\n -> Y: %04d > %04d\n", dst_XMin, dst_XMax, dst_YMin, dst_YMax); @@ -101,7 +376,7 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector #endif } - // skipping entries where the source circle isn't completely inside the image bounds + // skipping entries where the source circle isn't inside the image bounds, even partially if (src_XMin < 0 || src_XMax >= img->getWidth() || src_YMin < 0 || src_YMax >= img->getHeight()) { #ifndef NDEBUG @@ -116,6 +391,7 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector } // skipping entries where the dest circle is completely outside the image bounds + /* if (dst_XMin >= img->getWidth() || dst_XMax <= 0 || dst_YMin >= img->getHeight() || dst_YMax <= 0) { #ifndef NDEBUG @@ -128,6 +404,7 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector #endif continue; } + */ // ----------------- Core function ----------------- @@ -347,6 +624,9 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector printf ("\n"); #endif + + + // add solution to original image and store in tempPR for (int i = 0, i2 = dst_YMin; i2 < dst_YMax - 1; ++i, ++i2) { if (i2 < 0 || i2 >= img->getHeight()) { @@ -376,6 +656,7 @@ void ImProcFunctions::removeSpots (Imagefloat* img, const std::vector } } +#endif } From 4d4f54cbc24033fe1bf32c34657847ff07022c1e Mon Sep 17 00:00:00 2001 From: Hombre Date: Wed, 14 Aug 2019 15:52:22 +0200 Subject: [PATCH 10/34] Spot-Removal (#2239): Adding recusivity and border handling Warning: Debug builds will be slow due to the amount of debug output (no problem for Release builds). Code cleanup will be done when after testing phase. --- rtengine/simpleprocess.cc | 13 +- rtengine/spot.cc | 757 +++++++++++++++++++++++++++----------- rtgui/spot.cc | 16 + rtgui/spot.h | 16 + 4 files changed, 586 insertions(+), 216 deletions(-) diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 38ccb5133..6934cc8e8 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -781,7 +781,12 @@ private: params.toneCurve.brightness = 0; params.toneCurve.contrast = 0; params.toneCurve.black = 0; - } + } + + // Spot Removal + if (params.spot.enabled && !params.spot.entries.empty ()) { + ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, tr); + } // at this stage, we can flush the raw data to free up quite an important amount of memory // commented out because it makes the application crash when batch processing... @@ -924,12 +929,6 @@ private: } } - // Spot Removal - - if (params.spot.enabled && !params.spot.entries.empty ()) { - ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, tr); - } - // RGB processing curve1 (65536); diff --git a/rtengine/spot.cc b/rtengine/spot.cc index 60bfb2f6c..2090324dd 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -21,6 +21,19 @@ #include "alpha.h" #include "procparams.h" #include "imagesource.h" +#include + +namespace +{ + +// "ceil" rounding +template +constexpr T skips(T a, T b) +{ + return a / b + static_cast(a % b); +} + +} namespace rtengine { @@ -58,271 +71,597 @@ class SpotBox { public: enum class Type { SOURCE, - TARGET + TARGET, + FINAL }; + struct Rectangle { + public: + int x1; + int y1; + int x2; + int y2; + + Rectangle() : x1(0), y1(0), x2(0), y2(0) {} + Rectangle(const Rectangle &other) : x1(other.x1), y1(other.y1), x2(other.x2), y2(other.y2) {} + Rectangle(int X1, int Y1, int X2, int Y2) : x1(X1), y1(Y1), x2(X2), y2(Y2) {} + + bool intersects(const Rectangle &other) const { + return (other.x1 <= x2 && other.x2 >= x1) + && (other.y1 <= y2 && other.y2 >= y1); + } + + bool getIntersection(const Rectangle &other, std::unique_ptr &intersection) const { + if (intersects(other)) { + if (!intersection) { + intersection.reset(new Rectangle()); + } + intersection->x1 = rtengine::max(x1, other.x1); + intersection->x2 = rtengine::min(x2, other.x2); + intersection->y1 = rtengine::max(y1, other.y1); + intersection->y2 = rtengine::min(y2, other.y2); + + if (intersection->x1 > intersection->x2 || intersection->y1 > intersection->y2) { + intersection.release(); + return false; + } + return true; + } + if (intersection) { + intersection.release(); + } + return false; + } + + Rectangle& operator+=(const Coord &v) { + x1 += v.x; + y1 += v.y; + x2 += v.x; + y2 += v.y; + return *this; + } + + Rectangle& operator-=(const Coord &v) { + x1 -= v.x; + y1 -= v.y; + x2 -= v.x; + y2 -= v.y; + return *this; + } + + Rectangle& operator/=(const int &v) { + if (v == 1) { + return *this; + } + + /* + float fv = float(v); + x1 = int(float(x1) / fv + 0.5f); + y1 = int(float(y1) / fv + 0.5f); + x2 = int(float(x2) / fv + 0.5f); + y2 = int(float(y2) / fv + 0.5f); + */ + + // Aletrnate rounding possibility + int w = x2 - x1 + 1; + int h = x2 - x1 + 1; + w = w / v + (w % v > 0); + h = h / v + (h % v > 0); + x1 /= v; + y1 /= v; + x2 = x1 + w - 1; + y2 = y1 + h - 1; + + return *this; + } +}; + private: Type type; + Imagefloat* image; public: - int topLeftX; - int topLeftY; - int bottomRightX; - int bottomRightY; - Imagefloat* img; + // top/left and bottom/right coordinates of the spot in image space (at some point divided by scale factor) + Rectangle spotArea; + // top/left and bottom/right coordinates of the spot in scaled image space (on borders, imgArea won't cover spotArea) + Rectangle imgArea; + // top/left and bottom/right coordinates of useful part of the image in scaled image space (rounding error workaround) + Rectangle intersectionArea; + float radius; + float featherRadius; - SpotBox (int tl_x, int tl_y, int br_x, int br_y, Type type) : + SpotBox (int tl_x, int tl_y, int br_x, int br_y, int radius, int feather_radius, Imagefloat* image, Type type) : type(type), - topLeftX(tl_x), - topLeftY(tl_y), - bottomRightX(br_x), - bottomRightY(br_y), - img(nullptr) + image(image), + spotArea(tl_x, tl_y, br_x, br_y), + imgArea(spotArea), + intersectionArea(), + radius(radius), + featherRadius(feather_radius) {} - SpotBox (int tl_x, int tl_y, Imagefloat* image, Type type) : + SpotBox (int tl_x, int tl_y, int radius, int feather_radius, Imagefloat* image, Type type) : type(type), - topLeftX(tl_x), - topLeftY(tl_y), - bottomRightX(image ? tl_x + image->getWidth() - 1 : 0), - bottomRightY(image ? tl_y + image->getHeight() - 1 : 0), - img(image) + image(image), + spotArea(tl_x, tl_y, image ? tl_x + image->getWidth() - 1 : 0, image ? tl_y + image->getHeight() - 1 : 0), + imgArea(spotArea), + intersectionArea(), + radius(radius), + featherRadius(feather_radius) {} SpotBox (SpotEntry &spot, Type type) : type(type), - img(nullptr) + image(nullptr), + intersectionArea(), + radius(spot.radius), + featherRadius(int(spot.getFeatherRadius() + 0.5f)) // rounding to int before resizing { - float featherRadius = spot.radius * (1.f + spot.feather); - topLeftX = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) - featherRadius); - bottomRightX = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) + featherRadius); - topLeftY = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) - featherRadius); - bottomRightY = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) + featherRadius); + spotArea.x1 = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) - featherRadius); + spotArea.x2 = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) + featherRadius); + spotArea.y1 = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) - featherRadius); + spotArea.y2 = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) + featherRadius); + imgArea = spotArea; } - void translate(int dx, int dy) { - topLeftX += dx; - topLeftY += dy; - bottomRightX += dx; - bottomRightY += dy; + ~SpotBox() { + if (image && type != Type::FINAL) { + delete image; + } } - void operator /(float v) { - topLeftX = int(topLeftX / v + 0.5f); - topLeftY = int(topLeftY / v + 0.5f); - bottomRightX = int(bottomRightX / v + 0.5f); - bottomRightY = int(bottomRightY / v + 0.5f); - } - - void operator *(float v) { - topLeftX *= v; - topLeftY *= v; - bottomRightX *= v; - bottomRightY *= v; - } - - bool intersects(const SpotBox &other) const { - return (other.topLeftX <= bottomRightX && other.bottomRightX >= topLeftX) - && (other.topLeftY <= bottomRightY && other.bottomRightY >= topLeftY); + SpotBox& operator /=(const int& v) { + if (v == 1) { + return *this; + } + spotArea /= v; + imgArea /= v; + radius = radius / float(v); + featherRadius = getWidth() / 2.f; + // intersectionArea doesn't need resize, because it's set after resizing + return *this; } int getWidth() { - return bottomRightX - topLeftX + 1; + return spotArea.x2 - spotArea.x1 + 1; } int getHeight() { - return bottomRightY - topLeftY + 1; + return spotArea.y2 - spotArea.y1 + 1; } -}; + int getImageWidth() { + return imgArea.x2 - imgArea.x1 + 1; + } -void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) -{ - // ---------- Get the image areas (src & dst) from the source image + int getImageHeight() { + return imgArea.y2 - imgArea.y1 + 1; + } - printf("\n=======================================================================\n\n"); + int getIntersectionWidth() { + return intersectionArea.x2 - intersectionArea.x1 + 1; + } - std::vector< std::shared_ptr > srcSpotBoxs; - std::vector< std::shared_ptr > dstSpotBoxs; - for (auto entry : params->spot.entries) { - Coord origin; - int size = int(entry.getFeatherRadius() * 2.f + 0.5f); - int scaledSize = int(entry.getFeatherRadius() * 2.f / float(pp.getSkip()) + 0.5f); - //printf("size: %d - skip: %d -> scaledSize: %d", size, pp.getSkip(), scaledSize); + int getIntersectionHeight() { + return intersectionArea.y2 - intersectionArea.y1 + 1; + } - // ------ Source area - Imagefloat *currSrcSpot = new Imagefloat(scaledSize, scaledSize); - for (int y = 0; y < currSrcSpot->getHeight(); ++y) { - for (int x = 0; x < currSrcSpot->getWidth(); ++x) { - currSrcSpot->r(y,x) = 0.f; - currSrcSpot->g(y,x) = 0.f; - currSrcSpot->b(y,x) = 0.f; - } + bool checkImageSize() { + if (!image || getImageWidth() != image->getWidth() || getImageHeight() != image->getHeight()) { + return false; } - entry.sourcePos.get(origin.x, origin.y); - origin.x -= entry.getFeatherRadius(); - origin.y -= entry.getFeatherRadius(); - PreviewProps spp(origin.x, origin.y, size, size, pp.getSkip()); - imgsrc->getImage(currWB, tr, currSrcSpot, spp, params->toneCurve, params->raw); - //printf(" / src size: %d,%d", currSrcSpot->getWidth(), currSrcSpot->getHeight()); + return true; + } - std::shared_ptr srcSpotBox(new SpotBox(origin.x / pp.getSkip(), origin.y / pp.getSkip(), currSrcSpot, SpotBox::Type::SOURCE)); - srcSpotBoxs.push_back(srcSpotBox); - - // ------ Destination area - Imagefloat *currDstSpot = new Imagefloat(scaledSize, scaledSize); - for (int y = 0; y < currDstSpot->getHeight(); ++y) { - for (int x = 0; x < currDstSpot->getWidth(); ++x) { - currDstSpot->r(y,x) = 0.f; - currDstSpot->g(y,x) = 0.f; - currDstSpot->b(y,x) = 0.f; - } + void tuneImageSize() { + if (!image) { + return; } - entry.targetPos.get(origin.x, origin.y); - origin.x -= entry.getFeatherRadius(); - origin.y -= entry.getFeatherRadius(); - spp.set(origin.x, origin.y, size, size, pp.getSkip()); - imgsrc->getImage(currWB, tr, currDstSpot, spp, params->toneCurve, params->raw); - //printf(" / dst size: %d,%d\n", currDstSpot->getWidth(), currDstSpot->getHeight()); - - std::shared_ptr dstSpotBox(new SpotBox(origin.x / pp.getSkip(), origin.y / pp.getSkip(), currDstSpot, SpotBox::Type::TARGET)); - - dstSpotBoxs.push_back(dstSpotBox); - } - - // Filter out out of preview Spots - - /* - for (size_t i = entries.size(); i >= 0; ++i) { - float featherRadius = entries.at(i).radius * (1.f + entries.at(i).feather); - - SpotBox srcBox(entries.at(i), SpotBox::Type::SOURCE); - srcBox.translate(-pp.getX(), -pp.getY()); - srcBox /= float (pp.getSkip()); - - SpotBox dstBox(entries.at(i), SpotBox::Type::TARGET); - dstBox.translate(-pp.getX(), -pp.getY()); - dstBox /= float (pp.getSkip()); - - } - */ - - - - // ---------- Copy spots from src to dst - - for (int i = entries.size() - 1; i >= 0; --i) { - // 1. copy src to dst - std::shared_ptr srcSpotBox = srcSpotBoxs.at(i); - std::shared_ptr dstSpotBox = dstSpotBoxs.at(i); - float scaledRadius = float(entries.at(i).radius) / float(pp.getSkip()); - float scaledFeatherRadius = entries.at(i).getFeatherRadius() / float(pp.getSkip()); - Imagefloat *srcImg = srcSpotBox->img; - Imagefloat *dstImg = dstSpotBox->img; - - //printf("#%d: srcSpotBox @ %p - img @ %p / dstSpotBox @ %p - img @ %p\n", i, - // srcSpotBox.get(), srcSpotBox->img, dstSpotBox.get(), dstSpotBox->img ); - - //printf("#%d: srcSpotBox(%d,%d) srcImg(%d,%d) / dstSpotBox(%d,%d) dstImg(%d,%d)\n", i, - // srcSpotBox->getWidth(), srcSpotBox->getHeight(), srcImg->getWidth(), srcImg->getHeight(), - // dstSpotBox->getWidth(), dstSpotBox->getHeight(), dstImg->getWidth(), dstImg->getHeight() - // ); - - for (int y = 0; y < srcSpotBox->getHeight(); ++y) { - float dy = float(y - float(srcSpotBox->getHeight()) / 2.f); - for (int x = 0; x < srcSpotBox->getWidth(); ++x) { - float dx = float(x - float(srcSpotBox->getWidth()) / 2.f); - float r = sqrt(dx * dx + dy * dy); - if (r >= scaledFeatherRadius) { - continue; - } - if (r <= scaledRadius) { - dstImg->r(y, x) = srcImg->r(y, x); - dstImg->g(y, x) = srcImg->g(y, x); - dstImg->b(y, x) = srcImg->b(y, x); - } else { - float opacity = (scaledFeatherRadius - r) / (scaledFeatherRadius - scaledRadius); - dstImg->r(y, x) = (srcImg->r(y, x) - dstImg->r(y, x)) * opacity + dstImg->r(y,x); - dstImg->g(y, x) = (srcImg->g(y, x) - dstImg->g(y, x)) * opacity + dstImg->g(y,x); - dstImg->b(y, x) = (srcImg->b(y, x) - dstImg->b(y, x)) * opacity + dstImg->b(y,x); - } - } + if (getImageWidth() > image->getWidth()) { + imgArea.x2 = imgArea.x1 + image->getWidth() - 1; + } + if (getImageHeight() > image->getHeight()) { + imgArea.y2 = imgArea.y1 + image->getHeight() - 1; } - //printf("\n\n"); - - // 2. copy dst to later src and dst - } - // 3. copy all dst to the finale image + Imagefloat *getImage() { // TODO: this should send back a const value, but getImage don't want it to be const... + return image; + } - // Putting the dest image in a SpotBox - SpotBox imgSpotBox(pp.getX() / pp.getSkip(), pp.getY() / pp.getSkip(), img, SpotBox::Type::TARGET); - /* - printf("#--: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n\n", - imgSpotBox.topLeftX, imgSpotBox.topLeftY, imgSpotBox.bottomRightX, imgSpotBox.bottomRightY, - imgSpotBox.getWidth(), imgSpotBox.getHeight(), - imgSpotBox.img->getWidth(), imgSpotBox.img->getHeight() - ); - */ + void allocImage() { + int newW = imgArea.x2 - imgArea.x1 + 1; + int newH = imgArea.y2 - imgArea.y1 + 1; - for (size_t i = 0; i < entries.size(); ++i) { - // 1. copy src to dst - std::shared_ptr dstSpotBox = dstSpotBoxs.at(i); - Imagefloat *dstImg = dstSpotBox->img; + if (image && type != Type::FINAL && (image->getWidth() != newW || image->getHeight() != newH)) { + delete image; + image = nullptr; + } + if (image == nullptr) { + image = new Imagefloat(newW, newH); + } + } + + bool spotIntersects(const SpotBox &other) const { + return spotArea.intersects(other.spotArea); + } + + bool getSpotIntersection(const SpotBox &other, std::unique_ptr &intersection) const { + return spotArea.getIntersection(other.spotArea, intersection); + } + + bool imageIntersects(const SpotBox &other, bool atDestLocation=false) const { + if (atDestLocation) { + Coord v(other.spotArea.x1 - spotArea.x1, other.spotArea.y1 - spotArea.y1); + Rectangle imgArea2(imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2); + imgArea2 += v; + return imgArea2.intersects(other.imgArea); + } + return imgArea.intersects(other.imgArea); + } + + bool mutuallyClipImageArea(SpotBox &other) { + Coord v(other.spotArea.x1 - spotArea.x1, other.spotArea.y1 - spotArea.y1); + Rectangle imgArea2 = imgArea; + imgArea2 += v; + std::unique_ptr intersection; + if (!imgArea2.getIntersection(other.imgArea, intersection)) { + return false; + } + other.intersectionArea = *intersection; + Coord v2(-v.x, -v.y); + *intersection -= v; + intersectionArea = *intersection; + return true; + } + + bool setIntersectionWith(const SpotBox &other) { + if (!spotIntersects(other)) { + return false; + } + imgArea.x1 = rtengine::max(spotArea.x1, other.spotArea.x1); + imgArea.x2 = rtengine::min(spotArea.x2, other.spotArea.x2); + imgArea.y1 = rtengine::max(spotArea.y1, other.spotArea.y1); + imgArea.y2 = rtengine::min(spotArea.y2, other.spotArea.y2); + if (imgArea.x1 > imgArea.x2 || imgArea.y1 > imgArea.y2) { + return false; + } + return true; + } + + bool processIntersectionWith(SpotBox &destBox) { + Imagefloat *dstImg = destBox.image; + + if (image == nullptr || dstImg == nullptr) { + std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl; + return false; + } /* printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i, - dstSpotBox->topLeftX, dstSpotBox->topLeftY, dstSpotBox->bottomRightX, dstSpotBox->bottomRightY, - dstSpotBox->getWidth(), dstSpotBox->getHeight(), - dstImg->getWidth(), dstImg->getHeight() + spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, + getWidth(), getHeight(), + destBox.getWidth(), destBox.getHeight() ); */ - if (dstSpotBox->intersects(imgSpotBox)) { - int beginX = rtengine::max(dstSpotBox->topLeftX, imgSpotBox.topLeftX); - int endX = rtengine::min(dstSpotBox->bottomRightX, imgSpotBox.bottomRightX); - int beginY = rtengine::max(dstSpotBox->topLeftY, imgSpotBox.topLeftY); - int endY = rtengine::min(dstSpotBox->bottomRightY, imgSpotBox.bottomRightY); +#ifndef NDEBUG + printf("[processIntersectionWith] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n", + this, getWidth(), getHeight(), image, destBox.getWidth(), destBox.getHeight(), + &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight()); + printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", + spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, + destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2); + printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", + imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2, + destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2); + printf(" [intr] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", + intersectionArea.x1, intersectionArea.y1, intersectionArea.x2, intersectionArea.y2, + destBox.intersectionArea.x1, destBox.intersectionArea.y1, destBox.intersectionArea.x2, destBox.intersectionArea.y2); + printf(" radius = %.3f\n", radius); +#endif - //printf("--- Intersection: X1:%d, Y1:%d -> X2:%d, Y2:%d\n", beginX, beginY, endX, endY); + int srcImgY = intersectionArea.y1 - imgArea.y1; + int dstImgY = destBox.intersectionArea.y1 - destBox.imgArea.y1; + for (int y = intersectionArea.y1; y <= intersectionArea.y2; ++y) { + float dy = float(y - spotArea.y1) - featherRadius; - int dstSpotOffsetY = beginY - dstSpotBox->topLeftY; - int imgOffsetY = beginY - imgSpotBox.topLeftY; + int srcImgX = intersectionArea.x1 - imgArea.x1; + int dstImgX = destBox.intersectionArea.x1 - destBox.imgArea.x1; + for (int x = intersectionArea.x1; x <= intersectionArea.x2; ++x) { + float dx = float(x - spotArea.x1) - featherRadius; + float r = sqrt(dx * dx + dy * dy); - for (int y = beginY; y <= endY; ++y) { - int dstSpotOffsetX = beginX - dstSpotBox->topLeftX; - int imgOffsetX = beginX - imgSpotBox.topLeftX; - - for (int x = beginX; x <= endX; ++x) { - /* - if (y == beginY && x == beginX) { - printf("--- dstSpotOffsetX = beginX - dstSpotBox->topLeftX = %d - %d = %d\n", beginX, dstSpotBox->topLeftX, dstSpotOffsetX); - printf("--- dstSpotOffsetY = beginY - dstSpotBox->topLeftY = %d - %d = %d\n", beginY, dstSpotBox->topLeftY, dstSpotOffsetY); - printf("--- imgOffsetX = beginX - imgSpotBox.topLeftX = %d - %d = %d\n", beginX, imgSpotBox.topLeftX, imgOffsetX); - printf("--- imgOffsetX = beginY - imgSpotBox.topLeftY = %d - %d = %d\n", beginY, imgSpotBox.topLeftY, imgOffsetY); - } - */ - img->r(imgOffsetY, imgOffsetX) = dstImg->r(dstSpotOffsetY, dstSpotOffsetX); - img->g(imgOffsetY, imgOffsetX) = dstImg->g(dstSpotOffsetY, dstSpotOffsetX); - img->b(imgOffsetY, imgOffsetX) = dstImg->b(dstSpotOffsetY, dstSpotOffsetX); - ++imgOffsetX; - ++dstSpotOffsetX; + if (r >= featherRadius) { + ++srcImgX; + ++dstImgX; + continue; + } + if (r <= radius) { + dstImg->r(dstImgY, dstImgX) = image->r(srcImgY, srcImgX); + dstImg->g(dstImgY, dstImgX) = image->g(srcImgY, srcImgX); + dstImg->b(dstImgY, dstImgX) = image->b(srcImgY, srcImgX); + } else { + float opacity = (featherRadius - r) / (featherRadius - radius); + dstImg->r(dstImgY, dstImgX) = (image->r(srcImgY, srcImgX) - dstImg->r(dstImgY, dstImgX)) * opacity + dstImg->r(dstImgY,dstImgX); + dstImg->g(dstImgY, dstImgX) = (image->g(srcImgY, srcImgX) - dstImg->g(dstImgY, dstImgX)) * opacity + dstImg->g(dstImgY,dstImgX); + dstImg->b(dstImgY, dstImgX) = (image->b(srcImgY, srcImgX) - dstImg->b(dstImgY, dstImgX)) * opacity + dstImg->b(dstImgY,dstImgX); + } + ++srcImgX; + ++dstImgX; + } + ++srcImgY; + ++dstImgY; + } + + return true; + } + + // Copy the intersecting part + bool copyImgTo(SpotBox &destBox) { + Imagefloat *destImg = destBox.image; + + if (image == nullptr || destImg == nullptr) { + std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl; + return false; + } + + /* + printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i, + spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, + getWidth(), getHeight(), + destBox.getWidth(), destBox.getHeight() + ); + */ + + std::unique_ptr intersection; + + if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) { + return false; + } + + Imagefloat *srcImg = image; + Imagefloat *dstImg = destBox.image; + +#ifndef NDEBUG + printf("[copyImgTo] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n", + this, getWidth(), getHeight(), image, srcImg->getWidth(), srcImg->getHeight(), + &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight()); + printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", + spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, + destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2); + printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", + imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2, + destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2); + printf(" [intr] intersection (%d, %d) -> (%d, %d)\n", + intersection->x1, intersection->y1, intersection->x2, intersection->y2); +#endif + + int srcImgY = intersection->y1 - imgArea.y1; + int dstImgY = intersection->y1 - destBox.imgArea.y1; + for (int y = intersection->y1; y <= intersection->y2; ++y) { + int srcImgX = intersection->x1 - imgArea.x1; + int dstImgX = intersection->x1 - destBox.imgArea.x1; + + for (int x = intersection->x1; x <= intersection->x2; ++x) { + dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX); + dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX); + dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX); + ++srcImgX; + ++dstImgX; + } + ++srcImgY; + ++dstImgY; + } + + return true; + } +}; + +void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) +{ + //Get the clipped image areas (src & dst) from the source image + + std::vector< std::shared_ptr > srcSpotBoxs; + std::vector< std::shared_ptr > dstSpotBoxs; + int fullImgWidth = 0; + int fullImgHeight = 0; + imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr); + SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL); + SpotBox cropBox(pp.getX(), pp.getY(), + pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1, + 0, 0, img, SpotBox::Type::FINAL); + + std::set visibleSpots; // list of dest spots intersecting the preview's crop + int i = 0; + + for (auto entry : params->spot.entries) { + std::shared_ptr srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE)); + std::shared_ptr dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET)); + if ( !srcSpotBox->setIntersectionWith(fullImageBox) + || !dstSpotBox->setIntersectionWith(fullImageBox) + || !srcSpotBox->imageIntersects(*dstSpotBox, true)) + { + continue; + ++i; + } + + // If spot intersect the preview image, add it to the visible spots + if (dstSpotBox->spotIntersects(cropBox)) { +#ifndef NDEBUG + printf("Spot visible insere: %d\n", i); +#endif + visibleSpots.insert(i); + } + ++i; + + // Source area + PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1, + srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip()); + int w = 0; + int h = 0; + imgsrc->getSize(spp, w, h); + *srcSpotBox /= pp.getSkip(); + srcSpotBox->allocImage(); + Imagefloat *_image_ = srcSpotBox->getImage(); + for (int y = 0; y < (int)_image_->getHeight(); ++y) { + for (int x = 0; x < (int)_image_->getWidth(); ++x) { + _image_->r(y, x) = 60000.f; + _image_->g(y, x) = 500.f; + _image_->b(y, x) = 500.f; + } + } + +#ifndef NDEBUG + printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight()); +#endif + imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw); +#ifndef NDEBUG + printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight()); +#endif + assert(srcSpotBox->checkImageSize()); + //printf(" / src size: %d,%d", currSrcSpot->getWidth(), currSrcSpot->getHeight()); + + + // Destination area + spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(), + dstSpotBox->getImageHeight(), pp.getSkip()); + *dstSpotBox /= pp.getSkip(); + dstSpotBox->allocImage(); + _image_ = dstSpotBox->getImage(); + for (int y = 0; y < (int)_image_->getHeight(); ++y) { + for (int x = 0; x < (int)_image_->getWidth(); ++x) { + _image_->r(y, x) = 500.f; + _image_->g(y, x) = 500.f; + _image_->b(y, x) = 60000.f; + } + } +#ifndef NDEBUG + printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight()); +#endif + imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw); +#ifndef NDEBUG + printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight()); +#endif + assert(dstSpotBox->checkImageSize()); + //printf(" / src size: %d,%d", currDstSpot->getWidth(), currDstSpot->getHeight()); + + // Update the intersectionArea between src and dest + if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) { + srcSpotBoxs.push_back(srcSpotBox); + dstSpotBoxs.push_back(dstSpotBox); + +#ifndef NDEBUG + printf("Taille de l'image pour spot #%d: src(%d, %d) / dst(%d, %d)\n", i-1, + srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), + dstSpotBox->getImageWidth(), dstSpotBox->getImageHeight() + ); +#endif + } + + } + + // Construct list of upstream dependancies + + std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots +#ifndef NDEBUG + printf("Required spots: "); +#endif + for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) { + int spotNbr = *i; +#ifndef NDEBUG + printf("%d ", spotNbr); +#endif + requiredSpots.insert(spotNbr); + if (spotNbr > 0) { + for (int j = spotNbr - 1; j >= 0; --j) { + if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) { + requiredSpots.insert(spotNbr); +#ifndef NDEBUG + printf("%d ", spotNbr); +#endif } - ++imgOffsetY; - ++dstSpotOffsetY; } - //} else { - // printf("#%llu: No intersection !\n", i); } } +#ifndef NDEBUG + printf("\n"); +#endif - for (auto srcSpotBox : srcSpotBoxs) { - delete srcSpotBox->img; + // Process spots and copy them downstream + + for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) { + // Process +#ifndef NDEBUG + printf("========>>> Processing spot #%d\n", *i); +#endif + srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i)); + + // Propagate + std::set positiveSpots; // For DEBUG purpose only ! + auto j = i; + ++j; + while (j != requiredSpots.end()) { + bool intersectionFound = false; + int i_ = *i; + int j_ = *j; +#ifndef NDEBUG + printf("Propagating to SRC #%d ?\n", j_); +#endif + intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_)); +#ifndef NDEBUG + printf("Propagating to DSTC #%d ?\n", j_); +#endif + intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_)); + if (intersectionFound) { + positiveSpots.insert(j_); + } + ++j; + } +#ifndef NDEBUG + printf("||| DEST spot #%d propagated to : ", *i); + for (auto g : positiveSpots) { + printf("%d ", g); + } + printf("\n"); +#endif } - for (auto dstSpotBox : dstSpotBoxs) { - delete dstSpotBox->img; + + // Copy the dest spot to the preview image +#ifndef NDEBUG + printf("cropBox (%d, %d) -> (%d, %d)", + cropBox.imgArea.x1, cropBox.imgArea.y1, + cropBox.imgArea.x2, cropBox.imgArea.y2); +#endif + cropBox /= pp.getSkip(); +#ifndef NDEBUG + printf(" -> [/skip] (%d, %d) -> (%d, %d)", + cropBox.imgArea.x1, cropBox.imgArea.y1, + cropBox.imgArea.x2, cropBox.imgArea.y2); +#endif + cropBox.tuneImageSize(); + cropBox.intersectionArea = cropBox.imgArea; +#ifndef NDEBUG + printf(" -> [/tuned] (%d, %d) -> (%d, %d)\n", + cropBox.imgArea.x1, cropBox.imgArea.y1, + cropBox.imgArea.x2, cropBox.imgArea.y2); +#endif + + int f = 0; + for (auto i : visibleSpots) { + f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0; } +#ifndef NDEBUG + printf("Nombre de copie finale : %d\n", f); +#endif + + /* + printf("#--: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n\n", + cropBox.spotArea.x1, cropBox.spotArea.y1, cropBox.spotArea.x2, cropBox.spotArea.y2, + cropBox.getWidth(), cropBox.getHeight(), + cropBox.image->getWidth(), cropBox.image->getHeight() + ); + */ } #endif diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 9f7830a15..024f1ae26 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -1,6 +1,22 @@ /* * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 "editcallbacks.h" #include "spot.h" #include "rtimage.h" diff --git a/rtgui/spot.h b/rtgui/spot.h index b163848c0..f1a4cc604 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -1,6 +1,22 @@ /* * This file is part of RawTherapee. + * + * Copyright (c) 2019 Jean-Christophe FRISCH + * + * 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 _SPOT_H_ #define _SPOT_H_ From b4d77986a6b8b0d9ecb2e375893b329e4e195790 Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 15 Aug 2019 02:15:26 +0200 Subject: [PATCH 11/34] Non active anchors are now thinner + bugfix + code cleanup See issue #2239 --- rtdata/images/svg/spot-normal.svg | 65 ++-- rtengine/dcrop.cc | 4 +- rtengine/improccoordinator.cc | 2 +- rtengine/improcfun.h | 2 +- rtengine/simpleprocess.cc | 2 +- rtengine/spot.cc | 626 ++++++++++-------------------- 6 files changed, 246 insertions(+), 455 deletions(-) diff --git a/rtdata/images/svg/spot-normal.svg b/rtdata/images/svg/spot-normal.svg index ca9a933c2..d0320e31f 100644 --- a/rtdata/images/svg/spot-normal.svg +++ b/rtdata/images/svg/spot-normal.svg @@ -9,62 +9,65 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="16px" - height="16px" - id="svg2985" + width="11" + height="11" + id="svg2" version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="New document 2"> + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="spot-normal.svg"> + id="defs4" /> + id="metadata7"> image/svg+xml - + + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-1041.3622)"> + style="fill:#000000;fill-opacity:0.66666667;stroke:none" + d="M 6,1043.3622 5.378906,1046.7411 2,1047.3622 v 1 l 3.378906,0.6211 L 6,1052.3622 H 7 L 7.621094,1048.9833 11,1048.3622 v -1 L 7.621094,1046.7411 7,1043.3622 Z" + id="path818" + inkscape:connector-curvature="0" /> + d="m 5,1042.3622 h 1 l 0.8286408,4.5 L 6,1051.3622 H 5 l -0.8286408,-4.5 z" + id="rect2989" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccccc" /> + diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 72f5c6d72..488a56b46 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -623,7 +623,7 @@ void Crop::update(int todo) spotsDone = true; PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip); //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw); - parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr); + parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, nullptr, tr); } DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise; @@ -709,7 +709,7 @@ void Crop::update(int todo) baseCrop->copyData (spotCrop); PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip); int tr = getCoarseBitMask(params.coarse); - parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, tr); + parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, ¶ms.icm, tr); } else { if (spotCrop) { delete spotCrop; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 2c49bdef1..22bafd844 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -540,7 +540,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) allocCache(spotprev); orig_prev->copyData(spotprev); PreviewProps pp(0, 0, fw, fh, scale); - ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, tr); + ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, ¶ms->icm, tr); } else { if (spotprev) { delete spotprev; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 5700a354b..a1b79fc38 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -333,7 +333,7 @@ public: float MadRgb(const float * DataList, int datalen); // spot removal tool - void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr); + void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr); // pyramid wavelet void dirpyr_equalizer(float ** src, float ** dst, int srcwidth, int srcheight, float ** l_a, float ** l_b, const double * mult, const double dirpyrThreshold, const double skinprot, float b_l, float t_l, float t_r, int scale); //Emil's directional pyramid wavelet diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 6934cc8e8..c50306f6f 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -785,7 +785,7 @@ private: // Spot Removal if (params.spot.enabled && !params.spot.entries.empty ()) { - ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, tr); + ipf.removeSpots (baseImg, imgsrc, params.spot.entries, pp, currWB, nullptr, tr); } // at this stage, we can flush the raw data to free up quite an important amount of memory diff --git a/rtengine/spot.cc b/rtengine/spot.cc index 2090324dd..b12f5bc40 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -38,34 +38,6 @@ constexpr T skips(T a, T b) namespace rtengine { -/* Code taken from Gimp 2.8.10 and converted for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014 - * - * ORIGINAL NOTES - * - * The method used here is similar to the lighting invariant correction - * method but slightly different: we do not divide the RGB components, - * but substract them I2 = I0 - I1, where I0 is the sample image to be - * corrected, I1 is the reference pattern. Then we solve DeltaI=0 - * (Laplace) with I2 Dirichlet conditions at the borders of the - * mask. The solver is a unoptimized red/black checker Gauss-Siedel - * with an over-relaxation factor of 1.8. It can benefit from a - * multi-grid evaluation of an initial solution before the main - * iteration loop. - * - * I reduced the convergence criteria to 0.1% (0.001) as we are - * dealing here with RGB integer components, more is overkill. - * - * Jean-Yves Couleaud cjyves@free.fr - */ - -/* Original Algorithm Design: - * - * T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning - * http://www.tgeorgiev.net/Photoshop_Healing.pdf - */ - -#if 1 - class SpotBox { public: @@ -329,6 +301,9 @@ public: return true; } +#define ALGO 1 + +#if ALGO==1 bool processIntersectionWith(SpotBox &destBox) { Imagefloat *dstImg = destBox.image; @@ -337,30 +312,6 @@ public: return false; } - /* - printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i, - spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, - getWidth(), getHeight(), - destBox.getWidth(), destBox.getHeight() - ); - */ - -#ifndef NDEBUG - printf("[processIntersectionWith] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n", - this, getWidth(), getHeight(), image, destBox.getWidth(), destBox.getHeight(), - &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight()); - printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", - spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, - destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2); - printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", - imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2, - destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2); - printf(" [intr] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", - intersectionArea.x1, intersectionArea.y1, intersectionArea.x2, intersectionArea.y2, - destBox.intersectionArea.x1, destBox.intersectionArea.y1, destBox.intersectionArea.x2, destBox.intersectionArea.y2); - printf(" radius = %.3f\n", radius); -#endif - int srcImgY = intersectionArea.y1 - imgArea.y1; int dstImgY = destBox.intersectionArea.y1 - destBox.imgArea.y1; for (int y = intersectionArea.y1; y <= intersectionArea.y2; ++y) { @@ -396,358 +347,44 @@ public: return true; } - - // Copy the intersecting part - bool copyImgTo(SpotBox &destBox) { - Imagefloat *destImg = destBox.image; - - if (image == nullptr || destImg == nullptr) { - std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl; - return false; - } - - /* - printf("#%llu: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n", i, - spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, - getWidth(), getHeight(), - destBox.getWidth(), destBox.getHeight() - ); - */ - - std::unique_ptr intersection; - - if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) { - return false; - } - - Imagefloat *srcImg = image; - Imagefloat *dstImg = destBox.image; - -#ifndef NDEBUG - printf("[copyImgTo] srcSpotBox @ %p(%d, %d) - img @ %p(%d, %d) / dstSpotBox @ %p(%d, %d) - img @ %p(%d, %d)\n", - this, getWidth(), getHeight(), image, srcImg->getWidth(), srcImg->getHeight(), - &destBox, destBox.getWidth(), destBox.getHeight(), destBox.image, dstImg->getWidth(), dstImg->getHeight()); - printf(" [spot] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", - spotArea.x1, spotArea.y1, spotArea.x2, spotArea.y2, - destBox.spotArea.x1, destBox.spotArea.y1, destBox.spotArea.x2, destBox.spotArea.y2); - printf(" [img ] srcSpotBox (%d, %d) -> (%d, %d) / dstSpotBox (%d, %d) -> (%d, %d)\n", - imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2, - destBox.imgArea.x1, destBox.imgArea.y1, destBox.imgArea.x2, destBox.imgArea.y2); - printf(" [intr] intersection (%d, %d) -> (%d, %d)\n", - intersection->x1, intersection->y1, intersection->x2, intersection->y2); #endif - int srcImgY = intersection->y1 - imgArea.y1; - int dstImgY = intersection->y1 - destBox.imgArea.y1; - for (int y = intersection->y1; y <= intersection->y2; ++y) { - int srcImgX = intersection->x1 - imgArea.x1; - int dstImgX = intersection->x1 - destBox.imgArea.x1; - - for (int x = intersection->x1; x <= intersection->x2; ++x) { - dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX); - dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX); - dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX); - ++srcImgX; - ++dstImgX; - } - ++srcImgY; - ++dstImgY; - } - - return true; - } -}; - -void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) -{ - //Get the clipped image areas (src & dst) from the source image - - std::vector< std::shared_ptr > srcSpotBoxs; - std::vector< std::shared_ptr > dstSpotBoxs; - int fullImgWidth = 0; - int fullImgHeight = 0; - imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr); - SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL); - SpotBox cropBox(pp.getX(), pp.getY(), - pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1, - 0, 0, img, SpotBox::Type::FINAL); - - std::set visibleSpots; // list of dest spots intersecting the preview's crop - int i = 0; - - for (auto entry : params->spot.entries) { - std::shared_ptr srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE)); - std::shared_ptr dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET)); - if ( !srcSpotBox->setIntersectionWith(fullImageBox) - || !dstSpotBox->setIntersectionWith(fullImageBox) - || !srcSpotBox->imageIntersects(*dstSpotBox, true)) - { - continue; - ++i; - } - - // If spot intersect the preview image, add it to the visible spots - if (dstSpotBox->spotIntersects(cropBox)) { -#ifndef NDEBUG - printf("Spot visible insere: %d\n", i); -#endif - visibleSpots.insert(i); - } - ++i; - - // Source area - PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1, - srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip()); - int w = 0; - int h = 0; - imgsrc->getSize(spp, w, h); - *srcSpotBox /= pp.getSkip(); - srcSpotBox->allocImage(); - Imagefloat *_image_ = srcSpotBox->getImage(); - for (int y = 0; y < (int)_image_->getHeight(); ++y) { - for (int x = 0; x < (int)_image_->getWidth(); ++x) { - _image_->r(y, x) = 60000.f; - _image_->g(y, x) = 500.f; - _image_->b(y, x) = 500.f; - } - } - -#ifndef NDEBUG - printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight()); -#endif - imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw); -#ifndef NDEBUG - printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight()); -#endif - assert(srcSpotBox->checkImageSize()); - //printf(" / src size: %d,%d", currSrcSpot->getWidth(), currSrcSpot->getHeight()); +#if ALGO==2 + bool processIntersectionWith(SpotBox &destBox) { + /* The following disabled code has been taken from Gimp 2.8.10 and converted + * for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014. + * It has not been tested, at all, an can (does) contain bugs. Feel free to + * replace the actual working code by this one, if results are better. + */ - // Destination area - spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(), - dstSpotBox->getImageHeight(), pp.getSkip()); - *dstSpotBox /= pp.getSkip(); - dstSpotBox->allocImage(); - _image_ = dstSpotBox->getImage(); - for (int y = 0; y < (int)_image_->getHeight(); ++y) { - for (int x = 0; x < (int)_image_->getWidth(); ++x) { - _image_->r(y, x) = 500.f; - _image_->g(y, x) = 500.f; - _image_->b(y, x) = 60000.f; - } - } -#ifndef NDEBUG - printf("~~~~~ Image Size Before : %d, %d\n", _image_->getWidth(), _image_->getHeight()); -#endif - imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw); -#ifndef NDEBUG - printf("~~~~~ Image Size After : %d, %d\n", _image_->getWidth(), _image_->getHeight()); -#endif - assert(dstSpotBox->checkImageSize()); - //printf(" / src size: %d,%d", currDstSpot->getWidth(), currDstSpot->getHeight()); - - // Update the intersectionArea between src and dest - if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) { - srcSpotBoxs.push_back(srcSpotBox); - dstSpotBoxs.push_back(dstSpotBox); - -#ifndef NDEBUG - printf("Taille de l'image pour spot #%d: src(%d, %d) / dst(%d, %d)\n", i-1, - srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), - dstSpotBox->getImageWidth(), dstSpotBox->getImageHeight() - ); -#endif - } - - } - - // Construct list of upstream dependancies - - std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots -#ifndef NDEBUG - printf("Required spots: "); -#endif - for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) { - int spotNbr = *i; -#ifndef NDEBUG - printf("%d ", spotNbr); -#endif - requiredSpots.insert(spotNbr); - if (spotNbr > 0) { - for (int j = spotNbr - 1; j >= 0; --j) { - if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) { - requiredSpots.insert(spotNbr); -#ifndef NDEBUG - printf("%d ", spotNbr); -#endif - } - } - } - } -#ifndef NDEBUG - printf("\n"); -#endif - - // Process spots and copy them downstream - - for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) { - // Process -#ifndef NDEBUG - printf("========>>> Processing spot #%d\n", *i); -#endif - srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i)); - - // Propagate - std::set positiveSpots; // For DEBUG purpose only ! - auto j = i; - ++j; - while (j != requiredSpots.end()) { - bool intersectionFound = false; - int i_ = *i; - int j_ = *j; -#ifndef NDEBUG - printf("Propagating to SRC #%d ?\n", j_); -#endif - intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_)); -#ifndef NDEBUG - printf("Propagating to DSTC #%d ?\n", j_); -#endif - intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_)); - if (intersectionFound) { - positiveSpots.insert(j_); - } - ++j; - } -#ifndef NDEBUG - printf("||| DEST spot #%d propagated to : ", *i); - for (auto g : positiveSpots) { - printf("%d ", g); - } - printf("\n"); -#endif - } - - // Copy the dest spot to the preview image -#ifndef NDEBUG - printf("cropBox (%d, %d) -> (%d, %d)", - cropBox.imgArea.x1, cropBox.imgArea.y1, - cropBox.imgArea.x2, cropBox.imgArea.y2); -#endif - cropBox /= pp.getSkip(); -#ifndef NDEBUG - printf(" -> [/skip] (%d, %d) -> (%d, %d)", - cropBox.imgArea.x1, cropBox.imgArea.y1, - cropBox.imgArea.x2, cropBox.imgArea.y2); -#endif - cropBox.tuneImageSize(); - cropBox.intersectionArea = cropBox.imgArea; -#ifndef NDEBUG - printf(" -> [/tuned] (%d, %d) -> (%d, %d)\n", - cropBox.imgArea.x1, cropBox.imgArea.y1, - cropBox.imgArea.x2, cropBox.imgArea.y2); -#endif - - int f = 0; - for (auto i : visibleSpots) { - f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0; - } -#ifndef NDEBUG - printf("Nombre de copie finale : %d\n", f); -#endif - - /* - printf("#--: spotBox(X1:%d, Y1:%d, X2:%d, Y2:%d, W:%d, H:%d) img(W:%d, H:%d)\n\n", - cropBox.spotArea.x1, cropBox.spotArea.y1, cropBox.spotArea.x2, cropBox.spotArea.y2, - cropBox.getWidth(), cropBox.getHeight(), - cropBox.image->getWidth(), cropBox.image->getHeight() - ); - */ -} - -#endif + /* ORIGINAL NOTES + * + * The method used here is similar to the lighting invariant correction + * method but slightly different: we do not divide the RGB components, + * but substract them I2 = I0 - I1, where I0 is the sample image to be + * corrected, I1 is the reference pattern. Then we solve DeltaI=0 + * (Laplace) with I2 Dirichlet conditions at the borders of the + * mask. The solver is a unoptimized red/black checker Gauss-Siedel + * with an over-relaxation factor of 1.8. It can benefit from a + * multi-grid evaluation of an initial solution before the main + * iteration loop. + * + * I reduced the convergence criteria to 0.1% (0.001) as we are + * dealing here with RGB integer components, more is overkill. + * + * Jean-Yves Couleaud cjyves@free.fr + */ - - - -#if 0 -void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, int tr) -{ - - Alpha mask; - - - for (const auto entry : entries) { - float featherRadius = entry.getFeatherRadius(); - int scaledFeatherRadius = featherRadius / pp.getSkip (); - - SpotBox srcBox(entry, SpotBox::Type::SOURCE); - srcBox.translate(-pp.getX(), -pp.getY()); - srcBox /= float (pp.getSkip()); - - SpotBox dstBox(entry, SpotBox::Type::TARGET); - dstBox.translate(-pp.getX(), -pp.getY()); - dstBox /= float (pp.getSkip()); - - //printf(" -> X: %04d > %04d\n -> Y: %04d > %04d\n", dst_XMin, dst_XMax, dst_YMin, dst_YMax); - - // scaled spot is too small, we do not preview it - if (scaledFeatherRadius < 2 && pp.getSkip() != 1) { -#ifndef NDEBUG - - if (options.rtSettings.verbose) { - printf ("Skipping spot located at %d x %d, too small for the preview zoom rate\n", entry.sourcePos.x, entry.sourcePos.y); - } - -#endif - continue; - } - - // skipping entries totally transparent - if (entry.opacity == 0.) { -#ifndef NDEBUG - - if (options.rtSettings.verbose) { - printf ("Skipping spot located at %d x %d: opacity=%.3f\n", entry.sourcePos.x, entry.sourcePos.y, entry.opacity); - } - - continue; -#endif - } - - // skipping entries where the source circle isn't inside the image bounds, even partially - if (src_XMin < 0 || src_XMax >= img->getWidth() || src_YMin < 0 || src_YMax >= img->getHeight()) { -#ifndef NDEBUG - - if (options.rtSettings.verbose) { - printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity); - printf ("%d < 0 || %d >= %d || %d < 0 || %d >= %d\n", - src_XMin, src_XMax, img->getWidth(), src_YMin, src_YMax, img->getHeight()); - } - -#endif - continue; - } - - // skipping entries where the dest circle is completely outside the image bounds - /* - if (dst_XMin >= img->getWidth() || dst_XMax <= 0 || dst_YMin >= img->getHeight() || dst_YMax <= 0) { -#ifndef NDEBUG - - if (options.rtSettings.verbose) { - printf ("Skipping spot located at %d x %d, from the data at %d x %d, radius=%d, feather=%.3f, opacity=%.3f: source out of bounds\n", entry.sourcePos.x, entry.sourcePos.y, entry.targetPos.x, entry.targetPos.y, entry.radius, entry.feather, entry.opacity); - printf ("%d >= %d || %d <= 0 || %d >= %d || %d <= 0\n", - dst_XMin, img->getWidth(), dst_XMax, dst_YMin, img->getHeight(), dst_YMax); - } - -#endif - continue; - } - */ + /* Original Algorithm Design: + * + * T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning + * http://www.tgeorgiev.net/Photoshop_Healing.pdf + */ // ----------------- Core function ----------------- -#if 0 int scaledPPX = pp.getX() / pp.skip; int scaledPPY = pp.getY() / pp.skip; int scaledPPW = pp.getWidth() / pp.skip + (pp.getWidth() % pp.getSkip() > 0); @@ -810,7 +447,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s // repeat until convergence or max iterations for (int n = 0; n < MAX_ITER; ++n) { - printf ("<<< n=#%d\n", n); // ---------------------------------------------------------------- /* Perform one iteration of the Laplace solver for matrix. Store the @@ -829,8 +465,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s // do reds for (i = 0; i < matrix.getHeight(); ++i) { for (j = i % 2; j < matrix.getWidth(); j += 2) { - printf ("/%d,%d", j, i); - if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) { // do nothing at the boundary or outside mask solution.r (i, j) = matrix.r (i, j); @@ -893,8 +527,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s */ for (i = 0; i < matrix.getHeight(); i++) { for (j = (i % 2) ? 0 : 1; j < matrix.getWidth(); j += 2) { - printf (":%d,%d", j, i); - if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) { // do nothing at the boundary or outside mask solution.r (i, j) = matrix.r (i, j); @@ -956,16 +588,8 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s if (sqr_err_r < EPSILON && sqr_err_g < EPSILON && sqr_err_b < EPSILON) { break; } - - printf ("\n>>> n=#%d\n", n); } - printf ("\n"); -#endif - - - - // add solution to original image and store in tempPR for (int i = 0, i2 = dst_YMin; i2 < dst_YMax - 1; ++i, ++i2) { if (i2 < 0 || i2 >= img->getHeight()) { @@ -977,25 +601,189 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s continue; } - //float c2 = float (mask (i, j)) / 255.f; - //float c1 = 1.f - c2; - //resultPR->r(i,j) = (unsigned char) CLAMP0255 ( ROUND( double(first->r(i,j)) + double(secondPR->r(i,j)) ) ); - - - img->r (i2, j2) = 65535.0f; //img->r(i2,j2)*c1 + srcBuff->r(i,j)*c2; - img->g (i2, j2) = 0.0f; //img->g(i2,j2)*c1 + srcBuff->g(i,j)*c2; - img->b (i2, j2) = 0.0f; //img->b(i2,j2)*c1 + srcBuff->b(i,j)*c2; - /* + float c2 = float (mask (i, j)) / 255.f; + float c1 = 1.f - c2; + resultPR->r(i,j) = (unsigned char) CLAMP0255 ( ROUND( double(first->r(i,j)) + double(secondPR->r(i,j)) ) ); img->r(i2,j2) = img->r(i2,j2)*c1 + (solution.r(i,j) + srcBuff->r(i,j))*c2; img->g(i2,j2) = img->g(i2,j2)*c1 + (solution.g(i,j) + srcBuff->g(i,j))*c2; img->b(i2,j2) = img->b(i2,j2)*c1 + (solution.b(i,j) + srcBuff->b(i,j))*c2; - */ + } + } + } +#endif + + // Copy the intersecting part + bool copyImgTo(SpotBox &destBox) { + Imagefloat *destImg = destBox.image; + + if (image == nullptr || destImg == nullptr) { + std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl; + return false; + } + + std::unique_ptr intersection; + + if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) { + return false; + } + + Imagefloat *srcImg = image; + Imagefloat *dstImg = destBox.image; + + int srcImgY = intersection->y1 - imgArea.y1; + int dstImgY = intersection->y1 - destBox.imgArea.y1; + for (int y = intersection->y1; y <= intersection->y2; ++y) { + int srcImgX = intersection->x1 - imgArea.x1; + int dstImgX = intersection->x1 - destBox.imgArea.x1; + + for (int x = intersection->x1; x <= intersection->x2; ++x) { + dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX); + dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX); + dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX); + ++srcImgX; + ++dstImgX; + } + ++srcImgY; + ++dstImgY; + } + + return true; + } +}; + +void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr) +{ + //Get the clipped image areas (src & dst) from the source image + + std::vector< std::shared_ptr > srcSpotBoxs; + std::vector< std::shared_ptr > dstSpotBoxs; + int fullImgWidth = 0; + int fullImgHeight = 0; + imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr); + SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL); + SpotBox cropBox(pp.getX(), pp.getY(), + pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1, + 0, 0, img, SpotBox::Type::FINAL); + + std::set visibleSpots; // list of dest spots intersecting the preview's crop + int i = 0; + + for (auto entry : params->spot.entries) { + std::shared_ptr srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE)); + std::shared_ptr dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET)); + if ( !srcSpotBox->setIntersectionWith(fullImageBox) + || !dstSpotBox->setIntersectionWith(fullImageBox) + || !srcSpotBox->imageIntersects(*dstSpotBox, true)) + { + continue; + ++i; + } + + // If spot intersect the preview image, add it to the visible spots + if (dstSpotBox->spotIntersects(cropBox)) { + visibleSpots.insert(i); + } + ++i; + + // Source area + PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1, + srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip()); + int w = 0; + int h = 0; + imgsrc->getSize(spp, w, h); + *srcSpotBox /= pp.getSkip(); + srcSpotBox->allocImage(); + Imagefloat *srcImage = srcSpotBox->getImage(); + for (int y = 0; y < (int)srcImage->getHeight(); ++y) { + for (int x = 0; x < (int)srcImage->getWidth(); ++x) { + srcImage->r(y, x) = 60000.f; + srcImage->g(y, x) = 500.f; + srcImage->b(y, x) = 500.f; } } + imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw); + if (cmp) { + imgsrc->convertColorSpace(srcImage, *cmp, currWB); + } + assert(srcSpotBox->checkImageSize()); + + + // Destination area + spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(), + dstSpotBox->getImageHeight(), pp.getSkip()); + *dstSpotBox /= pp.getSkip(); + dstSpotBox->allocImage(); + Imagefloat *dstImage = dstSpotBox->getImage(); + for (int y = 0; y < (int)dstImage->getHeight(); ++y) { + for (int x = 0; x < (int)dstImage->getWidth(); ++x) { + dstImage->r(y, x) = 500.f; + dstImage->g(y, x) = 500.f; + dstImage->b(y, x) = 60000.f; + } + } + imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw); + if (cmp) { + imgsrc->convertColorSpace(dstImage, *cmp, currWB); + } + assert(dstSpotBox->checkImageSize()); + + // Update the intersectionArea between src and dest + if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) { + srcSpotBoxs.push_back(srcSpotBox); + dstSpotBoxs.push_back(dstSpotBox); + } + + } + + // Construct list of upstream dependancies + + std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots + for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) { + int spotNbr = *i; + requiredSpots.insert(spotNbr); + if (spotNbr > 0) { + for (int j = spotNbr - 1; j >= 0; --j) { + if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) { + requiredSpots.insert(spotNbr); + } + } + } + } + + // Process spots and copy them downstream + + for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) { + // Process + srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i)); + + // Propagate + std::set positiveSpots; // For DEBUG purpose only ! + auto j = i; + ++j; + while (j != requiredSpots.end()) { + bool intersectionFound = false; + int i_ = *i; + int j_ = *j; + intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_)); + intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_)); + if (intersectionFound) { + positiveSpots.insert(j_); + } + ++j; + } + } + + // Copy the dest spot to the preview image + cropBox /= pp.getSkip(); + cropBox.tuneImageSize(); + cropBox.intersectionArea = cropBox.imgArea; + + int f = 0; + for (auto i : visibleSpots) { + f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0; } } -#endif } From a46b85f226c04046b6b50066e226be93abf4e6a6 Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 15 Aug 2019 19:01:30 +0200 Subject: [PATCH 12/34] Fix crash reported in #2239 + suppress warning from header files Crash reported here https://github.com/Beep6581/RawTherapee/issues/2239#issuecomment-521585237 --- rtengine/improccoordinator.h | 4 ++-- rtengine/improcfun.h | 2 +- rtgui/spot.cc | 20 ++++++++++--------- rtgui/spot.h | 38 ++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/rtengine/improccoordinator.h b/rtengine/improccoordinator.h index e66867d2b..061876968 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -188,8 +188,6 @@ protected: void updateLRGBHistograms (); void setScale (int prevscale); void updatePreviewImage (int todo, bool panningRelatedChange); - void setTweakOperator (TweakOperator *tOperator); - void unsetTweakOperator (TweakOperator *tOperator); MyMutex mProcessing; const std::unique_ptr params; // used for the rendering, can be eventually tweaked @@ -275,6 +273,8 @@ public: DetailedCrop* createCrop (::EditDataProvider *editDataProvider, bool isDetailWindow) override; + void setTweakOperator (TweakOperator *tOperator) override; + void unsetTweakOperator (TweakOperator *tOperator) override; bool getAutoWB (double& temp, double& green, double equal, double tempBias) override; void getCamWB (double& temp, double& green) override; void getSpotWB (int x, int y, int rectSize, double& temp, double& green) override; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index a1b79fc38..01c85953a 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -42,8 +42,8 @@ namespace procparams { class ProcParams; -class SpotEntry; +struct SpotEntry; struct DirPyrDenoiseParams; struct SharpeningParams; struct VignettingParams; diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 024f1ae26..9c5702aa3 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -117,6 +117,8 @@ void Spot::read (const ProcParams* pp, const ParamsEdited* pedited) setEnabled (pp->spot.enabled); lastEnabled = pp->spot.enabled; + activeSpot = -1; + lastObject = -1; if (spots.size() != oldSize) { createGeometry(); @@ -430,12 +432,12 @@ void Spot::deleteSelectedEntry() } // TODO -CursorShape Spot::getCursor (const int objectID) +CursorShape Spot::getCursor (int objectID) { return CSHandOpen; } -bool Spot::mouseOver (const int modifierKey) +bool Spot::mouseOver (int modifierKey) { EditDataProvider* editProvider = getEditProvider(); @@ -485,7 +487,7 @@ bool Spot::mouseOver (const int modifierKey) } // Create a new Target and Source point or start the drag of a Target point under the cursor -bool Spot::button1Pressed (const int modifierKey) +bool Spot::button1Pressed (int modifierKey) { EditDataProvider* editProvider = getEditProvider(); @@ -521,7 +523,7 @@ bool Spot::button1Released() } // Delete a point -bool Spot::button2Pressed (const int modifierKey) +bool Spot::button2Pressed (int modifierKey) { EditDataProvider* editProvider = getEditProvider(); @@ -537,7 +539,7 @@ bool Spot::button2Pressed (const int modifierKey) } // Create a new Target and Source point or start the drag of a Target point under the cursor -bool Spot::button3Pressed (const int modifierKey) +bool Spot::button3Pressed (int modifierKey) { EditDataProvider* editProvider = getEditProvider(); @@ -575,7 +577,7 @@ bool Spot::button3Released() return false; } -bool Spot::drag1 (const int modifierKey) +bool Spot::drag1 (int modifierKey) { EditDataProvider *editProvider = getEditProvider(); int imW, imH; @@ -656,7 +658,7 @@ bool Spot::drag1 (const int modifierKey) return modified; } -bool Spot::drag3 (const int modifierKey) +bool Spot::drag3 (int modifierKey) { EditDataProvider *editProvider = getEditProvider(); int imW, imH; @@ -683,12 +685,12 @@ bool Spot::drag3 (const int modifierKey) return modified; } -bool Spot::pick2 (const bool picked) +bool Spot::pick2 (bool picked) { return pick3 (picked); } -bool Spot::pick3 (const bool picked) +bool Spot::pick3 (bool picked) { EditDataProvider* editProvider = getEditProvider(); diff --git a/rtgui/spot.h b/rtgui/spot.h index f1a4cc604..eea5e8933 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -53,7 +53,7 @@ * (the point will be deleted on button release). */ -class Spot : public ToolParamBlock, public FoldableToolPanel, public EditSubscriber, public rtengine::TweakOperator +class Spot : public ToolParamBlock, public FoldableToolPanel, public rtengine::TweakOperator, public EditSubscriber { private: @@ -93,31 +93,31 @@ public: Spot (); ~Spot (); - void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr); - void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr); + void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited = nullptr) override; + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr) override; - void enabledChanged (); + void enabledChanged () override; - void setEditProvider (EditDataProvider* provider); + void setEditProvider (EditDataProvider* provider) override; - void setBatchMode (bool batchMode); + void setBatchMode (bool batchMode) override; // 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 (); + CursorShape getCursor (int objectID); + bool mouseOver (int modifierKey) override; + bool button1Pressed (int modifierKey) override; + bool button1Released () override; + bool button2Pressed (int modifierKey) override; + bool button3Pressed (int modifierKey) override; + bool button3Released () override; + bool drag1 (int modifierKey) override; + bool drag3 (int modifierKey) override; + bool pick2 (bool picked) override; + bool pick3 (bool picked) override; + void switchOffEditMode () override; //TweakOperator interface - void tweakParams(rtengine::procparams::ProcParams& pparams) override; + void tweakParams(rtengine::procparams::ProcParams& pparams); rtengine::ProcEvent EvSpotEnabled; rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode From 68e26fb6489d0bb58eee14d2e543fed4589ff69c Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 15 Aug 2019 20:23:29 +0200 Subject: [PATCH 13/34] More bugfix for issue #2239 --- rtengine/spot.cc | 13 ++----------- rtgui/spot.cc | 2 +- rtgui/spot.h | 4 ++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/rtengine/spot.cc b/rtengine/spot.cc index b12f5bc40..2244d882e 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -106,17 +106,8 @@ public: return *this; } - /* - float fv = float(v); - x1 = int(float(x1) / fv + 0.5f); - y1 = int(float(y1) / fv + 0.5f); - x2 = int(float(x2) / fv + 0.5f); - y2 = int(float(y2) / fv + 0.5f); - */ - - // Aletrnate rounding possibility int w = x2 - x1 + 1; - int h = x2 - x1 + 1; + int h = y2 - y1 + 1; w = w / v + (w % v > 0); h = h / v + (h % v > 0); x1 /= v; @@ -188,7 +179,7 @@ public: } spotArea /= v; imgArea /= v; - radius = radius / float(v); + radius /= float(v); featherRadius = getWidth() / 2.f; // intersectionArea doesn't need resize, because it's set after resizing return *this; diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 9c5702aa3..9a957f77a 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -432,7 +432,7 @@ void Spot::deleteSelectedEntry() } // TODO -CursorShape Spot::getCursor (int objectID) +CursorShape Spot::getCursor (int objectID) const { return CSHandOpen; } diff --git a/rtgui/spot.h b/rtgui/spot.h index eea5e8933..aa0daa4ca 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -103,7 +103,7 @@ public: void setBatchMode (bool batchMode) override; // EditSubscriber interface - CursorShape getCursor (int objectID); + CursorShape getCursor (int objectID) const; bool mouseOver (int modifierKey) override; bool button1Pressed (int modifierKey) override; bool button1Released () override; @@ -117,7 +117,7 @@ public: void switchOffEditMode () override; //TweakOperator interface - void tweakParams(rtengine::procparams::ProcParams& pparams); + void tweakParams(rtengine::procparams::ProcParams& pparams) override; rtengine::ProcEvent EvSpotEnabled; rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode From 50064b7e3c890a6437fef656a6b6a853071acc44 Mon Sep 17 00:00:00 2001 From: Hombre Date: Thu, 15 Aug 2019 23:39:44 +0200 Subject: [PATCH 14/34] Supressing a warning when building (see issue #2239) --- rtgui/spot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/spot.h b/rtgui/spot.h index aa0daa4ca..7604d7846 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -103,7 +103,7 @@ public: void setBatchMode (bool batchMode) override; // EditSubscriber interface - CursorShape getCursor (int objectID) const; + CursorShape getCursor (int objectID) const override; bool mouseOver (int modifierKey) override; bool button1Pressed (int modifierKey) override; bool button1Released () override; From 0d19cd40a6371bbcde6b9939149ef06cbca4880f Mon Sep 17 00:00:00 2001 From: Hombre Date: Fri, 16 Aug 2019 02:46:55 +0200 Subject: [PATCH 15/34] Adding documentation of Spot Removal in a tooltip (see issue #2239) --- rtdata/languages/Francais | 1 + rtdata/languages/default | 1 + rtgui/spot.cc | 1 + 3 files changed, 3 insertions(+) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index 8011b9189..0fce8721c 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -2010,6 +2010,7 @@ TP_SOFTLIGHT_LABEL;Lumière douce TP_SOFTLIGHT_STRENGTH;Force TP_SPOT_COUNTLABEL;%1 point(s) TP_SPOT_ENTRYCHANGED;Modification d'un point +TP_SPOT_HINT;Cliquez sur ce bouton pour pouvoir opérer sur la zone de prévisualisation.\n\nPour ajouter un spot, pressez Ctrl et le bouton gauche de la souris, tirez le cercle (la touche Ctrl peut être relâchée) vers la position source, puis relâchez le bouton de la souris.\n\nPour éditer un spot, placez le curseur au-dessus de la marque blanche situant une zone éditée, faisant apparaître la géométrie d'édition.\n\nPour déplacer le spot source ou destination, placez le curseur en son centre et tirez le.\n\nLe cercle intérieur (zone d'effet maximum) et le cercle "d'adoucicement" peuvent être redimmensionné en plaçant le curseur dessus (le cercle devient orange) et en le tirant (le cercle devient rouge).\n\nQuand les changements sont terminés, un clic droit en dehors de tout spot termine le mode d'édition, ou cliquez à nouveau sur ce bouton. TP_SPOT_LABEL;Retrait de taches TP_TM_FATTAL_AMOUNT;Quantité TP_TM_FATTAL_ANCHOR;Ancre diff --git a/rtdata/languages/default b/rtdata/languages/default index 3296a4c84..d887d9864 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -2043,6 +2043,7 @@ TP_SOFTLIGHT_LABEL;Soft Light TP_SOFTLIGHT_STRENGTH;Strength TP_SPOT_COUNTLABEL;%1 point(s) TP_SPOT_ENTRYCHANGED;Point changed +TP_SPOT_HINT;Click on this button to be able to operate on the preview area.\n\nTo edit a spot, hover the white mark locating an edited aera, making the editing geometry appear.\n\nTo add a spot, press Ctrl and left mouse button, drag the circle (Ctrl key can be released) to a source location, then release the mouse button.\n\nTo move the source or destination spot, hover its center then drag it.\n\nThe inner circle (maximum effect area) and the "feather" circle can be resized by hovering them (the circle becomes orange) and draging it (the circle becomes red).\n\nWhen the changes are done, right click outside any spot to end the Spot editing mode, or click on this button again. TP_SPOT_LABEL;Spot removal TP_TM_FATTAL_AMOUNT;Amount TP_TM_FATTAL_ANCHOR;Anchor diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 9a957f77a..6392b95ab 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -40,6 +40,7 @@ Spot::Spot() : FoldableToolPanel (this, "spot", M ("TP_SPOT_LABEL"), true, true) edit = Gtk::manage (new Gtk::ToggleButton()); edit->add (*Gtk::manage (new RTImage ("edit-point.png"))); editConn = edit->signal_toggled().connect ( sigc::mem_fun (*this, &Spot::editToggled) ); + edit->set_tooltip_text(M("TP_SPOT_HINT")); reset = Gtk::manage (new Gtk::Button ()); reset->add (*Gtk::manage (new RTImage ("undo-small.png"))); From 82e7caa635eb109fe5a7669a6ea69eb9316a013d Mon Sep 17 00:00:00 2001 From: Hombre Date: Sun, 1 Sep 2019 02:08:48 +0200 Subject: [PATCH 16/34] Spot removal : differentiating source/dest, betted cursor handling + some code cleanup from floessie (see issue #2239) --- rtengine/spot.cc | 47 +++++------- rtgui/cropwindow.cc | 27 +++++-- rtgui/curveeditor.cc | 2 +- rtgui/curveeditor.h | 2 +- rtgui/editcallbacks.cc | 8 +-- rtgui/editcallbacks.h | 10 +-- rtgui/editwidgets.cc | 157 +++++++++++++++++++++-------------------- rtgui/editwidgets.h | 20 +++++- rtgui/filmnegative.cc | 2 +- rtgui/filmnegative.h | 2 +- rtgui/gradient.cc | 2 +- rtgui/gradient.h | 2 +- rtgui/spot.cc | 64 +++++++++++++++-- rtgui/spot.h | 9 ++- 14 files changed, 222 insertions(+), 132 deletions(-) diff --git a/rtengine/spot.cc b/rtengine/spot.cc index 2244d882e..e9d43ef31 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -23,18 +23,6 @@ #include "imagesource.h" #include -namespace -{ - -// "ceil" rounding -template -constexpr T skips(T a, T b) -{ - return a / b + static_cast(a % b); -} - -} - namespace rtengine { @@ -48,14 +36,12 @@ public: }; struct Rectangle { - public: int x1; int y1; int x2; int y2; - Rectangle() : x1(0), y1(0), x2(0), y2(0) {} - Rectangle(const Rectangle &other) : x1(other.x1), y1(other.y1), x2(other.x2), y2(other.y2) {} + Rectangle() : Rectangle(0, 0, 0, 0) {} Rectangle(int X1, int Y1, int X2, int Y2) : x1(X1), y1(Y1), x2(X2), y2(Y2) {} bool intersects(const Rectangle &other) const { @@ -65,21 +51,24 @@ public: bool getIntersection(const Rectangle &other, std::unique_ptr &intersection) const { if (intersects(other)) { - if (!intersection) { - intersection.reset(new Rectangle()); - } - intersection->x1 = rtengine::max(x1, other.x1); - intersection->x2 = rtengine::min(x2, other.x2); - intersection->y1 = rtengine::max(y1, other.y1); - intersection->y2 = rtengine::min(y2, other.y2); + std::unique_ptr intsec( + new Rectangle( + rtengine::max(x1, other.x1), + rtengine::max(y1, other.y1), + rtengine::min(x2, other.x2), + rtengine::min(y2, other.y2) + ) + ); - if (intersection->x1 > intersection->x2 || intersection->y1 > intersection->y2) { - intersection.release(); + if (intsec->x1 > intsec->x2 || intsec->y1 > intsec->y2) { return false; } + + intersection = std::move(intsec); return true; } if (intersection) { + // There's no intersection, we delete the Rectangle structure intersection.release(); } return false; @@ -101,15 +90,15 @@ public: return *this; } - Rectangle& operator/=(const int &v) { + Rectangle& operator/=(int v) { if (v == 1) { return *this; } int w = x2 - x1 + 1; int h = y2 - y1 + 1; - w = w / v + (w % v > 0); - h = h / v + (h % v > 0); + w = w / v + static_cast(w % v); + h = h / v + static_cast(h % v); x1 /= v; y1 /= v; x2 = x1 + w - 1; @@ -117,7 +106,7 @@ public: return *this; } -}; + }; private: Type type; @@ -173,7 +162,7 @@ public: } } - SpotBox& operator /=(const int& v) { + SpotBox& operator /=(int v) { if (v == 1) { return *this; } diff --git a/rtgui/cropwindow.cc b/rtgui/cropwindow.cc index 1e6ae3238..69ee6b30e 100644 --- a/rtgui/cropwindow.cc +++ b/rtgui/cropwindow.cc @@ -497,7 +497,7 @@ void CropWindow::buttonPress (int button, int type, int bstate, int x, int y) cropgl->cropInit (cropHandler.cropParams->x, cropHandler.cropParams->y, cropHandler.cropParams->w, cropHandler.cropParams->h); } else if (iarea->getToolMode () == TMHand) { if (editSubscriber) { - if ((cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) && editSubscriber->getEditingType() == ET_PIPETTE && (bstate & GDK_CONTROL_MASK))) { + if ((cropgl && cropgl->inImageArea(iarea->posImage.x, iarea->posImage.y) && (editSubscriber->getEditingType() == ET_PIPETTE && (bstate & GDK_CONTROL_MASK))) || editSubscriber->getEditingType() == ET_OBJECTS) { needRedraw = editSubscriber->button1Pressed(bstate); if (editSubscriber->isDragging()) { state = SEditDrag1; @@ -1277,7 +1277,10 @@ void CropWindow::updateCursor (int x, int y) } else if (onArea (CropToolBar, x, y)) { newType = CSMove; } else if (iarea->getObject() > -1 && editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { - newType = editSubscriber->getCursor(iarea->getObject()); + int cursorX; + int cursorY; + screenCoordToImage (x, y, cursorX, cursorY); + newType = editSubscriber->getCursor(iarea->getObject(), cursorX, cursorY); } else if (onArea (CropResize, x, y)) { newType = CSResizeDiagonal; } else if (tm == TMColorPicker && hoveredPicker) { @@ -1304,7 +1307,10 @@ void CropWindow::updateCursor (int x, int y) } if (objectID > -1) { - newType = editSubscriber->getCursor(objectID); + int cursorX; + int cursorY; + screenCoordToImage (x, y, cursorX, cursorY); + newType = editSubscriber->getCursor(objectID, cursorX, cursorY); } else if (tm == TMHand) { if (onArea (CropObserved, x, y)) { newType = CSMove; @@ -1330,7 +1336,10 @@ void CropWindow::updateCursor (int x, int y) } if (objectID > -1) { - newType = editSubscriber->getCursor(objectID); + int cursorX; + int cursorY; + screenCoordToImage (x, y, cursorX, cursorY); + newType = editSubscriber->getCursor(objectID, cursorX, cursorY); } else { newType = CSArrow; } @@ -1359,6 +1368,16 @@ void CropWindow::updateCursor (int x, int y) newType = CSResizeDiagonal; } else if (state == SDragPicker) { newType = CSMove2D; + } else if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) { + int objectID = iarea->getObject(); + if (objectID > -1) { + int cursorX; + int cursorY; + screenCoordToImage (x, y, cursorX, cursorY); + newType = editSubscriber->getCursor(objectID, cursorX, cursorY); + } else { + newType = CSArrow; + } } if (newType != cursor_type) { diff --git a/rtgui/curveeditor.cc b/rtgui/curveeditor.cc index 528378423..99e371850 100644 --- a/rtgui/curveeditor.cc +++ b/rtgui/curveeditor.cc @@ -490,7 +490,7 @@ bool CurveEditor::drag1(int modifierKey) return false; } -CursorShape CurveEditor::getCursor(int objectID) const +CursorShape CurveEditor::getCursor(int objectID, int xPos, int yPos) const { if (remoteDrag) { return CSResizeHeight; diff --git a/rtgui/curveeditor.h b/rtgui/curveeditor.h index e38d3f205..10963aa1d 100644 --- a/rtgui/curveeditor.h +++ b/rtgui/curveeditor.h @@ -133,7 +133,7 @@ public: bool button1Pressed(int modifierKey) override; bool button1Released() override; bool drag1(int modifierKey) override; - CursorShape getCursor(int objectID) const override; + CursorShape getCursor(int objectID, int xPos, int yPos) const override; }; diff --git a/rtgui/editcallbacks.cc b/rtgui/editcallbacks.cc index d51d672a5..711e56f11 100644 --- a/rtgui/editcallbacks.cc +++ b/rtgui/editcallbacks.cc @@ -172,10 +172,10 @@ void EditDataProvider::setPipetteVal3(float newVal) pipetteVal3 = newVal; } -CursorShape EditDataProvider::getCursor(int objectID) const +CursorShape EditDataProvider::getCursor(int objectID, int xPos, int yPos) const { if (currSubscriber) { - currSubscriber->getCursor(objectID); + currSubscriber->getCursor(objectID, xPos, yPos); } return CSHandOpen; @@ -186,12 +186,12 @@ EditSubscriber* EditDataProvider::getCurrSubscriber() const return currSubscriber; } -EditDataProvider* EditSubscriber::getEditProvider() +EditDataProvider* EditSubscriber::getEditProvider() const { return provider; } -CursorShape EditSubscriber::getCursor(int objectID) const +CursorShape EditSubscriber::getCursor(int objectID, int xPos, int yPos) const { return CSHandOpen; } diff --git a/rtgui/editcallbacks.h b/rtgui/editcallbacks.h index c4003f9ca..0f5f1a0bc 100644 --- a/rtgui/editcallbacks.h +++ b/rtgui/editcallbacks.h @@ -57,7 +57,7 @@ public: virtual ~EditSubscriber () = default; void setEditProvider(EditDataProvider *provider); - EditDataProvider* getEditProvider (); + EditDataProvider* getEditProvider () const; void setEditID(EditUniqueID ID, BufferType buffType); bool isCurrentSubscriber() const; virtual void subscribe(); @@ -70,8 +70,10 @@ public: bool isPicking() const; /// Returns true if something is being picked /** @brief Get the cursor to be displayed when above handles - @param objectID object currently "hovered" */ - virtual CursorShape getCursor (int objectID) const; + @param objectID object currently "hovered" + @param xPos X cursor position in image space + @param yPos Y cursor position in image space */ + virtual CursorShape getCursor (int objectID, int xPos, int yPos) const; /** @brief Triggered when the mouse is moving over an object This method is also triggered when the cursor is moving over the image in ET_PIPETTE mode @@ -188,7 +190,7 @@ public: void setPipetteVal1(float newVal); void setPipetteVal2(float newVal); void setPipetteVal3(float newVal); - virtual CursorShape getCursor(int objectID) const; + virtual CursorShape getCursor(int objectID, int xPos, int yPos) const; int getPipetteRectSize () const; EditSubscriber* getCurrSubscriber() const; virtual void getImageSize (int &w, int&h) = 0; diff --git a/rtgui/editwidgets.cc b/rtgui/editwidgets.cc index 16cd67cd4..1728d1811 100644 --- a/rtgui/editwidgets.cc +++ b/rtgui/editwidgets.cc @@ -20,7 +20,11 @@ #include "editwidgets.h" #include "editbuffer.h" #include "editcallbacks.h" -#include "../rtengine/rt_math.h" + +const std::vector Geometry::dash = {3., 1.5}; + +#define INNERGEOM_OPACITY 1. +#define OUTERGEOM_OPACITY 0.7 RGBColor Geometry::getInnerLineColor () { @@ -65,7 +69,8 @@ RGBColor Geometry::getOuterLineColor () void Circle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { - if ((flags & F_VISIBLE) && state != INSENSITIVE) { + double lineWidth = getOuterLineWidth(); + if ((flags & F_VISIBLE) && state != INSENSITIVE && lineWidth > 0. && innerLineWidth > 0.) { RGBColor color; if (flags & F_AUTO_COLOR) { @@ -74,8 +79,9 @@ void Circle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer color = outerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), OUTERGEOM_OPACITY * rtengine::min(innerLineWidth / 2.f, 1.f)); + cr->set_line_width (lineWidth); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); rtengine::Coord center_ = center; double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); @@ -105,10 +111,11 @@ void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer color = innerLineColor; } - cr->set_source_rgb(color.getR(), color.getG(), color.getB()); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), INNERGEOM_OPACITY); } - cr->set_line_width( innerLineWidth ); + cr->set_line_width(innerLineWidth); + cr->set_line_cap(flags & F_DASHED ? Cairo::LINE_CAP_BUTT : Cairo::LINE_CAP_ROUND); rtengine::Coord center_ = center; double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); @@ -121,9 +128,12 @@ void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer center_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; } - if (filled && state != INSENSITIVE) { - cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); + if (flags & F_DASHED) { + cr->set_dash(dash, 0.); + } + if (filled) { + cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); if (innerLineWidth > 0.) { cr->fill_preserve(); cr->stroke(); @@ -132,28 +142,20 @@ void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer } } else if (innerLineWidth > 0.) { cr->arc(center_.x + 0.5, center_.y + 0.5, radius_, 0., 2.*rtengine::RT_PI); - - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } + cr->stroke(); } - } + + if (flags & F_DASHED) { + cr->unset_dash(); + } +} } void Circle::drawToMOChannel (Cairo::RefPtr &cr, unsigned short id, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { if (flags & F_HOVERABLE) { cr->set_line_width( getMouseOverLineWidth() ); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); rtengine::Coord center_ = center; double radius_ = radiusInImageSpace ? coordSystem.scaleValueToCanvas(double(radius)) : double(radius); @@ -188,7 +190,8 @@ void Circle::drawToMOChannel (Cairo::RefPtr &cr, unsigned short void Line::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { - if ((flags & F_VISIBLE) && state != INSENSITIVE) { + double lineWidth = getOuterLineWidth(); + if ((flags & F_VISIBLE) && state != INSENSITIVE && lineWidth > 0. && innerLineWidth > 0.) { RGBColor color; if (flags & F_AUTO_COLOR) { @@ -197,8 +200,9 @@ void Line::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer * color = outerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), OUTERGEOM_OPACITY * rtengine::min(innerLineWidth / 2.f, 1.f)); + cr->set_line_width (lineWidth); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); rtengine::Coord begin_ = begin; rtengine::Coord end_ = end; @@ -232,10 +236,11 @@ void Line::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer * color = innerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), INNERGEOM_OPACITY); } cr->set_line_width(innerLineWidth); + cr->set_line_cap(flags & F_DASHED ? Cairo::LINE_CAP_BUTT : Cairo::LINE_CAP_ROUND); rtengine::Coord begin_ = begin; rtengine::Coord end_ = end; @@ -251,21 +256,16 @@ void Line::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer * end_ += objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; } + if (flags & F_DASHED) { + cr->set_dash(dash, 0.); + } + cr->move_to(begin_.x + 0.5, begin_.y + 0.5); cr->line_to(end_.x + 0.5, end_.y + 0.5); + cr->stroke(); - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); + if (flags & F_DASHED) { + cr->unset_dash(); } } } @@ -274,6 +274,7 @@ void Line::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, { if (flags & F_HOVERABLE) { cr->set_line_width( getMouseOverLineWidth() ); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); rtengine::Coord begin_ = begin; rtengine::Coord end_ = end; @@ -302,7 +303,8 @@ void Line::drawToMOChannel(Cairo::RefPtr &cr, unsigned short id, void Polyline::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { - if ((flags & F_VISIBLE) && state != INSENSITIVE && points.size() > 1) { + double lineWidth = getOuterLineWidth(); + if ((flags & F_VISIBLE) && state != INSENSITIVE && points.size() > 1 && lineWidth > 0.) { RGBColor color; if (flags & F_AUTO_COLOR) { @@ -311,8 +313,10 @@ void Polyline::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuff color = outerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), OUTERGEOM_OPACITY * rtengine::min(innerLineWidth / 2.f, 1.f)); + cr->set_line_width (lineWidth); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); + cr->set_line_join(Cairo::LINE_JOIN_ROUND); rtengine::Coord currPos; @@ -355,10 +359,16 @@ void Polyline::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuff color = innerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), INNERGEOM_OPACITY); } - cr->set_line_width( innerLineWidth ); + cr->set_line_width(innerLineWidth); + cr->set_line_cap(flags & F_DASHED ? Cairo::LINE_CAP_BUTT : Cairo::LINE_CAP_ROUND); + cr->set_line_join(Cairo::LINE_JOIN_ROUND); + + if (flags & F_DASHED) { + cr->set_dash(dash, 0.); + } if (filled && state != INSENSITIVE) { rtengine::Coord currPos; @@ -407,20 +417,11 @@ void Polyline::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuff cr->line_to(currPos.x + 0.5, currPos.y + 0.5); } } + cr->stroke(); + } - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } + if (flags & F_DASHED) { + cr->unset_dash(); } } } @@ -437,8 +438,11 @@ void Polyline::drawToMOChannel (Cairo::RefPtr &cr, unsigned shor cr->set_source_rgba (0., 0., 0., (id + 1) / 65535.); } + cr->set_line_width( getMouseOverLineWidth() ); + cr->set_line_cap(Cairo::LINE_CAP_ROUND); + cr->set_line_join(Cairo::LINE_JOIN_ROUND); + for (unsigned int i = 0; i < points.size(); ++i) { - cr->set_line_width( getMouseOverLineWidth() ); currPos = points.at(i); if (datum == IMAGE) { @@ -495,7 +499,8 @@ void Rectangle::setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight) void Rectangle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer *objectBuffer, EditCoordSystem &coordSystem) { - if ((flags & F_VISIBLE) && state != INSENSITIVE) { + double lineWidth = getOuterLineWidth(); + if ((flags & F_VISIBLE) && state != INSENSITIVE && lineWidth > 0. && innerLineWidth > 0.) { RGBColor color; if (flags & F_AUTO_COLOR) { @@ -504,8 +509,9 @@ void Rectangle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuf color = outerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); - cr->set_line_width( getOuterLineWidth() ); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), OUTERGEOM_OPACITY * rtengine::min(innerLineWidth / 2.f, 1.f)); + cr->set_line_width (lineWidth); + cr->set_line_join(Cairo::LINE_JOIN_BEVEL); rtengine::Coord tl, br; @@ -548,10 +554,11 @@ void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuf color = innerLineColor; } - cr->set_source_rgb (color.getR(), color.getG(), color.getB()); + cr->set_source_rgba (color.getR(), color.getG(), color.getB(), INNERGEOM_OPACITY); } - cr->set_line_width( innerLineWidth ); + cr->set_line_width(innerLineWidth); + cr->set_line_join(Cairo::LINE_JOIN_BEVEL); rtengine::Coord tl, br; @@ -571,7 +578,11 @@ void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuf br = bottomRight + objectBuffer->getDataProvider()->posScreen + objectBuffer->getDataProvider()->deltaScreen; } - if (filled && state != INSENSITIVE) { + if (flags & F_DASHED) { + cr->set_dash(dash, 0.); + } + + if (filled) { cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); if (innerLineWidth > 0.) { @@ -582,20 +593,11 @@ void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuf } } else if (innerLineWidth > 0.) { cr->rectangle(tl.x + 0.5, tl.y + 0.5, br.x - tl.x, br.y - tl.y); + cr->stroke(); + } - if (state == INSENSITIVE) { - std::valarray ds(1); - ds[0] = 4; - cr->set_source_rgba(1.0, 1.0, 1.0, 0.618); - cr->stroke_preserve(); - cr->set_source_rgba(0.0, 0.0, 0.0, 0.618); - cr->set_dash(ds, 0); - cr->stroke(); - ds.resize(0); - cr->set_dash(ds, 0); - } else { - cr->stroke(); - } + if (flags & F_DASHED) { + cr->unset_dash(); } } } @@ -604,6 +606,7 @@ void Rectangle::drawToMOChannel(Cairo::RefPtr &cr, unsigned shor { if (flags & F_HOVERABLE) { cr->set_line_width( getMouseOverLineWidth() ); + cr->set_line_join(Cairo::LINE_JOIN_ROUND); rtengine::Coord tl, br; diff --git a/rtgui/editwidgets.h b/rtgui/editwidgets.h index 1ae185f7a..0282e2bb1 100644 --- a/rtgui/editwidgets.h +++ b/rtgui/editwidgets.h @@ -24,6 +24,7 @@ #include "editbuffer.h" #include "editcoordsys.h" #include "../rtengine/coord.h" +#include "../rtengine/rt_math.h" class ObjectMOBuffer; @@ -208,6 +209,8 @@ public: F_VISIBLE = 1 << 0, /// true if the geometry have to be drawn on the visible layer F_HOVERABLE = 1 << 1, /// true if the geometry have to be drawn on the "mouse over" layer F_AUTO_COLOR = 1 << 2, /// true if the color depend on the state value, not the color field above + F_DASHED = 1 << 3, /// true if the geometry have to be drawn as a dash line + // (TODO: add a F_LARGE_DASH to have two different dash size ?) }; /// @brief Key point of the image's rectangle that is used to locate the icon copy to the target point: @@ -224,6 +227,7 @@ public: }; protected: + static const std::vector dash; RGBColor innerLineColor; RGBColor outerLineColor; short flags; @@ -249,6 +253,8 @@ public: void setVisible (bool visible); bool isHoverable (); void setHoverable (bool visible); + bool isDashed (); + void setDashed (bool dashed); // setActive will enable/disable the visible and hoverable flags in one shot! @@ -427,7 +433,7 @@ inline void Geometry::setOuterLineColor (char r, char g, char b) { } inline double Geometry::getMouseOverLineWidth () { - return getOuterLineWidth () + 2.; + return rtengine::max(double(innerLineWidth), 1.) + 2.; } inline void Geometry::setAutoColor (bool aColor) { @@ -462,6 +468,18 @@ inline void Geometry::setHoverable (bool hoverable) { } } +inline bool Geometry::isDashed () { + return flags & F_DASHED; +} + +inline void Geometry::setDashed (bool dashed) { + if (dashed) { + flags |= F_DASHED; + } else { + flags &= ~F_DASHED; + } +} + inline void Geometry::setActive (bool active) { if (active) { flags |= (F_VISIBLE | F_HOVERABLE); diff --git a/rtgui/filmnegative.cc b/rtgui/filmnegative.cc index 6e93b2364..d490be488 100644 --- a/rtgui/filmnegative.cc +++ b/rtgui/filmnegative.cc @@ -225,7 +225,7 @@ void FilmNegative::setEditProvider(EditDataProvider* provider) EditSubscriber::setEditProvider(provider); } -CursorShape FilmNegative::getCursor(int objectID) const +CursorShape FilmNegative::getCursor(int objectID, int xPos, int yPos) const { return CSSpotWB; } diff --git a/rtgui/filmnegative.h b/rtgui/filmnegative.h index a1efdc455..46993dba5 100644 --- a/rtgui/filmnegative.h +++ b/rtgui/filmnegative.h @@ -59,7 +59,7 @@ public: void setEditProvider(EditDataProvider* provider) override; // EditSubscriber interface - CursorShape getCursor(int objectID) const override; + CursorShape getCursor(int objectID, int xPos, int yPos) const override; bool mouseOver(int modifierKey) override; bool button1Pressed(int modifierKey) override; bool button1Released() override; diff --git a/rtgui/gradient.cc b/rtgui/gradient.cc index 4f389e112..fda961912 100644 --- a/rtgui/gradient.cc +++ b/rtgui/gradient.cc @@ -327,7 +327,7 @@ void Gradient::editToggled () } } -CursorShape Gradient::getCursor(int objectID) const +CursorShape Gradient::getCursor(int objectID, int xPos, int yPos) const { switch (objectID) { case (0): diff --git a/rtgui/gradient.h b/rtgui/gradient.h index 05a267a0d..3b8a18f97 100644 --- a/rtgui/gradient.h +++ b/rtgui/gradient.h @@ -51,7 +51,7 @@ public: void setEditProvider (EditDataProvider* provider) override; // EditSubscriber interface - CursorShape getCursor(int objectID) const override; + CursorShape getCursor(int objectID, int xPos, int yPos) const override; bool mouseOver(int modifierKey) override; bool button1Pressed(int modifierKey) override; bool button1Released() override; diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 6392b95ab..5fb1de8c9 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -32,8 +32,14 @@ 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 (nullptr) +Spot::Spot() : + FoldableToolPanel(this, "spot", M ("TP_SPOT_LABEL"), true, true), + EditSubscriber(ET_OBJECTS), + draggedSide(DraggedSide::NONE), + lastObject(-1), + activeSpot(-1), + sourceIcon("spot-normal.png", "spot-active.png", "spot-prelight.png", "", "", Geometry::DP_CENTERCENTER), + editedCheckBox(nullptr) { countLabel = Gtk::manage (new Gtk::Label (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0))); @@ -61,6 +67,7 @@ Spot::Spot() : FoldableToolPanel (this, "spot", M ("TP_SPOT_LABEL"), true, true) sourceCircle.datum = Geometry::IMAGE; sourceCircle.setActive (false); sourceCircle.radiusInImageSpace = true; + sourceCircle.setDashed(true); sourceMODisc.datum = Geometry::IMAGE; sourceMODisc.setActive (false); sourceMODisc.radiusInImageSpace = true; @@ -77,9 +84,12 @@ Spot::Spot() : FoldableToolPanel (this, "spot", M ("TP_SPOT_LABEL"), true, true) sourceFeatherCircle.datum = Geometry::IMAGE; sourceFeatherCircle.setActive (false); sourceFeatherCircle.radiusInImageSpace = true; + sourceFeatherCircle.setDashed(true); + sourceFeatherCircle.innerLineWidth = 0.7; targetFeatherCircle.datum = Geometry::IMAGE; targetFeatherCircle.setActive (false); targetFeatherCircle.radiusInImageSpace = true; + targetFeatherCircle.innerLineWidth = 0.7; link.datum = Geometry::IMAGE; link.setActive (false); @@ -241,6 +251,10 @@ Geometry* Spot::getVisibleGeometryFromMO (int MOID) return &sourceIcon; } + if (MOID > STATIC_MO_OBJ_NBR) { + return EditSubscriber::visibleGeometry.at(MOID - STATIC_MO_OBJ_NBR); + } + return EditSubscriber::mouseOverGeometry.at (MOID); } @@ -259,10 +273,12 @@ void Spot::createGeometry () delete EditSubscriber::visibleGeometry.at (i); } - size_t i = 0, j = 0; + // mouse over geometry starts with the static geometry, then the spot's icon geometry EditSubscriber::mouseOverGeometry.resize (STATIC_MO_OBJ_NBR + nbrEntry); + // visible geometry starts with the spot's icon geometry, then the static geometry EditSubscriber::visibleGeometry.resize (nbrEntry + STATIC_VISIBLE_OBJ_NBR); + size_t i = 0, j = 0; 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 @@ -351,6 +367,10 @@ void Spot::updateGeometry() } else { link.setActive (false); } + + sourceCircle.setVisible(draggedSide != DraggedSide::SOURCE); + targetCircle.setVisible(draggedSide != DraggedSide::TARGET); + link.setVisible(draggedSide == DraggedSide::NONE); } else { targetCircle.setActive (false); targetMODisc.setActive (false); @@ -432,10 +452,37 @@ void Spot::deleteSelectedEntry() } } -// TODO -CursorShape Spot::getCursor (int objectID) const +CursorShape Spot::getCursor (int objectID, int xPos, int yPos) const { - return CSHandOpen; + const EditDataProvider* editProvider = getEditProvider(); + if (editProvider) { + if (draggedSide != DraggedSide::NONE) { + return CSEmpty; + } + + int object = editProvider->getObject(); + if (object == 0 || object == 1) { + return CSMove2D; + } + if (object >= 2 || object <= 5) { + Coord delta(Coord(xPos, yPos) - ((object == 3 || object == 5) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos)); + PolarCoord polarPos(delta); + if (polarPos.angle < 0.) { + polarPos.angle += 180.; + } + if (polarPos.angle < 22.5 || polarPos.angle >= 157.5) { + return CSMove1DH; + } + if (polarPos.angle < 67.5) { + return CSResizeBottomRight; + } + if (polarPos.angle < 112.5) { + return CSMove1DV; + } + return CSResizeBottomLeft; + } + } + return CSCrosshair; } bool Spot::mouseOver (int modifierKey) @@ -494,10 +541,12 @@ bool Spot::button1Pressed (int modifierKey) if (editProvider) { if (lastObject == -1 && (modifierKey & GDK_CONTROL_MASK)) { + draggedSide = DraggedSide::SOURCE; addNewEntry(); EditSubscriber::action = EditSubscriber::Action::DRAGGING; return true; } else if (lastObject > -1) { + draggedSide = lastObject == 0 ? DraggedSide::TARGET : lastObject == 1 ? DraggedSide::SOURCE : DraggedSide::NONE; getVisibleGeometryFromMO (lastObject)->state = Geometry::DRAGGED; EditSubscriber::action = EditSubscriber::Action::DRAGGING; return true; @@ -519,6 +568,7 @@ bool Spot::button1Released() loGeom->state = Geometry::PRELIGHT; EditSubscriber::action = EditSubscriber::Action::NONE; + draggedSide = DraggedSide::NONE; updateGeometry(); return true; } @@ -552,6 +602,7 @@ bool Spot::button3Pressed (int modifierKey) lastObject = 1; // sourceMODisc sourceIcon.state = Geometry::DRAGGED; EditSubscriber::action = EditSubscriber::Action::DRAGGING; + draggedSide = DraggedSide::SOURCE; return true; } else if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) { EditSubscriber::action = EditSubscriber::Action::PICKING; @@ -571,6 +622,7 @@ bool Spot::button3Released() lastObject = -1; sourceIcon.state = Geometry::ACTIVE; + draggedSide = DraggedSide::NONE; updateGeometry(); EditSubscriber::action = EditSubscriber::Action::NONE; return true; diff --git a/rtgui/spot.h b/rtgui/spot.h index 7604d7846..a29eb582c 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -57,6 +57,13 @@ class Spot : public ToolParamBlock, public FoldableToolPanel, public rtengine::T { private: + enum class DraggedSide { + NONE, + SOURCE, + TARGET + }; + + DraggedSide draggedSide; // tells which of source or target is being dragged int lastObject; // current object that is hovered int activeSpot; // currently active spot, being edited std::vector spots; // list of edited spots @@ -103,7 +110,7 @@ public: void setBatchMode (bool batchMode) override; // EditSubscriber interface - CursorShape getCursor (int objectID) const override; + CursorShape getCursor (int objectID, int xPos, int yPos) const override; bool mouseOver (int modifierKey) override; bool button1Pressed (int modifierKey) override; bool button1Released () override; From 4a9d913c50e027bc2b1ec088e428a77e82c7cff8 Mon Sep 17 00:00:00 2001 From: Hombre Date: Tue, 10 Sep 2019 22:55:20 +0200 Subject: [PATCH 17/34] Slight cleanup + removing dead code --- rtengine/alpha.cc | 6 +- rtengine/alpha.h | 6 +- rtengine/spot.cc | 287 +++------------------------------------------- rtgui/spot.h | 3 - 4 files changed, 20 insertions(+), 282 deletions(-) diff --git a/rtengine/alpha.cc b/rtengine/alpha.cc index 8d1fd6b0e..34132b879 100644 --- a/rtengine/alpha.cc +++ b/rtengine/alpha.cc @@ -54,7 +54,7 @@ void Alpha::setSize (int width, int height) } } -int Alpha::getWidth() +int Alpha::getWidth() const { if (surface) { return surface->get_width(); @@ -63,7 +63,7 @@ int Alpha::getWidth() return -1; } -int Alpha::getHeight() +int Alpha::getHeight() const { if (surface) { return surface->get_height(); @@ -73,7 +73,7 @@ int Alpha::getHeight() } -Cairo::RefPtr Alpha::getSurface () +Cairo::RefPtr Alpha::getSurface () const { return surface; // to be used in bitmap edition } diff --git a/rtengine/alpha.h b/rtengine/alpha.h index 90f32265e..1fe2a7a7c 100644 --- a/rtengine/alpha.h +++ b/rtengine/alpha.h @@ -39,10 +39,10 @@ public: //~Alpha (); void setSize (int width, int height); - int getWidth(); - int getHeight(); + int getWidth() const; + int getHeight() const; - Cairo::RefPtr getSurface (); + Cairo::RefPtr getSurface () const; // TODO: to make the editing faster, we should add an iterator class diff --git a/rtengine/spot.cc b/rtengine/spot.cc index e9d43ef31..9d059ee1c 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -44,6 +44,14 @@ public: Rectangle() : Rectangle(0, 0, 0, 0) {} Rectangle(int X1, int Y1, int X2, int Y2) : x1(X1), y1(Y1), x2(X2), y2(Y2) {} + int getWidth() { + return x2 - x1 + 1; + } + + int getHeight() { + return y2 - y1 + 1; + } + bool intersects(const Rectangle &other) const { return (other.x1 <= x2 && other.x2 >= x1) && (other.y1 <= y2 && other.y2 >= y1); @@ -175,27 +183,27 @@ public: } int getWidth() { - return spotArea.x2 - spotArea.x1 + 1; + return spotArea.getWidth(); } int getHeight() { - return spotArea.y2 - spotArea.y1 + 1; + return spotArea.getHeight(); } int getImageWidth() { - return imgArea.x2 - imgArea.x1 + 1; + return imgArea.getWidth(); } int getImageHeight() { - return imgArea.y2 - imgArea.y1 + 1; + return imgArea.getHeight(); } int getIntersectionWidth() { - return intersectionArea.x2 - intersectionArea.x1 + 1; + return intersectionArea.getWidth(); } int getIntersectionHeight() { - return intersectionArea.y2 - intersectionArea.y1 + 1; + return intersectionArea.getHeight(); } bool checkImageSize() { @@ -281,9 +289,6 @@ public: return true; } -#define ALGO 1 - -#if ALGO==1 bool processIntersectionWith(SpotBox &destBox) { Imagefloat *dstImg = destBox.image; @@ -327,270 +332,6 @@ public: return true; } -#endif - -#if ALGO==2 - bool processIntersectionWith(SpotBox &destBox) { - /* The following disabled code has been taken from Gimp 2.8.10 and converted - * for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014. - * It has not been tested, at all, an can (does) contain bugs. Feel free to - * replace the actual working code by this one, if results are better. - */ - - - /* 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 - */ - - // ----------------- Core function ----------------- - - int scaledPPX = pp.getX() / pp.skip; - int scaledPPY = pp.getY() / pp.skip; - int scaledPPW = pp.getWidth() / pp.skip + (pp.getWidth() % pp.getSkip() > 0); - int scaledPPH = pp.getHeight() / pp.skip + (pp.getHeight() % 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) { - - // ---------------------------------------------------------------- - - /* 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) { - 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) { - 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; - } - } - - // add solution to original image and store in tempPR - for (int i = 0, i2 = dst_YMin; i2 < dst_YMax - 1; ++i, ++i2) { - if (i2 < 0 || i2 >= img->getHeight()) { - continue; - } - - for (int j = 0, j2 = dst_XMin; j2 < dst_XMax - 1; ++j, ++j2) { - if (j2 < 0 || j2 >= img->getWidth()) { - 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) = img->r(i2,j2)*c1 + (solution.r(i,j) + srcBuff->r(i,j))*c2; - img->g(i2,j2) = img->g(i2,j2)*c1 + (solution.g(i,j) + srcBuff->g(i,j))*c2; - img->b(i2,j2) = img->b(i2,j2)*c1 + (solution.b(i,j) + srcBuff->b(i,j))*c2; - } - } - } -#endif // Copy the intersecting part bool copyImgTo(SpotBox &destBox) { diff --git a/rtgui/spot.h b/rtgui/spot.h index a29eb582c..bbe70469d 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -130,9 +130,6 @@ public: rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode rtengine::ProcEvent EvSpotEntry; rtengine::ProcEvent EvSpotEntryOPA; - - rtengine::ProcEvent evt; - }; #endif From 1935820884927f93402b6fcd417af2284b49e0f6 Mon Sep 17 00:00:00 2001 From: Hombre Date: Sat, 14 Sep 2019 23:03:19 +0200 Subject: [PATCH 18/34] Fix crash in Spot Removal tool when deleting a Spot entry --- rtgui/spot.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 5fb1de8c9..31f5a138e 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -460,12 +460,11 @@ CursorShape Spot::getCursor (int objectID, int xPos, int yPos) const return CSEmpty; } - int object = editProvider->getObject(); - if (object == 0 || object == 1) { + if (objectID == 0 || objectID == 1) { return CSMove2D; } - if (object >= 2 || object <= 5) { - Coord delta(Coord(xPos, yPos) - ((object == 3 || object == 5) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos)); + if (objectID >= 2 && objectID <= 5) { + Coord delta(Coord(xPos, yPos) - ((objectID == 3 || objectID == 5) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos)); PolarCoord polarPos(delta); if (polarPos.angle < 0.) { polarPos.angle += 180.; From 9bb19eb1a305835d59bf56cdad20cfa7d492128f Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Sun, 8 Dec 2019 02:01:03 +0100 Subject: [PATCH 19/34] Post-merge bugfix --- rtengine/improccoordinator.cc | 2 -- rtengine/improcfun.h | 4 +++- rtengine/spot.cc | 3 +++ rtgui/editwidgets.h | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 684d34e01..d8d9b2d82 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -576,8 +576,6 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) spotprev->copyData(oprevi); } - readyphase++; - if ((todo & M_HDR) && (params->fattal.enabled || params->dehaze.enabled)) { if (fattal_11_dcrop_cache) { delete fattal_11_dcrop_cache; diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h index 995288e7a..a9f0d4eeb 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -56,6 +56,8 @@ class Image8; class Imagefloat; class LabImage; class wavelet_decomposition; +class ImageSource; +class ColorTemp; namespace procparams { @@ -248,7 +250,7 @@ public: float MadRgb(const float * DataList, int datalen); // spot removal tool - void removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr); + void removeSpots (rtengine::Imagefloat* img, rtengine::ImageSource* imgsrc, const std::vector &entries, const PreviewProps &pp, const rtengine::ColorTemp &currWB, const procparams::ColorManagementParams *cmp, int tr); // pyramid wavelet void dirpyr_equalizer(const float * const * src, float ** dst, int srcwidth, int srcheight, const float * const * l_a, const float * const * l_b, const double * mult, double dirpyrThreshold, double skinprot, float b_l, float t_l, float t_r, int scale); //Emil's directional pyramid wavelet diff --git a/rtengine/spot.cc b/rtengine/spot.cc index 9d059ee1c..cd19cc62a 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -21,7 +21,10 @@ #include "alpha.h" #include "procparams.h" #include "imagesource.h" +#include "imagefloat.h" +#include "rt_math.h" #include +#include namespace rtengine { diff --git a/rtgui/editwidgets.h b/rtgui/editwidgets.h index 2dcd5274c..d814f0918 100644 --- a/rtgui/editwidgets.h +++ b/rtgui/editwidgets.h @@ -24,11 +24,11 @@ #include #include "editcoordsys.h" +#include "rtsurface.h" #include "../rtengine/coord.h" #include "../rtengine/rt_math.h" class ObjectMOBuffer; -class RTSurface; /** @file * From 8b60ea4794eaa93860c337fc4ad4bff1c03e375d Mon Sep 17 00:00:00 2001 From: Hombre57 Date: Tue, 17 Dec 2019 21:46:18 +0100 Subject: [PATCH 20/34] Bugfix in SpotRemoval tool GUI (Batch Editor) + Partial Paste handling --- rtdata/languages/Francais | 1 + rtdata/languages/default | 1 + rtgui/partialpastedlg.cc | 9 +++++++++ rtgui/partialpastedlg.h | 3 ++- rtgui/spot.cc | 19 +++++++++++++------ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index 363feacb9..144837411 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -1022,6 +1022,7 @@ PARTIALPASTE_SHARPENEDGE;Bords PARTIALPASTE_SHARPENING;Netteté PARTIALPASTE_SHARPENMICRO;Microcontraste PARTIALPASTE_SOFTLIGHT;Lumière douce +PARTIALPASTE_SPOT;Retrait de taches PARTIALPASTE_TM_FATTAL;Compression de plage dynamique PARTIALPASTE_VIBRANCE;Vibrance PARTIALPASTE_VIGNETTING;Correction du vignettage diff --git a/rtdata/languages/default b/rtdata/languages/default index e0e65ea0c..8fdfa205c 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1044,6 +1044,7 @@ PARTIALPASTE_SHARPENEDGE;Edges PARTIALPASTE_SHARPENING;Sharpening (USM/RL) PARTIALPASTE_SHARPENMICRO;Microcontrast PARTIALPASTE_SOFTLIGHT;Soft light +PARTIALPASTE_SPOT;Spot removal PARTIALPASTE_TM_FATTAL;Dynamic range compression PARTIALPASTE_VIBRANCE;Vibrance PARTIALPASTE_VIGNETTING;Vignetting correction diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index 22f608ae4..569f39435 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -57,6 +57,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren labcurve = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_LABCURVE"))); // Detail Settings: + spot = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_SPOT"))); sharpen = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_SHARPENING"))); localcontrast = Gtk::manage(new Gtk::CheckButton(M("PARTIALPASTE_LOCALCONTRAST"))); sharpenedge = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_SHARPENEDGE"))); @@ -164,6 +165,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren //DETAIL vboxes[1]->pack_start (*detail, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*hseps[1], Gtk::PACK_SHRINK, 2); + vboxes[1]->pack_start (*spot, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*sharpen, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*localcontrast, Gtk::PACK_SHRINK, 2); vboxes[1]->pack_start (*sharpenedge, Gtk::PACK_SHRINK, 2); @@ -326,6 +328,7 @@ PartialPasteDlg::PartialPasteDlg (const Glib::ustring &title, Gtk::Window* paren labcurveConn = labcurve->signal_toggled().connect (sigc::bind (sigc::mem_fun(*basic, &Gtk::CheckButton::set_inconsistent), true)); // Detail Settings: + spotConn = spot->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); sharpenConn = sharpen->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); localcontrastConn = localcontrast->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); gradsharpenConn = sharpenedge->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true)); @@ -539,6 +542,7 @@ void PartialPasteDlg::basicToggled () void PartialPasteDlg::detailToggled () { + ConnectionBlocker spotBlocker(spotConn); ConnectionBlocker sharpenBlocker(sharpenConn); ConnectionBlocker localcontrastBlocker(localcontrastConn); ConnectionBlocker gradsharpenBlocker(gradsharpenConn); @@ -551,6 +555,7 @@ void PartialPasteDlg::detailToggled () detail->set_inconsistent (false); + spot->set_active (detail->get_active ()); sharpen->set_active (detail->get_active ()); localcontrast->set_active(detail->get_active()); sharpenedge->set_active (detail->get_active ()); @@ -717,6 +722,10 @@ void PartialPasteDlg::applyPaste (rtengine::procparams::ProcParams* dstPP, Param filterPE.colorappearance = falsePE.colorappearance; } + if (!spot->get_active ()) { + filterPE.spot = falsePE.spot; + } + if (!sharpen->get_active ()) { filterPE.sharpening = falsePE.sharpening; } diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h index 1403e7c1b..8089c0677 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -67,6 +67,7 @@ public: Gtk::CheckButton* colorappearance; // options in detail: + Gtk::CheckButton* spot; Gtk::CheckButton* sharpen; Gtk::CheckButton* sharpenedge; Gtk::CheckButton* sharpenmicro; @@ -146,7 +147,7 @@ public: sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaConn, rawConn, advancedConn; sigc::connection wbConn, exposureConn, localcontrastConn, shConn, pcvignetteConn, gradientConn, labcurveConn, colorappearanceConn; - sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn, dehazeConn; + sigc::connection spotConn, sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, defringeConn, epdConn, fattalConn, dirpyreqConn, waveletConn, retinexConn, dehazeConn; sigc::connection vibranceConn, chmixerConn, hsveqConn, rgbcurvesConn, chmixerbwConn, colortoningConn, filmSimulationConn, softlightConn; sigc::connection distortionConn, cacorrConn, vignettingConn, lcpConn; sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, prsharpeningConn, perspectiveConn, commonTransConn; diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 31f5a138e..a90c495d0 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -131,11 +131,16 @@ void Spot::read (const ProcParams* pp, const ParamsEdited* pedited) activeSpot = -1; lastObject = -1; - if (spots.size() != oldSize) { - createGeometry(); + if (batchMode) { + editedCheckBox->set_label(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size())); } + else { + if (spots.size() != oldSize) { + createGeometry(); + } - updateGeometry(); + updateGeometry(); + } enableListener (); } @@ -147,7 +152,7 @@ void Spot::write (ProcParams* pp, ParamsEdited* pedited) if (pedited) { pedited->spot.enabled = !get_inconsistent(); - pedited->spot.entries = !editedCheckBox->get_active(); + pedited->spot.entries = editedCheckBox->get_active(); } } @@ -160,8 +165,10 @@ void Spot::resetPressed() editedCheckBox->set_active (true); editedConn.block (false); + editedCheckBox->set_label(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size())); + if (listener) { - listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size())); + listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)); } } else { if (!spots.empty()) { @@ -783,7 +790,7 @@ void Spot::tweakParams(procparams::ProcParams& pparams) //params->raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST); // -> disabling all transform - //params->coarse = CoarseTransformParams(); + //pparams.coarse = CoarseTransformParams(); pparams.lensProf = LensProfParams(); pparams.cacorrection = CACorrParams(); pparams.distortion = DistortionParams(); From 10b5f65b30b5996b96508b219e466ca0af7fa9cc Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sat, 6 Mar 2021 21:29:56 -0800 Subject: [PATCH 21/34] Fix spot removal enable/disable history message The history panel was showing "HISTORY_MSG_1049". --- rtgui/spot.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/spot.cc b/rtgui/spot.cc index a90c495d0..1f3759c6b 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -95,7 +95,7 @@ Spot::Spot() : auto m = ProcEventMapper::getInstance(); EvSpotEnabled = m->newEvent(ALLNORAW, "TP_SPOT_LABEL"); - EvSpotEnabledOPA = m->newEvent(SPOTADJUST, ""); + EvSpotEnabledOPA = m->newEvent(SPOTADJUST, "TP_SPOT_LABEL"); EvSpotEntry = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); EvSpotEntryOPA = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); From f958074d9bd5ece66aeb79a79424c978995940e0 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sun, 7 Mar 2021 11:58:36 -0800 Subject: [PATCH 22/34] Fix crash when deleting spot removal spot Deleting a spot while hovering over one of the circles for adjusting the radius or feathering could trigger a crash. This is because immediately after deleting a geometry object, EditSubscriber::getCursor is given an object ID based on old data, causing the spot tool to attempt to get information about the deleted geometry object. This commit fixes the issue by returning the default cursor if the active spot is -1. It works because the active spot is set to -1 when deleting a spot, and there is no need to use a special cursor when there is no active spot. A better long-term solution would be updating the geometry data before calling EditSubscriber::getCursor. --- rtgui/spot.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 1f3759c6b..6d749838b 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -462,7 +462,7 @@ void Spot::deleteSelectedEntry() CursorShape Spot::getCursor (int objectID, int xPos, int yPos) const { const EditDataProvider* editProvider = getEditProvider(); - if (editProvider) { + if (editProvider && activeSpot > -1) { if (draggedSide != DraggedSide::NONE) { return CSEmpty; } From aa7d635f29ed9e50b7c24b1ed8f4abb4f849a98d Mon Sep 17 00:00:00 2001 From: Thanatomanic <6567747+Thanatomanic@users.noreply.github.com> Date: Thu, 13 May 2021 12:32:56 +0200 Subject: [PATCH 23/34] Fix https in license text refreshmap.h --- rtengine/refreshmap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtengine/refreshmap.h b/rtengine/refreshmap.h index 39c80feef..05a8f7f44 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -14,7 +14,7 @@ * 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 . + * along with RawTherapee. If not, see . */ #pragma once From bb623d3a02ddd20ebbace4ab8a8c18c3adf75f77 Mon Sep 17 00:00:00 2001 From: Thanatomanic <6567747+Thanatomanic@users.noreply.github.com> Date: Thu, 13 May 2021 12:34:27 +0200 Subject: [PATCH 24/34] Fix instance of deprecated HBox --- rtgui/spot.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/spot.cc b/rtgui/spot.cc index 6d749838b..10ff99767 100644 --- a/rtgui/spot.cc +++ b/rtgui/spot.cc @@ -54,7 +54,7 @@ Spot::Spot() : reset->set_border_width (0); reset->signal_clicked().connect ( sigc::mem_fun (*this, &Spot::resetPressed) ); - labelBox = Gtk::manage (new Gtk::HBox()); + labelBox = Gtk::manage (new Gtk::Box()); labelBox->set_spacing (2); labelBox->pack_start (*countLabel, false, false, 0); labelBox->pack_end (*edit, false, false, 0); From fa2776c7327eb8c69f8f386339d38a6aa379c068 Mon Sep 17 00:00:00 2001 From: Thanatomanic <6567747+Thanatomanic@users.noreply.github.com> Date: Thu, 13 May 2021 12:41:01 +0200 Subject: [PATCH 25/34] Remove double include --- rtgui/labgrid.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/rtgui/labgrid.cc b/rtgui/labgrid.cc index b4c4a5c9d..d8a22b1d9 100644 --- a/rtgui/labgrid.cc +++ b/rtgui/labgrid.cc @@ -37,7 +37,6 @@ */ #include "labgrid.h" -#include "options.h" #include "../rtengine/color.h" #include "options.h" From 09621242f440420e67388c3860ef087405647d7c Mon Sep 17 00:00:00 2001 From: Thanatomanic <6567747+Thanatomanic@users.noreply.github.com> Date: Thu, 13 May 2021 13:01:41 +0200 Subject: [PATCH 26/34] Fix forgotten HBox -> Box change in header --- rtgui/spot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtgui/spot.h b/rtgui/spot.h index bbe70469d..db1fdac05 100644 --- a/rtgui/spot.h +++ b/rtgui/spot.h @@ -84,7 +84,7 @@ private: void resetPressed (); protected: - Gtk::HBox* labelBox; + Gtk::Box* labelBox; Gtk::CheckButton* editedCheckBox; Gtk::Label* countLabel; Gtk::ToggleButton* edit; From f098e70d4a80bf2773b2a92aecbf0d228bfe848b Mon Sep 17 00:00:00 2001 From: Thanatomanic <6567747+Thanatomanic@users.noreply.github.com> Date: Thu, 13 May 2021 13:03:07 +0200 Subject: [PATCH 27/34] Capitalize tool name for consistency in GUI --- rtdata/languages/default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index 0f8761b50..bfe524b45 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -3510,7 +3510,7 @@ TP_SOFTLIGHT_STRENGTH;Strength TP_SPOT_COUNTLABEL;%1 point(s) TP_SPOT_ENTRYCHANGED;Point changed TP_SPOT_HINT;Click on this button to be able to operate on the preview area.\n\nTo edit a spot, hover the white mark locating an edited aera, making the editing geometry appear.\n\nTo add a spot, press Ctrl and left mouse button, drag the circle (Ctrl key can be released) to a source location, then release the mouse button.\n\nTo move the source or destination spot, hover its center then drag it.\n\nThe inner circle (maximum effect area) and the "feather" circle can be resized by hovering them (the circle becomes orange) and draging it (the circle becomes red).\n\nWhen the changes are done, right click outside any spot to end the Spot editing mode, or click on this button again. -TP_SPOT_LABEL;Spot removal +TP_SPOT_LABEL;Spot Removal TP_TM_FATTAL_AMOUNT;Amount TP_TM_FATTAL_ANCHOR;Anchor TP_TM_FATTAL_LABEL;Dynamic Range Compression From ad1770f431016ff269d4a902e74d2f62ca8f864e Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Thu, 13 May 2021 21:35:21 -0700 Subject: [PATCH 28/34] Fix history message --- rtdata/languages/Francais | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index 172da2508..4145002a3 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -663,7 +663,7 @@ HISTORY_MSG_421;Retinex - Gamma HISTORY_MSG_422;Retinex - Gamma HISTORY_MSG_423;Retinex - Pente Gamma HISTORY_MSG_424;Retinex - Seuille HL -HISTORY_MSG_425;Retrait de taches +HISTORY_MSG_425;Retinex - Base Log HISTORY_MSG_426;Retinex - Égaliseur de teinte HISTORY_MSG_427;Intention de rendu de sortie HISTORY_MSG_428;Intention de rendu du moniteur From c41eb247607e3165e93c18f597f7b4c55a02e352 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Fri, 14 May 2021 22:07:01 -0700 Subject: [PATCH 29/34] Remove dead code --- rtengine/spot.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/rtengine/spot.cc b/rtengine/spot.cc index cd19cc62a..6ef0ba5e0 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -400,7 +400,6 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s || !srcSpotBox->imageIntersects(*dstSpotBox, true)) { continue; - ++i; } // If spot intersect the preview image, add it to the visible spots From 1b7ddf2b31eb35660b9aac853db22bf4add1d1a2 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Fri, 14 May 2021 22:20:25 -0700 Subject: [PATCH 30/34] Fix perspective and local adjustments cursor icons Correctly override the getCursor virtual function. --- rtgui/controllines.cc | 2 +- rtgui/controllines.h | 2 +- rtgui/controlspotpanel.cc | 2 +- rtgui/controlspotpanel.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rtgui/controllines.cc b/rtgui/controllines.cc index 573b3263f..c078b4322 100644 --- a/rtgui/controllines.cc +++ b/rtgui/controllines.cc @@ -268,7 +268,7 @@ bool ControlLineManager::getEdited(void) const return edited; } -CursorShape ControlLineManager::getCursor(int objectID) const +CursorShape ControlLineManager::getCursor(int objectID, int xPos, int yPos) const { return cursor; } diff --git a/rtgui/controllines.h b/rtgui/controllines.h index c623ee1db..2b2d179a4 100644 --- a/rtgui/controllines.h +++ b/rtgui/controllines.h @@ -109,7 +109,7 @@ public: bool pick1(bool picked) override; bool pick3(bool picked) override; bool drag1(int modifierKey) override; - CursorShape getCursor(int objectID) const; + CursorShape getCursor(int objectID, int xPos, int yPos) const override; bool mouseOver(int modifierKey) override; void switchOffEditMode(void) override; }; diff --git a/rtgui/controlspotpanel.cc b/rtgui/controlspotpanel.cc index 5450ba3ed..6289ed1ae 100644 --- a/rtgui/controlspotpanel.cc +++ b/rtgui/controlspotpanel.cc @@ -2196,7 +2196,7 @@ void ControlSpotPanel::updateCurveOpacity(const Gtk::TreeModel::Row& selectedRow } } -CursorShape ControlSpotPanel::getCursor(int objectID) const +CursorShape ControlSpotPanel::getCursor(int objectID, int xPos, int yPos) const { // printf("Object ID: %d\n", objectID); diff --git a/rtgui/controlspotpanel.h b/rtgui/controlspotpanel.h index bebc24bee..b748df544 100644 --- a/rtgui/controlspotpanel.h +++ b/rtgui/controlspotpanel.h @@ -267,7 +267,7 @@ private: void updateControlSpotCurve(const Gtk::TreeModel::Row& row); void deleteControlSpotCurve(Gtk::TreeModel::Row& row); void updateCurveOpacity(const Gtk::TreeModel::Row& selectedRow); - CursorShape getCursor(int objectID) const; + CursorShape getCursor(int objectID, int xPos, int yPos) const override; bool mouseOver(int modifierKey) override; bool button1Pressed(int modifierKey) override; bool button1Released() override; From 22e89d83947246a7d48a20a010533691d3f5b4ce Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 15 May 2021 14:24:16 -0700 Subject: [PATCH 31/34] Fix spot removal spot dependency calculation Recursively find dependencies and output them in order. --- rtengine/spot.cc | 92 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 12 deletions(-) diff --git a/rtengine/spot.cc b/rtengine/spot.cc index 6ef0ba5e0..f43eaa909 100644 --- a/rtengine/spot.cc +++ b/rtengine/spot.cc @@ -25,6 +25,40 @@ #include "rt_math.h" #include #include +#include + +namespace rtengine +{ + +class SpotBox; + +} + +namespace +{ + +using Boxes = std::vector>; + +/** + * Add the spot and its dependencies to a set of dependencies. + * + * @param spotNum The spot's index. + * @param dependencies A set to place the dependencies in. Spots that are + * already in the set must have all their dependencies included already. + * @param srcSpots Information on spot sources. + * @param dstSpots Information on spot destinations. + */ +void addSpotDependencies(int spotNum, std::unordered_set &dependencies, const Boxes &srcSpots, const Boxes &dstSpots); + +/** + * Returns the supplied spots and all their dependencies. + * + * @param visibleSpots The spots to get dependencies for. + * @param srcSpots Information on spot sources. + * @param dstSpots Information on spot destinations. + */ +std::unordered_set calcSpotDependencies(const std::set &visibleSpots, const Boxes &srcSpots, const Boxes &dstSpots); +} namespace rtengine { @@ -461,18 +495,10 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s // Construct list of upstream dependancies - std::set requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots - for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) { - int spotNbr = *i; - requiredSpots.insert(spotNbr); - if (spotNbr > 0) { - for (int j = spotNbr - 1; j >= 0; --j) { - if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) { - requiredSpots.insert(spotNbr); - } - } - } - } + std::unordered_set requiredSpotsSet = calcSpotDependencies(visibleSpots, srcSpotBoxs, dstSpotBoxs); + std::vector requiredSpots(requiredSpotsSet.size()); + std::copy(requiredSpotsSet.begin(), requiredSpotsSet.end(), requiredSpots.begin()); + std::sort(requiredSpots.begin(), requiredSpots.end()); // Process spots and copy them downstream @@ -510,3 +536,45 @@ void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const s } +namespace +{ + +void addSpotDependencies(int spotNum, std::unordered_set &dependencies, const Boxes &srcSpots, const Boxes &dstSpots) +{ + dependencies.insert(spotNum); + + // Our spot can depend on previous spots. + for (int i = spotNum - 1; i >= 0; --i) { + if (dependencies.find(i) != dependencies.end()) { + continue; // Spot already has its dependencies added. + } + + // Check if our spot depends on this previous spot. + if (srcSpots.at(spotNum)->imageIntersects(*dstSpots.at(i))) { + // If so, add it and its dependencies. + addSpotDependencies(i, dependencies, srcSpots, dstSpots); + } + } +} + +std::unordered_set calcSpotDependencies(const std::set &visibleSpots, const Boxes &srcSpots, const Boxes &dstSpots) +{ + std::unordered_set dependencies; + std::vector visibleSpotsOrdered(visibleSpots.size()); + + std::copy(visibleSpots.begin(), visibleSpots.end(), visibleSpotsOrdered.begin()); + std::sort(visibleSpotsOrdered.begin(), visibleSpotsOrdered.end()); + + // Add dependencies, starting with the last spot. + for (auto i = visibleSpotsOrdered.crbegin(); i != visibleSpotsOrdered.crend(); ++i) { + if (dependencies.find(*i) != dependencies.end()) { + continue; // Spot already has its dependencies added. + } + addSpotDependencies(*i, dependencies, srcSpots, dstSpots); + } + + return dependencies; +} + +} + From 64248ab5cdb01631bf76be343a712dae154942e5 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 15 May 2021 22:54:15 -0700 Subject: [PATCH 32/34] Refresh thumbnail when en/disabling spot removal --- rtengine/improccoordinator.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index fa338a7dd..e7af18fa0 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -2458,6 +2458,7 @@ void ImProcCoordinator::process() || params->dehaze != nextParams->dehaze || params->pdsharpening != nextParams->pdsharpening || params->filmNegative != nextParams->filmNegative + || params->spot.enabled != nextParams->spot.enabled || sharpMaskChanged; sharpMaskChanged = false; From 907da2db96d2d1b403ecd10064aa8b90d761c4c7 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 15 May 2021 22:55:49 -0700 Subject: [PATCH 33/34] Fix spelling in spot removal tooltip --- rtdata/languages/default | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtdata/languages/default b/rtdata/languages/default index bfe524b45..dd77e2946 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -3509,7 +3509,7 @@ TP_SOFTLIGHT_LABEL;Soft Light TP_SOFTLIGHT_STRENGTH;Strength TP_SPOT_COUNTLABEL;%1 point(s) TP_SPOT_ENTRYCHANGED;Point changed -TP_SPOT_HINT;Click on this button to be able to operate on the preview area.\n\nTo edit a spot, hover the white mark locating an edited aera, making the editing geometry appear.\n\nTo add a spot, press Ctrl and left mouse button, drag the circle (Ctrl key can be released) to a source location, then release the mouse button.\n\nTo move the source or destination spot, hover its center then drag it.\n\nThe inner circle (maximum effect area) and the "feather" circle can be resized by hovering them (the circle becomes orange) and draging it (the circle becomes red).\n\nWhen the changes are done, right click outside any spot to end the Spot editing mode, or click on this button again. +TP_SPOT_HINT;Click on this button to be able to operate on the preview area.\n\nTo edit a spot, hover the white mark locating an edited area, making the editing geometry appear.\n\nTo add a spot, press Ctrl and left mouse button, drag the circle (Ctrl key can be released) to a source location, then release the mouse button.\n\nTo move the source or destination spot, hover its center then drag it.\n\nThe inner circle (maximum effect area) and the "feather" circle can be resized by hovering them (the circle becomes orange) and dragging it (the circle becomes red).\n\nWhen the changes are done, right click outside any spot to end the Spot editing mode, or click on this button again. TP_SPOT_LABEL;Spot Removal TP_TM_FATTAL_AMOUNT;Amount TP_TM_FATTAL_ANCHOR;Anchor From 38075be8cc5682e73bb59ddd6f784c125a401f99 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sat, 15 May 2021 23:49:25 -0700 Subject: [PATCH 34/34] Fix thumbnail spot removal with dehaze & film neg. --- rtengine/dcrop.cc | 2 +- rtengine/improccoordinator.cc | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index d33a2e8d3..ed7ec2495 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -750,7 +750,7 @@ void Crop::update(int todo) parent->imgsrc->getImage(parent->currWB, tr, f, pp, params.toneCurve, params.raw); parent->imgsrc->convertColorSpace(f, params.icm, parent->currWB); - if (params.dirpyrDenoise.enabled || params.filmNegative.enabled) { + if (params.dirpyrDenoise.enabled || params.filmNegative.enabled || params.spot.enabled) { // copy the denoised crop int oy = trafy / skip; int ox = trafx / skip; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index 4490f4ba0..3b0a17bcb 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -346,6 +346,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) RAWParams rp = params->raw; ColorManagementParams cmp = params->icm; LCurveParams lcur = params->labCurve; + bool spotsDone = false; if (!highDetailNeeded) { // if below 100% magnification, take a fast path @@ -609,6 +610,13 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) ipf.setScale(scale); imgsrc->getImage(currWB, tr, orig_prev, pp, params->toneCurve, params->raw); + + if ((todo & M_SPOT) && params->spot.enabled && !params->spot.entries.empty()) { + spotsDone = true; + PreviewProps pp(0, 0, fw, fh, scale); + ipf.removeSpots(orig_prev, imgsrc, params->spot.entries, pp, currWB, nullptr, tr); + } + denoiseInfoStore.valid = false; //ColorTemp::CAT02 (orig_prev, ¶ms) ; // printf("orig_prevW=%d\n scale=%d",orig_prev->width, scale); @@ -680,7 +688,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) oprevi = orig_prev; - if (todo & M_SPOT) { + if ((todo & M_SPOT) && !spotsDone) { if (params->spot.enabled && !params->spot.entries.empty()) { allocCache(spotprev); orig_prev->copyData(spotprev); @@ -694,10 +702,7 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } } if (spotprev) { - if (oprevi == orig_prev) { - oprevi = new Imagefloat(pW, pH); - } - spotprev->copyData(oprevi); + spotprev->copyData(orig_prev); } if ((todo & M_HDR) && (params->fattal.enabled || params->dehaze.enabled)) {