diff --git a/rtdata/images/svg/spot-active.svg b/rtdata/images/svg/spot-active.svg new file mode 100644 index 000000000..bc36a7a19 --- /dev/null +++ b/rtdata/images/svg/spot-active.svg @@ -0,0 +1,81 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/rtdata/images/svg/spot-normal.svg b/rtdata/images/svg/spot-normal.svg new file mode 100644 index 000000000..d0320e31f --- /dev/null +++ b/rtdata/images/svg/spot-normal.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/rtdata/images/svg/spot-prelight.svg b/rtdata/images/svg/spot-prelight.svg new file mode 100644 index 000000000..9cfc1acfa --- /dev/null +++ b/rtdata/images/svg/spot-prelight.svg @@ -0,0 +1,74 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/rtdata/languages/Francais b/rtdata/languages/Francais index dce3aa6b7..8377b6cbc 100644 --- a/rtdata/languages/Francais +++ b/rtdata/languages/Francais @@ -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 @@ -1024,6 +1026,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 @@ -2745,6 +2748,10 @@ TP_SHARPENMICRO_MATRIX;Matrice 3×3 au lieu de 5×5 TP_SHARPENMICRO_UNIFORMITY;Uniformité 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 TP_TM_FATTAL_LABEL;Compression de Plage Dynamique diff --git a/rtdata/languages/default b/rtdata/languages/default index 54742eea6..bdc470762 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -1396,6 +1396,8 @@ HISTORY_MSG_SIGMAFIN;Final contrast Attenuation response HISTORY_MSG_SIGMATON;Toning Attenuation response 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_TEMPOUT;CAM02 automatic temperature HISTORY_MSG_THRESWAV;Balance threshold HISTORY_MSG_TM_FATTAL_ANCHOR;DRC - Anchor @@ -1698,6 +1700,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 @@ -3598,6 +3601,10 @@ TP_SHARPENMICRO_MATRIX;3×3 matrix instead of 5×5 TP_SHARPENMICRO_UNIFORMITY;Uniformity 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 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 TP_TM_FATTAL_LABEL;Dynamic Range Compression diff --git a/rtengine/CMakeLists.txt b/rtengine/CMakeLists.txt index d87bcca0f..1f3cf352b 100644 --- a/rtengine/CMakeLists.txt +++ b/rtengine/CMakeLists.txt @@ -64,6 +64,7 @@ endif() set(CAMCONSTSFILE "camconst.json") set(RTENGINESOURCEFILES + alpha.cc ahd_demosaic_RT.cc amaze_demosaic_RT.cc badpixels.cc @@ -163,6 +164,7 @@ set(RTENGINESOURCEFILES rtthumbnail.cc shmap.cc simpleprocess.cc + spot.cc stdimagesource.cc tmo_fattal02.cc utils.cc diff --git a/rtengine/alpha.cc b/rtengine/alpha.cc new file mode 100644 index 000000000..34132b879 --- /dev/null +++ b/rtengine/alpha.cc @@ -0,0 +1,96 @@ +/* + * 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() const +{ + if (surface) { + return surface->get_width(); + } + + return -1; +} + +int Alpha::getHeight() const +{ + if (surface) { + return surface->get_height(); + } + + return -1; +} + + +Cairo::RefPtr Alpha::getSurface () const +{ + 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 new file mode 100644 index 000000000..1fe2a7a7c --- /dev/null +++ b/rtengine/alpha.h @@ -0,0 +1,58 @@ +/* + * 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() const; + int getHeight() const; + + Cairo::RefPtr getSurface () const; + + // 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); + unsigned char operator () (unsigned row, unsigned col) const; +}; + +} + +#endif diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc index 2641f8885..465fc8dd7 100644 --- a/rtengine/dcrop.cc +++ b/rtengine/dcrop.cc @@ -54,7 +54,7 @@ namespace rtengine { Crop::Crop(ImProcCoordinator* parent, EditDataProvider *editDataProvider, bool isDetailWindow) - : PipetteBuffer(editDataProvider), origCrop(nullptr), laboCrop(nullptr), labnCrop(nullptr), + : PipetteBuffer(editDataProvider), origCrop(nullptr), spotCrop(nullptr), laboCrop(nullptr), labnCrop(nullptr), cropImg(nullptr), shbuf_real(nullptr), transCrop(nullptr), cieCrop(nullptr), shbuffer(nullptr), updating(false), newUpdatePending(false), skip(10), cropx(0), cropy(0), cropw(-1), croph(-1), @@ -153,6 +153,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); @@ -615,6 +616,13 @@ 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()) { + 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, nullptr, tr); + } + DirPyrDenoiseParams denoiseParams = params.dirpyrDenoise; if (params.dirpyrDenoise.Lmethod == "CUR") { @@ -697,6 +705,28 @@ 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); + // Apply Spot removal + if ((todo & M_SPOT) && !spotsDone) { + 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); + int tr = getCoarseBitMask(params.coarse); + parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, ¶ms.icm, tr); + } 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)) { @@ -722,7 +752,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/dcrop.h b/rtengine/dcrop.h index 1840eabfa..361f0462d 100644 --- a/rtengine/dcrop.h +++ b/rtengine/dcrop.h @@ -38,6 +38,7 @@ class Crop final : 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 ; displayed image in monitor color space, showing the output profile as well (soft-proofing enabled, which then correspond to workimg) or not diff --git a/rtengine/iimage.h b/rtengine/iimage.h index 984b98a6f..2c75a0d59 100644 --- a/rtengine/iimage.h +++ b/rtengine/iimage.h @@ -347,6 +347,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) override { @@ -756,6 +773,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) final { @@ -1392,6 +1428,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) final { diff --git a/rtengine/image16.cc b/rtengine/image16.cc index 1a9130b5d..ae981c7bd 100644 --- a/rtengine/image16.cc +++ b/rtengine/image16.cc @@ -134,7 +134,21 @@ Image16* Image16::copy() const return cp; } -void Image16::getStdImage(const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const +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(const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const { // compute channel multipliers diff --git a/rtengine/image16.h b/rtengine/image16.h index de9718708..25b777832 100644 --- a/rtengine/image16.h +++ b/rtengine/image16.h @@ -39,8 +39,9 @@ public: ~Image16() override; Image16* copy() const; + Image16* copySubRegion (int x, int y, int width, int height); - void getStdImage(const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const override; + void getStdImage(const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const override; const char* getType() const override { diff --git a/rtengine/image8.cc b/rtengine/image8.cc index 67b38d0c1..1b4e49d84 100644 --- a/rtengine/image8.cc +++ b/rtengine/image8.cc @@ -100,7 +100,7 @@ Image8* Image8::copy () const return cp; } -void Image8::getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const +void Image8::getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const { // compute channel multipliers float rm = 1.f, gm = 1.f, bm = 1.f; diff --git a/rtengine/image8.h b/rtengine/image8.h index dd3be5d9b..76a580bb6 100644 --- a/rtengine/image8.h +++ b/rtengine/image8.h @@ -38,7 +38,7 @@ public: Image8* copy () const; - void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const override; + void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const override; const char* getType () const override { diff --git a/rtengine/imagedimensions.cc b/rtengine/imagedimensions.cc index 5b60e5da7..5e780558c 100644 --- a/rtengine/imagedimensions.cc +++ b/rtengine/imagedimensions.cc @@ -54,6 +54,14 @@ int PreviewProps::getSkip() const return skip; } +void PreviewProps::set (int x, int y, int w, int h, int skip) { + this->x = x; + this->y = y; + this->width = w; + this->height = h; + this->skip = skip; +} + ImageDimensions::ImageDimensions() : width(-1), height(-1) diff --git a/rtengine/imagedimensions.h b/rtengine/imagedimensions.h index eb92798c3..fce7a4dea 100644 --- a/rtengine/imagedimensions.h +++ b/rtengine/imagedimensions.h @@ -29,6 +29,7 @@ public: int getWidth() const; int getHeight() const; int getSkip() const; + void set (int x, int y, int w, int h, int skip); private: int x; diff --git a/rtengine/imagefloat.cc b/rtengine/imagefloat.cc index d9ac841eb..7bc75fe6d 100644 --- a/rtengine/imagefloat.cc +++ b/rtengine/imagefloat.cc @@ -165,8 +165,22 @@ Imagefloat* Imagefloat::copy () const 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 (const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const +void Imagefloat::getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const { // compute channel multipliers diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h index 3362afcda..fc3ba318d 100644 --- a/rtengine/imagefloat.h +++ b/rtengine/imagefloat.h @@ -44,8 +44,9 @@ public: ~Imagefloat () override; Imagefloat* copy () const; + Imagefloat* copySubRegion (int x, int y, int width, int height); - void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const override; + void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const override; const char* getType () const override { diff --git a/rtengine/imageio.h b/rtengine/imageio.h index 866855bfe..566fef13b 100644 --- a/rtengine/imageio.h +++ b/rtengine/imageio.h @@ -93,7 +93,7 @@ public: void setSampleArrangement(IIOSampleArrangement sArrangement); IIOSampleArrangement getSampleArrangement() const; - virtual void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, PreviewProps pp) const = 0; + virtual void getStdImage (const ColorTemp &ctemp, int tran, Imagefloat* image, const PreviewProps &pp) const = 0; virtual int getBPS () const = 0; virtual void getScanline (int row, unsigned char* buffer, int bps, bool isFloat = false) const = 0; virtual void setScanline (int row, const unsigned char* buffer, int bps, unsigned int numSamples = 3) = 0; diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc index b3913103e..938048169 100644 --- a/rtengine/improccoordinator.cc +++ b/rtengine/improccoordinator.cc @@ -36,6 +36,7 @@ #include "labimage.h" #include "lcp.h" #include "procparams.h" +#include "tweakoperator.h" #include "refreshmap.h" #include "utils.h" @@ -58,6 +59,7 @@ namespace rtengine ImProcCoordinator::ImProcCoordinator() : orig_prev(nullptr), oprevi(nullptr), + spotprev(nullptr), oprevl(nullptr), nprevl(nullptr), fattal_11_dcrop_cache(nullptr), @@ -167,6 +169,7 @@ ImProcCoordinator::ImProcCoordinator() : hListener(nullptr), resultValid(false), params(new procparams::ProcParams), + tweakOperator(nullptr), lastOutputProfile("BADFOOD"), lastOutputIntent(RI__COUNT), lastOutputBPC(false), @@ -280,9 +283,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) @@ -322,6 +348,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 @@ -585,6 +612,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); @@ -654,6 +688,25 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) ipf.firstAnalysis(orig_prev, *params, vhist16); } + oprevi = orig_prev; + + if ((todo & M_SPOT) && !spotsDone) { + if (params->spot.enabled && !params->spot.entries.empty()) { + allocCache(spotprev); + orig_prev->copyData(spotprev); + PreviewProps pp(0, 0, fw, fh, scale); + ipf.removeSpots(spotprev, imgsrc, params->spot.entries, pp, currWB, ¶ms->icm, tr); + } else { + if (spotprev) { + delete spotprev; + spotprev = nullptr; + } + } + } + if (spotprev) { + spotprev->copyData(orig_prev); + } + if ((todo & M_HDR) && (params->fattal.enabled || params->dehaze.enabled)) { if (fattal_11_dcrop_cache) { delete fattal_11_dcrop_cache; @@ -668,12 +721,11 @@ void ImProcCoordinator::updatePreviewImage(int todo, bool panningRelatedChange) } } - oprevi = orig_prev; - // Remove transformation if unneeded bool needstransform = ipf.needsTransform(fw, fh, imgsrc->getRotateDegree(), imgsrc->getMetaData()); if ((needstransform || ((todo & (M_TRANSFORM | M_RGBCURVE)) && params->dirpyrequalizer.cbdlMethod == "bef" && params->dirpyrequalizer.enabled && !params->colorappearance.enabled))) { + // Forking the image assert(oprevi); Imagefloat *op = oprevi; oprevi = new Imagefloat(pW, pH); @@ -1885,15 +1937,31 @@ 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() { if (allocated) { + if (spotprev && spotprev != oprevi) { + delete spotprev; + } + spotprev = nullptr; + if (orig_prev != oprevi) { delete oprevi; } @@ -1926,6 +1994,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) @@ -1944,9 +2021,9 @@ void ImProcCoordinator::setScale(int prevscale) do { prevscale--; - PreviewProps pp(0, 0, fw, fh, prevscale); - imgsrc->getSize(pp, nW, nH); - } while (nH < 400 && prevscale > 1 && (nW * nH < 1000000)); // sctually hardcoded values, perhaps a better choice is possible + PreviewProps pp (0, 0, fw, fh, prevscale); + imgsrc->getSize (pp, nW, nH); + } while (nH < 400 && prevscale > 1 && (nW * nH < 1000000)); // actually hardcoded values, perhaps a better choice is possible if (nW != pW || nH != pH) { @@ -2387,35 +2464,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 == "autold") { - if (lastAwbEqual != params->wb.equal || lastAwbTempBias != params->wb.tempBias) { + } else if (validParams->wb.method == "autold") { + 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); } } @@ -2437,12 +2516,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 @@ -2473,7 +2552,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); @@ -2582,12 +2661,22 @@ void ImProcCoordinator::process() || params->dehaze != nextParams->dehaze || params->pdsharpening != nextParams->pdsharpening || params->filmNegative != nextParams->filmNegative + || params->spot.enabled != nextParams->spot.enabled || sharpMaskChanged; sharpMaskChanged = false; *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 796895125..8e4586c6d 100644 --- a/rtengine/improccoordinator.h +++ b/rtengine/improccoordinator.h @@ -42,6 +42,7 @@ namespace rtengine using namespace procparams; class Crop; +class TweakOperator; /** @brief Manages the image processing, espc. of the preview windows * @@ -62,6 +63,7 @@ class ImProcCoordinator final : public StagedImageProcessor, public HistogramObs protected: Imagefloat *orig_prev; Imagefloat *oprevi; + 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) @@ -206,6 +208,9 @@ protected: MyMutex minit; // to gain mutually exclusive access to ... to what exactly? + void backupParams(); + void restoreParams(); + void allocCache (Imagefloat* &imgfloat); void notifyHistogramChanged(); void reallocAll(); /// Updates L, R, G, and B histograms. Returns true unless not updated. @@ -220,7 +225,9 @@ protected: void updatePreviewImage (int todo, bool panningRelatedChange); 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 @@ -369,7 +376,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; @@ -410,6 +417,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 5020b36f2..ae914fcda 100644 --- a/rtengine/improcfun.h +++ b/rtengine/improcfun.h @@ -23,6 +23,7 @@ #include "coord2d.h" #include "gamutwarning.h" +#include "imagedimensions.h" #include "jaggedarray.h" #include "pipettebuffer.h" #include "array2D.h" @@ -78,12 +79,15 @@ class Image8; class Imagefloat; class LabImage; class wavelet_decomposition; +class ImageSource; +class ColorTemp; namespace procparams { class ProcParams; +struct SpotEntry; struct DehazeParams; struct FattalToneMappingParams; struct ColorManagementParams; @@ -448,6 +452,9 @@ enum class BlurType { float Mad(const float * DataList, int datalen); float MadRgb(const float * DataList, int datalen); + // spot removal tool + 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 cbdl_local_temp(float ** src, float ** loctemp, int srcwidth, int srcheight, const float * mult, float kchro, const double dirpyrThreshold, const float mergeL, const float contres, const double skinprot, const bool gamutlab, float b_l, float t_l, float t_r, float b_r, int choice, int scale, bool multiThread); 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/procparams.cc b/rtengine/procparams.cc index 317b34893..d9ee8c55f 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -365,6 +365,10 @@ namespace rtengine namespace procparams { +const short SpotParams::minRadius = 5; +const short SpotParams::maxRadius = 100; + + ToneCurveParams::ToneCurveParams() : autoexp(false), clip(0.02), @@ -1691,6 +1695,57 @@ bool EPDParams::operator !=(const EPDParams& other) const return !(*this == other); } +SpotEntry::SpotEntry() : + radius(25), + feather(1.f), + opacity(1.f) +{ +} + +float SpotEntry::getFeatherRadius() const +{ + return radius * (1.f + feather); +} + +bool SpotEntry::operator ==(const SpotEntry& other) const +{ + return other.sourcePos == sourcePos && other.targetPos == targetPos && + other.radius == radius && other.feather == feather && other.opacity == opacity; +} + +bool SpotEntry::operator !=(const SpotEntry& other) const +{ + return other.sourcePos != sourcePos || other.targetPos != targetPos || + other.radius != radius || other.feather != feather || other.opacity != opacity; +} + +SpotParams::SpotParams() : + enabled(false) +{ + entries.clear (); +} + +bool SpotParams::operator ==(const SpotParams& other) const +{ + if (enabled != other.enabled || entries.size() != other.entries.size()) { + return false; + } + + size_t i = 0; + for (auto entry : entries) { + if (entry != other.entries[i]) { + return false; + } + } + return true; +} + +bool SpotParams::operator !=(const SpotParams& other) const +{ + return !(*this == other); +} + + FattalToneMappingParams::FattalToneMappingParams() : enabled(false), threshold(30), @@ -6790,6 +6845,25 @@ int ProcParams::save(const Glib::ustring& fname, const Glib::ustring& fname2, bo saveToKeyfile(!pedited || pedited->wavelet.hueskin2, "Wavelet", "HueRange", wavelet.hueskin2.toVector(), keyFile); saveToKeyfile(!pedited || pedited->wavelet.contrast, "Wavelet", "Contrast", wavelet.contrast, keyFile); +//Spot removal + saveToKeyfile(!pedited || pedited->spot.enabled, "Spot removal", "Enabled", spot.enabled, keyFile); + for (size_t i = 0; i < spot.entries.size (); ++i) { + std::vector entry(7); + + entry[0] = double (spot.entries.at (i).sourcePos.x); + entry[1] = double (spot.entries.at (i).sourcePos.y); + entry[2] = double (spot.entries.at (i).targetPos.x); + entry[3] = double (spot.entries.at (i).targetPos.y); + entry[4] = double (spot.entries.at (i).radius); + entry[5] = double (spot.entries.at (i).feather); + entry[6] = double (spot.entries.at (i).opacity); + + std::stringstream ss; + ss << "Spot" << (i + 1); + + saveToKeyfile(!pedited || pedited->spot.entries, "Spot removal", ss.str(), entry, keyFile); + } + // Directional pyramid equalizer saveToKeyfile(!pedited || pedited->dirpyrequalizer.enabled, "Directional Pyramid Equalizer", "Enabled", dirpyrequalizer.enabled, keyFile); saveToKeyfile(!pedited || pedited->dirpyrequalizer.gamutlab, "Directional Pyramid Equalizer", "Gamutlab", dirpyrequalizer.gamutlab, keyFile); @@ -8520,6 +8594,34 @@ int ProcParams::load(const Glib::ustring& fname, ParamsEdited* pedited) } } + if (keyFile.has_group ("Spot removal")) { + assignFromKeyfile(keyFile, "Spot removal", "Enabled", pedited, spot.enabled, pedited->spot.enabled); + int i = 0; + do { + std::stringstream ss; + ss << "Spot" << (i++ + 1); + + if (keyFile.has_key ("Spot removal", ss.str())) { + Glib::ArrayHandle entry = keyFile.get_double_list ("Spot removal", ss.str()); + const double epsilon = 0.001; // to circumvent rounding of integer saved as double + SpotEntry se; + + se.sourcePos.set(int(entry.data()[0] + epsilon), int(entry.data()[1] + epsilon)); + se.targetPos.set(int(entry.data()[2] + epsilon), int(entry.data()[3] + epsilon)); + se.radius = LIM(int (entry.data()[4] + epsilon), SpotParams::minRadius, SpotParams::maxRadius); + se.feather = float(entry.data()[5]); + se.opacity = float(entry.data()[6]); + spot.entries.push_back(se); + + if (pedited) { + pedited->spot.entries = true; + } + } else { + break; + } + } while (1); + } + if (keyFile.has_group("PostDemosaicSharpening")) { assignFromKeyfile(keyFile, "PostDemosaicSharpening", "Enabled", pedited, pdsharpening.enabled, pedited->pdsharpening.enabled); assignFromKeyfile(keyFile, "PostDemosaicSharpening", "Contrast", pedited, pdsharpening.contrast, pedited->pdsharpening.contrast); @@ -9679,6 +9781,7 @@ bool ProcParams::operator ==(const ProcParams& other) const && chmixer == other.chmixer && blackwhite == other.blackwhite && resize == other.resize + && spot == other.spot && raw == other.raw && icm == other.icm && wavelet == other.wavelet diff --git a/rtengine/procparams.h b/rtengine/procparams.h index f7ca3e0f6..c9695a4ad 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -27,6 +27,7 @@ #include #include +#include "coord.h" #include "noncopyable.h" struct ParamsEdited; @@ -1697,6 +1698,41 @@ struct ResizeParams { bool operator !=(const ResizeParams& other) const; }; +/** + * Parameters entry + */ +struct SpotEntry { + Coord sourcePos; + Coord targetPos; + int radius; + float feather; + float opacity; + + SpotEntry(); + float getFeatherRadius() const; + + bool operator ==(const SpotEntry& other) const; + bool operator !=(const SpotEntry& other) const; +}; + +/** + * Parameters of the dust removal tool + */ +struct SpotParams { + bool enabled; + std::vector entries; + + // the following constant can be used for experimentation before the final merge + static const short minRadius; + static const short maxRadius; + + SpotParams(); + + bool operator ==(const SpotParams& other) const; + bool operator !=(const SpotParams& other) const; +}; + + /** * Parameters of the color spaces used during the processing */ @@ -2386,6 +2422,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/refreshmap.h b/rtengine/refreshmap.h index 0f3e5ee90..05a8f7f44 100644 --- a/rtengine/refreshmap.h +++ b/rtengine/refreshmap.h @@ -32,6 +32,7 @@ // Elementary functions that can be done to // the preview image when an event occurs +#define M_SPOT (1<<19) #define M_CSHARP (1<<18) #define M_MONITOR (1<<14) #define M_RETINEX (1<<13) @@ -51,23 +52,24 @@ // 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_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_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_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_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define DEMOSAIC (M_RAW|M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define ALLNORAW (M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR) -#define CAPTURESHARPEN (M_INIT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_CSHARP) -#define HDR (M_LINDENOISE|M_HDR|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_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) -#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 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 CAPTURESHARPEN (M_INIT|M_SPOT|M_LINDENOISE|M_HDR|M_TRANSFORM|M_BLURMAP|M_AUTOEXP|M_RGBCURVE|M_LUMACURVE|M_LUMINANCE|M_COLOR|M_CSHARP) +#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|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) +#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_VOID //M_MONITOR #define CROP M_CROP #define RESIZE M_VOID diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 142f2751e..83d47fa96 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -81,6 +81,7 @@ class IImage8; class IImage16; class IImagefloat; class ImageSource; +class TweakOperator; /** * This class provides functions to obtain exif and IPTC metadata information @@ -553,9 +554,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/simpleprocess.cc b/rtengine/simpleprocess.cc index 9a0d3b90b..f59140786 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -796,6 +796,11 @@ private: params.toneCurve.black = 0; } + // Spot Removal + if (params.spot.enabled && !params.spot.entries.empty ()) { + 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 // commented out because it makes the application crash when batch processing... // TODO: find a better place to flush rawData and rawRGB diff --git a/rtengine/spot.cc b/rtengine/spot.cc new file mode 100644 index 000000000..f43eaa909 --- /dev/null +++ b/rtengine/spot.cc @@ -0,0 +1,580 @@ +/* + * 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" +#include "procparams.h" +#include "imagesource.h" +#include "imagefloat.h" +#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 +{ + +class SpotBox { + +public: + enum class Type { + SOURCE, + TARGET, + FINAL + }; + + struct Rectangle { + int x1; + int y1; + int x2; + int y2; + + 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); + } + + bool getIntersection(const Rectangle &other, std::unique_ptr &intersection) const { + if (intersects(other)) { + 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 (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; + } + + 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/=(int v) { + if (v == 1) { + return *this; + } + + int w = x2 - x1 + 1; + int h = y2 - y1 + 1; + w = w / v + static_cast(w % v); + h = h / v + static_cast(h % v); + x1 /= v; + y1 /= v; + x2 = x1 + w - 1; + y2 = y1 + h - 1; + + return *this; + } + }; + +private: + Type type; + Imagefloat* image; + +public: + // 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, int radius, int feather_radius, Imagefloat* image, Type type) : + type(type), + 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, int radius, int feather_radius, Imagefloat* image, Type type) : + type(type), + 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), + image(nullptr), + intersectionArea(), + radius(spot.radius), + featherRadius(int(spot.getFeatherRadius() + 0.5f)) // rounding to int before resizing + { + 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; + } + + ~SpotBox() { + if (image && type != Type::FINAL) { + delete image; + } + } + + SpotBox& operator /=(int v) { + if (v == 1) { + return *this; + } + spotArea /= v; + imgArea /= v; + radius /= float(v); + featherRadius = getWidth() / 2.f; + // intersectionArea doesn't need resize, because it's set after resizing + return *this; + } + + int getWidth() { + return spotArea.getWidth(); + } + + int getHeight() { + return spotArea.getHeight(); + } + + int getImageWidth() { + return imgArea.getWidth(); + } + + int getImageHeight() { + return imgArea.getHeight(); + } + + int getIntersectionWidth() { + return intersectionArea.getWidth(); + } + + int getIntersectionHeight() { + return intersectionArea.getHeight(); + } + + bool checkImageSize() { + if (!image || getImageWidth() != image->getWidth() || getImageHeight() != image->getHeight()) { + return false; + } + return true; + } + + void tuneImageSize() { + if (!image) { + return; + } + if (getImageWidth() > image->getWidth()) { + imgArea.x2 = imgArea.x1 + image->getWidth() - 1; + } + if (getImageHeight() > image->getHeight()) { + imgArea.y2 = imgArea.y1 + image->getHeight() - 1; + } + } + + Imagefloat *getImage() { // TODO: this should send back a const value, but getImage don't want it to be const... + return image; + } + + void allocImage() { + int newW = imgArea.x2 - imgArea.x1 + 1; + int newH = imgArea.y2 - imgArea.y1 + 1; + + 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; + } + + 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 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); + + 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; + } + + 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; + } + + // 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::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 + + 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; + } +} + +} + +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; +} + +} + 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/CMakeLists.txt b/rtgui/CMakeLists.txt index 5f8baed47..7976bdc7a 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -150,6 +150,7 @@ set(NONCLISOURCEFILES softlight.cc soundman.cc splash.cc + spot.cc threadutils.cc thresholdadjuster.cc thresholdselector.cc diff --git a/rtgui/batchtoolpanelcoord.cc b/rtgui/batchtoolpanelcoord.cc index 4d7a08b5f..9e74ddb90 100644 --- a/rtgui/batchtoolpanelcoord.cc +++ b/rtgui/batchtoolpanelcoord.cc @@ -578,6 +578,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 ea11f97c8..0009724e8 100644 --- a/rtgui/batchtoolpanelcoord.h +++ b/rtgui/batchtoolpanelcoord.h @@ -56,6 +56,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/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 0ff449092..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 override; + 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 48aa6dab1..bdf723992 100644 --- a/rtgui/controlspotpanel.cc +++ b/rtgui/controlspotpanel.cc @@ -2197,7 +2197,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 2f92b9a77..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 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/cropwindow.cc b/rtgui/cropwindow.cc index 1ed9211a1..a3e00c80a 100644 --- a/rtgui/cropwindow.cc +++ b/rtgui/cropwindow.cc @@ -510,7 +510,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; @@ -1128,7 +1128,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()); @@ -1293,7 +1293,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) { @@ -1320,7 +1323,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; @@ -1346,7 +1352,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; } @@ -1375,6 +1384,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 e38f3a947..67cea5174 100644 --- a/rtgui/curveeditor.cc +++ b/rtgui/curveeditor.cc @@ -504,7 +504,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 d5ec49559..398c63603 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 5404b0bc9..71c342874 100644 --- a/rtgui/editcallbacks.cc +++ b/rtgui/editcallbacks.cc @@ -173,10 +173,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; @@ -187,12 +187,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 d81e84806..134ea6477 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 @@ -189,7 +191,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 fccdb874a..f9c9b3781 100644 --- a/rtgui/editwidgets.cc +++ b/rtgui/editwidgets.cc @@ -22,7 +22,11 @@ #include "editbuffer.h" #include "editcallbacks.h" #include "rtsurface.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 () { @@ -67,7 +71,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) { @@ -76,8 +81,9 @@ void Circle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer color = outerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); - 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); @@ -107,10 +113,11 @@ void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer color = innerLineColor; } - cr->set_source_rgba(color.getR(), color.getG(), color.getB(), opacity / 100.); + 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); @@ -123,9 +130,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.f) { cr->fill_preserve(); cr->stroke(); @@ -134,28 +144,20 @@ void Circle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer } } else if (innerLineWidth > 0.f) { 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); @@ -190,7 +192,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) { @@ -199,8 +202,9 @@ void Line::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuffer * color = outerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.0); - 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; @@ -234,10 +238,11 @@ void Line::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuffer * color = innerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); + 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; @@ -253,21 +258,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(); } } } @@ -276,6 +276,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; @@ -304,7 +305,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) { @@ -313,8 +315,10 @@ void Polyline::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuff color = outerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); - 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; @@ -357,10 +361,16 @@ void Polyline::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuff color = innerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); + 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; @@ -409,20 +419,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(); } } } @@ -439,8 +440,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) { @@ -497,7 +501,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) { @@ -506,8 +511,9 @@ void Rectangle::drawOuterGeometry(Cairo::RefPtr &cr, ObjectMOBuf color = outerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); - 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; @@ -550,10 +556,11 @@ void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuf color = innerLineColor; } - cr->set_source_rgba (color.getR(), color.getG(), color.getB(), opacity / 100.); + 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; @@ -573,7 +580,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.f) { @@ -584,20 +595,11 @@ void Rectangle::drawInnerGeometry(Cairo::RefPtr &cr, ObjectMOBuf } } else if (innerLineWidth > 0.f) { 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(); } } } @@ -606,6 +608,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 c86949cb4..fd539c355 100644 --- a/rtgui/editwidgets.h +++ b/rtgui/editwidgets.h @@ -24,10 +24,11 @@ #include #include "editcoordsys.h" +#include "rtsurface.h" #include "../rtengine/coord.h" +#include "../rtengine/rt_math.h" class ObjectMOBuffer; -class RTSurface; /** @file * @@ -210,6 +211,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: @@ -226,6 +229,7 @@ public: }; protected: + static const std::vector dash; RGBColor innerLineColor; RGBColor outerLineColor; short flags; @@ -252,6 +256,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! @@ -449,7 +455,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) { @@ -484,6 +490,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 5fc15b5ba..c57d404c1 100644 --- a/rtgui/filmnegative.cc +++ b/rtgui/filmnegative.cc @@ -540,7 +540,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 e27a38e09..64ec572d3 100644 --- a/rtgui/filmnegative.h +++ b/rtgui/filmnegative.h @@ -71,7 +71,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 b0c6000be..972105dd0 100644 --- a/rtgui/gradient.cc +++ b/rtgui/gradient.cc @@ -329,7 +329,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 64eabefae..139b281a8 100644 --- a/rtgui/gradient.h +++ b/rtgui/gradient.h @@ -55,7 +55,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/paramsedited.cc b/rtgui/paramsedited.cc index f6ad44605..a28d666b4 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -426,6 +426,10 @@ void ParamsEdited::set(bool v) resize.width = v; resize.height = v; resize.enabled = v; + + spot.enabled = v; + spot.entries = v; + resize.allowUpscaling = v; icm.inputProfile = v; icm.toneCurve = v; @@ -1715,6 +1719,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; resize.allowUpscaling = resize.allowUpscaling && p.resize.allowUpscaling == other.resize.allowUpscaling; icm.inputProfile = icm.inputProfile && p.icm.inputProfile == other.icm.inputProfile; icm.toneCurve = icm.toneCurve && p.icm.toneCurve == other.icm.toneCurve; @@ -5526,6 +5532,14 @@ void ParamsEdited::combine(rtengine::procparams::ProcParams& toEdit, const rteng } + if (spot.enabled) { + toEdit.spot.enabled = mods.spot.enabled; + } + + if (spot.entries) { + toEdit.spot.entries = mods.spot.entries; + } + if (pcvignette.enabled) { toEdit.pcvignette.enabled = mods.pcvignette.enabled; } diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 09623ea1d..a422b653e 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -1087,6 +1087,13 @@ struct ResizeParamsEdited { bool allowUpscaling; }; +class SpotParamsEdited +{ +public: + bool enabled; + bool entries; +}; + struct ColorManagementParamsEdited { bool inputProfile; bool toneCurve; @@ -1437,6 +1444,7 @@ struct ParamsEdited { ChannelMixerParamsEdited chmixer; BlackWhiteParamsEdited blackwhite; ResizeParamsEdited resize; + SpotParamsEdited spot; ColorManagementParamsEdited icm; RAWParamsEdited raw; DirPyrEqualizerParamsEdited dirpyrequalizer; diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc index c759bcd44..9a3399662 100644 --- a/rtgui/partialpastedlg.cc +++ b/rtgui/partialpastedlg.cc @@ -226,6 +226,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"))); @@ -339,6 +340,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); @@ -501,6 +503,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)); @@ -719,6 +722,7 @@ void PartialPasteDlg::basicToggled () void PartialPasteDlg::detailToggled () { + ConnectionBlocker spotBlocker(spotConn); ConnectionBlocker sharpenBlocker(sharpenConn); ConnectionBlocker localcontrastBlocker(localcontrastConn); ConnectionBlocker gradsharpenBlocker(gradsharpenConn); @@ -731,6 +735,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 ()); @@ -912,6 +917,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 c61408df7..19e1eb462 100644 --- a/rtgui/partialpastedlg.h +++ b/rtgui/partialpastedlg.h @@ -142,6 +142,7 @@ public: Gtk::CheckButton* colorappearance; // options in detail: + Gtk::CheckButton* spot; Gtk::CheckButton* sharpen; Gtk::CheckButton* sharpenedge; Gtk::CheckButton* sharpenmicro; @@ -224,7 +225,7 @@ public: sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaConn, rawConn, advancedConn; sigc::connection locallabConn; 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/sharpenedge.cc b/rtgui/sharpenedge.cc index 552169fc1..e00d919c3 100644 --- a/rtgui/sharpenedge.cc +++ b/rtgui/sharpenedge.cc @@ -23,7 +23,6 @@ #include "guiutils.h" #include "options.h" - #include "../rtengine/procparams.h" using namespace rtengine; diff --git a/rtgui/spot.cc b/rtgui/spot.cc new file mode 100644 index 000000000..10ff99767 --- /dev/null +++ b/rtgui/spot.cc @@ -0,0 +1,817 @@ +/* + * 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" +#include +#include "../rtengine/rt_math.h" +#include "guiutils.h" +#include "eventmapper.h" +#include "../rtengine/refreshmap.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), + 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))); + + 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"))); + 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::Box()); + 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; + sourceCircle.setDashed(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; + 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); + + auto m = ProcEventMapper::getInstance(); + EvSpotEnabled = m->newEvent(ALLNORAW, "TP_SPOT_LABEL"); + EvSpotEnabledOPA = m->newEvent(SPOTADJUST, "TP_SPOT_LABEL"); + EvSpotEntry = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); + EvSpotEntryOPA = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY"); + + 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; + activeSpot = -1; + lastObject = -1; + + if (batchMode) { + editedCheckBox->set_label(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size())); + } + else { + 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); + + editedCheckBox->set_label(Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size())); + + if (listener) { + listener->panelChanged (EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)); + } + } else { + if (!spots.empty()) { + spots.clear(); + activeSpot = -1; + lastObject = -1; + createGeometry(); + updateGeometry(); + + if (listener) { + listener->panelChanged (edit->get_active() ? EvSpotEntryOPA : 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 = 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); + 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 (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_UNCHANGED")); + } else if (getEnabled()) { + listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_ENABLED")); + } else { + listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_DISABLED")); + } + } +} + +void Spot::setEditProvider (EditDataProvider* provider) +{ + EditSubscriber::setEditProvider (provider); +} + +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 + } + } +} + +Geometry* Spot::getVisibleGeometryFromMO (int MOID) +{ + if (MOID == -1) { + return nullptr; + } + + if (MOID == 0) { + return getActiveSpotIcon(); + } + + if (MOID == 1) { // sourceMODisc + return &sourceIcon; + } + + if (MOID > STATIC_MO_OBJ_NBR) { + return EditSubscriber::visibleGeometry.at(MOID - STATIC_MO_OBJ_NBR); + } + + 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); + } + + // 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 + 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 (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; + //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); + } + + sourceCircle.setVisible(draggedSide != DraggedSide::SOURCE); + targetCircle.setVisible(draggedSide != DraggedSide::TARGET); + link.setVisible(draggedSide == DraggedSide::NONE); + } 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 nullptr; +} + +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 (EvSpotEntryOPA, 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")); + } +} + +CursorShape Spot::getCursor (int objectID, int xPos, int yPos) const +{ + const EditDataProvider* editProvider = getEditProvider(); + if (editProvider && activeSpot > -1) { + if (draggedSide != DraggedSide::NONE) { + return CSEmpty; + } + + if (objectID == 0 || objectID == 1) { + return CSMove2D; + } + 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.; + } + 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) +{ + EditDataProvider* editProvider = getEditProvider(); + + if (editProvider && editProvider->getObject() != 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->getObject() > -1) { + getVisibleGeometryFromMO (editProvider->getObject())->state = Geometry::PRELIGHT; + + if (editProvider->getObject() >= STATIC_MO_OBJ_NBR) { + // a Spot is being edited + int oldActiveSpot = activeSpot; + activeSpot = editProvider->getObject() - 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->getObject(); + + 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 (int modifierKey) +{ + EditDataProvider* editProvider = getEditProvider(); + + 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; + } + } + + return false; +} + +// End the drag of a Target point +bool Spot::button1Released() +{ + Geometry *loGeom = getVisibleGeometryFromMO (lastObject); + + if (!loGeom) { + EditSubscriber::action = EditSubscriber::Action::NONE; + return false; + } + + loGeom->state = Geometry::PRELIGHT; + EditSubscriber::action = EditSubscriber::Action::NONE; + draggedSide = DraggedSide::NONE; + updateGeometry(); + return true; +} + +// Delete a point +bool Spot::button2Pressed (int modifierKey) +{ + EditDataProvider* editProvider = getEditProvider(); + + if (!editProvider || lastObject == -1 || activeSpot == -1) { + return false; + } + + if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) { + EditSubscriber::action = EditSubscriber::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 (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 = EditSubscriber::Action::DRAGGING; + draggedSide = DraggedSide::SOURCE; + return true; + } else if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) { + EditSubscriber::action = EditSubscriber::Action::PICKING; + } + + return false; +} + +bool Spot::button3Released() +{ + Geometry *loGeom = getVisibleGeometryFromMO (lastObject); + + if (!loGeom) { + EditSubscriber::action = EditSubscriber::Action::NONE; + return false; + } + + lastObject = -1; + sourceIcon.state = Geometry::ACTIVE; + draggedSide = DraggedSide::NONE; + updateGeometry(); + EditSubscriber::action = EditSubscriber::Action::NONE; + return true; + + return false; +} + +bool Spot::drag1 (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 (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 (bool picked) +{ + return pick3 (picked); +} + +bool Spot::pick3 (bool picked) +{ + EditDataProvider* editProvider = getEditProvider(); + + if (!picked) { + if (editProvider->getObject() != lastObject) { + return false; + } + } + + // Object is picked, we delete it + deleteSelectedEntry(); + EditSubscriber::action = EditSubscriber::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 + 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 + //pparams.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 new file mode 100644 index 000000000..db1fdac05 --- /dev/null +++ b/rtgui/spot.h @@ -0,0 +1,135 @@ +/* + * 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_ + +#include +#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 + * + * 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 rtengine::TweakOperator, public EditSubscriber +{ + +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 + 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::Box* 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 = nullptr) override; + void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited = nullptr) override; + + void enabledChanged () override; + + void setEditProvider (EditDataProvider* provider) override; + + void setBatchMode (bool batchMode) override; + + // EditSubscriber interface + CursorShape getCursor (int objectID, int xPos, int yPos) const override; + 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; + + rtengine::ProcEvent EvSpotEnabled; + rtengine::ProcEvent EvSpotEnabledOPA; // used to toggle-on the Spot 'On Preview Adjustment' mode + rtengine::ProcEvent EvSpotEntry; + rtengine::ProcEvent EvSpotEntryOPA; +}; + +#endif diff --git a/rtgui/thumbimageupdater.cc b/rtgui/thumbimageupdater.cc index 03606bb3d..540ad625e 100644 --- a/rtgui/thumbimageupdater.cc +++ b/rtgui/thumbimageupdater.cc @@ -47,8 +47,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/toolpanel.h b/rtgui/toolpanel.h index f07f5f0a1..8fdb4540d 100644 --- a/rtgui/toolpanel.h +++ b/rtgui/toolpanel.h @@ -39,12 +39,21 @@ namespace procparams class ProcParams; } } +class EditDataProvider; 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; + /// @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 0866e970b..12e780cca 100644 --- a/rtgui/toolpanelcoord.cc +++ b/rtgui/toolpanelcoord.cc @@ -49,6 +49,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit 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 ()); @@ -117,6 +118,7 @@ ToolPanelCoordinator::ToolPanelCoordinator (bool batch) : ipc (nullptr), favorit addfavoritePanel (colorPanel, chmixer); addfavoritePanel (colorPanel, blackwhite); addfavoritePanel (exposurePanel, shadowshighlights); + addfavoritePanel (detailsPanel, spot); addfavoritePanel (detailsPanel, sharpening); addfavoritePanel (detailsPanel, localContrast); addfavoritePanel (detailsPanel, sharpenEdge); @@ -451,6 +453,33 @@ void ToolPanelCoordinator::imageTypeChanged(bool isRaw, bool isBayer, bool isXtr } +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) { + 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 675b77de7..28bfed5ab 100644 --- a/rtgui/toolpanelcoord.h +++ b/rtgui/toolpanelcoord.h @@ -76,6 +76,7 @@ #include "sharpening.h" #include "sharpenmicro.h" #include "softlight.h" +#include "spot.h" #include "tonecurve.h" #include "toolbar.h" #include "toolpanel.h" @@ -133,6 +134,7 @@ protected: ToneCurve* toneCurve; ShadowsHighlights* shadowshighlights; LocalContrast *localContrast; + Spot* spot; Defringe* defringe; ImpulseDenoise* impulsedenoise; DirPyrDenoise* dirpyrdenoise; @@ -252,8 +254,12 @@ 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; // profilechangelistener interface diff --git a/rtgui/xtransrawexposure.cc b/rtgui/xtransrawexposure.cc index 655a036c7..e1b56b9f0 100644 --- a/rtgui/xtransrawexposure.cc +++ b/rtgui/xtransrawexposure.cc @@ -22,7 +22,6 @@ #include "guiutils.h" #include "options.h" - #include "../rtengine/procparams.h" using namespace rtengine;