diff --git a/WindowsEnvironmentSetup.html b/WindowsEnvironmentSetup.html
new file mode 100644
index 000000000..de8e4b61d
--- /dev/null
+++ b/WindowsEnvironmentSetup.html
@@ -0,0 +1,45 @@
+
Building Raw Therapee in Windows 64
+12 November 2011
+
+
+This short guide outlines setting up a development environment for building RawTherapee (64 bit) in Windows.
+Apology and warning: the procedure is convoluted. Prepare for frustration if you deviate even one bit. Sorry.
+
+Step 0:
+Use TortoiseHg to grab the latest Raw Therapee, put it in a directory whose name doesn't contain spaces. This is more or less simple but well documented, please figure it out yourself.
+
+Step 1:
+Download the latest CMake. Install it into C:\CMake.
+
+Step 2:
+Download a rather specific version of gtkmm64. Install it into C:\gtkmm64.
+
+Step 3:
+Download a rather specific build of MinGW. When installing, uncheck "Check for updated files...", check "Experimental (32 and 64 bit)", check the "openmp" component under Components->gcc, install into C:\MinGW64.
+
+Step 4:
+Download the latest Precompiled package for 64bit Windows. Unzip the file contents (already organized into several subdirectories) into C:\MinGW64.
+
+Step 5:
+Download a rather specific build of MSYS. Install to C:\msys. At the end it asks for your MinGW directory, make sure you give it C:/MinGW64. That's a forward slash, and it matters.
+
+Step 6:
+Create a file named build.bat in your raw therapee source directory with the following content:
+
+set GTKMM_BASEPATH=C:\gtkmm64
+set GTKMM64_BASEPATH=C:\gtkmm64
+set MINGW_BASEPATH=C:\MinGW64
+set PATH=%PATH%;C:\gtkmm64\bin;C:\MinGW64\bin;C:\CMake\bin
+set PKG_CONFIG_PATH=C:\MinGW64\lib\pkgconfig;c:\gtkmm64\lib\pkgconfig
+
+cmake -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" -DPROC_TARGET_NUMBER:STRING=2
+mingw32-make -j2 install
+pause
+
+
+
+Running the batch file above after completing all steps properly should slowly but flawlessly build Raw Therapee.
+If it doesn't work, please tell us about it at the Raw Therapee forum. Maybe this document needs updating.
+Do not rest until it builds. A complicated build sucks, but out of date or inaccurate build documentation is unacceptable.
+
+
diff --git a/rtdata/languages/default b/rtdata/languages/default
index 2b4fbce8d..1df01f9fa 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -347,6 +347,10 @@ HISTORY_MSG_97;'b' curve
HISTORY_MSG_98;Demosaicing method
HISTORY_MSG_99;Hot/dead pixel filtering
HISTORY_MSG_9;Highlight Compression
+HISTORY_MSG_158;Strength
+HISTORY_MSG_159;Edge stopping
+HISTORY_MSG_160;Scale
+HISTORY_MSG_161;Reweighting iterates
HISTORY_NEWSNAPSHOT;Add
HISTORY_NEWSNAPSHOTAS;As...
HISTORY_NEWSSDIALOGLABEL;Label of the snapshot:
@@ -509,6 +513,7 @@ PARTIALPASTE_CROP;Crop
PARTIALPASTE_DARKFRAMEAUTOSELECT;Dark Frame Auto Select
PARTIALPASTE_DARKFRAMEFILE;Dark Frame File
PARTIALPASTE_DEFRINGE;Defringe
+PARTIALPASTE_EPD;Tone Mapping
PARTIALPASTE_DETAILGROUP;Detail settings
PARTIALPASTE_DIALOGLABEL;Partial paste processing profile
PARTIALPASTE_DIRPYRDENOISE;Noise reduction
@@ -796,6 +801,11 @@ TP_DARKFRAME_LABEL;Dark Frame
TP_DEFRINGE_LABEL;Defringe
TP_DEFRINGE_RADIUS;Radius
TP_DEFRINGE_THRESHOLD;Threshold
+TP_EPD_LABEL;Tone Mapping
+TP_EPD_STRENGTH;Strength
+TP_EPD_EDGESTOPPING;Edge stopping
+TP_EPD_SCALE;Scale
+TP_EPD_REWEIGHTINGITERATES;Reweighting iterates
TP_DETAIL_AMOUNT;Amount
TP_DIRPYRDENOISE_CHROMA;Chrominance
TP_DIRPYRDENOISE_GAMMA;Gamma
diff --git a/rtengine/EdgePreservingDecomposition.cc b/rtengine/EdgePreservingDecomposition.cc
new file mode 100644
index 000000000..04b97f448
--- /dev/null
+++ b/rtengine/EdgePreservingDecomposition.cc
@@ -0,0 +1,461 @@
+#include "EdgePreservingDecomposition.h"
+
+#ifndef min
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+
+
+
+/* Solves A x = b by the conjugate gradient method, where instead of feeding it the matrix A you feed it a function which
+calculates A x where x is some vector. Stops when rms residual < RMSResidual or when maximum iterates is reached.
+Stops at n iterates if MaximumIterates = 0 since that many iterates gives exact solution. Applicable to symmetric positive
+definite problems only, which is what unconstrained smooth optimization pretty much always is.
+Parameter pass can be passed through, containing whatever info you like it to contain (matrix info?).
+Takes less memory with OkToModify_b = true, and Preconditioner = NULL. */
+float *SparseConjugateGradient(void Ax(float *Product, float *x, void *Pass), float *b, unsigned int n, bool OkToModify_b,
+ float *x, float RMSResidual, void *Pass, unsigned int MaximumIterates, void Preconditioner(float *Product, float *x, void *Pass)){
+ unsigned int iterate, i;
+
+ //Start r and x.
+ float *r = new float[n];
+ if(x == NULL){
+ x = new float[n];
+ memset(x, 0, sizeof(float)*n); //Zero initial guess if x == NULL.
+ memcpy(r, b, sizeof(float)*n);
+ }else{
+ Ax(r, x, Pass);
+ for(i = 0; i != n; i++) r[i] = b[i] - r[i]; //r = b - A x.
+ }
+
+ //s is preconditionment of r. Without, direct to r.
+ float *s = r, rs = 0.0f;
+ if(Preconditioner != NULL){
+ s = new float[n];
+ Preconditioner(s, r, Pass);
+ }
+ for(i = 0; i != n; i++) rs += r[i]*s[i];
+
+ //Search direction d.
+ float *d = new float[n];
+ memcpy(d, s, sizeof(float)*n);
+
+ //Store calculations of Ax in this.
+ float *ax = b;
+ if(!OkToModify_b) ax = new float[n];
+
+ //Start iterating!
+ if(MaximumIterates == 0) MaximumIterates = n;
+ for(iterate = 0; iterate != MaximumIterates; iterate++){
+ //Get step size alpha, store ax while at it.
+ float ab = 0.0f;
+ Ax(ax, d, Pass);
+ for(i = 0; i != n; i++) ab += d[i]*ax[i];
+
+ if(ab == 0.0f) break; //So unlikely. It means perfectly converged or singular, stop either way.
+ ab = rs/ab;
+
+ //Update x and r with this step size.
+ float rms = 0.0;
+ for(i = 0; i != n; i++){
+ x[i] += ab*d[i];
+ r[i] -= ab*ax[i]; //"Fast recursive formula", use explicit r = b - Ax occasionally?
+ rms += r[i]*r[i];
+ }
+ rms = sqrtf(rms/n);
+//printf("%f\n", rms);
+ //Quit? This probably isn't the best stopping condition, but ok.
+ if(rms < RMSResidual) break;
+
+ if(Preconditioner != NULL) Preconditioner(s, r, Pass);
+
+ //Get beta.
+ ab = rs;
+ rs = 0.0f;
+ for(i = 0; i != n; i++) rs += r[i]*s[i];
+ ab = rs/ab;
+
+ //Update search direction p.
+ for(i = 0; i != n; i++) d[i] = s[i] + ab*d[i];
+ }
+ if(iterate == MaximumIterates)
+ if(iterate != n && RMSResidual != 0.0f)
+ printf("Warning: MaximumIterates (%u) reached in SparseConjugateGradient.\n", MaximumIterates);
+
+ if(ax != b) delete[] ax;
+ if(s != r) delete[] s;
+ delete[] r;
+ delete[] d;
+ return x;
+}
+
+
+MultiDiagonalSymmetricMatrix::MultiDiagonalSymmetricMatrix(unsigned int Dimension, unsigned int NumberOfDiagonalsInLowerTriangle){
+ n = Dimension;
+ m = NumberOfDiagonalsInLowerTriangle;
+ IncompleteCholeskyFactorization = NULL;
+
+ Diagonals = new float *[m];
+ StartRows = new unsigned int [m];
+ memset(Diagonals, 0, sizeof(float *)*m);
+ memset(StartRows, 0, sizeof(unsigned int)*m);
+}
+
+MultiDiagonalSymmetricMatrix::~MultiDiagonalSymmetricMatrix(){
+ for(unsigned int i = 0; i != m; i++) delete[] Diagonals[i];
+ delete[] Diagonals;
+ delete[] StartRows;
+}
+
+bool MultiDiagonalSymmetricMatrix::CreateDiagonal(unsigned int index, unsigned int StartRow){
+ if(index >= m){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateDiagonal: invalid index.\n");
+ return false;
+ }
+ if(index > 0)
+ if(StartRow <= StartRows[index - 1]){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateDiagonal: each StartRow must exceed the previous.\n");
+ return false;
+ }
+
+ delete[] Diagonals[index];
+ Diagonals[index] = new float[DiagonalLength(StartRow)];
+ if(Diagonals[index] == NULL){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateDiagonal: memory allocation failed. Out of memory?\n");
+ return false;
+ }
+
+ StartRows[index] = StartRow;
+ memset(Diagonals[index], 0, sizeof(float)*DiagonalLength(StartRow));
+ return true;
+}
+
+int MultiDiagonalSymmetricMatrix::FindIndex(unsigned int StartRow){
+ //There's GOT to be a better way to do this. "Bidirectional map?"
+ for(unsigned int i = 0; i != m; i++)
+ if(StartRows[i] == StartRow)
+ return i;
+ return -1;
+}
+
+bool MultiDiagonalSymmetricMatrix::LazySetEntry(float value, unsigned int row, unsigned int column){
+ //On the strict upper triangle? Swap, this is ok due to symmetry.
+ int i, sr;
+ if(column > row)
+ i = column,
+ column = row,
+ row = i;
+ if(row >= n) return false;
+ sr = row - column;
+
+ //Locate the relevant diagonal.
+ i = FindIndex(sr);
+ if(i < 0) return false;
+
+ Diagonals[i][column] = value;
+ return true;
+}
+
+void MultiDiagonalSymmetricMatrix::VectorProduct(float *Product, float *x){
+ //Initialize to zero.
+ memset(Product, 0, n*sizeof(float));
+
+ //Loop over the stored diagonals.
+ for(unsigned int i = 0; i != m; i++){
+ unsigned int sr = StartRows[i];
+ float *a = Diagonals[i]; //One fewer dereference.
+ unsigned int j, l = DiagonalLength(sr);
+
+ if(sr == 0)
+ for(j = 0; j != l; j++)
+ Product[j] += a[j]*x[j]; //Separate, fairly simple treatment for the main diagonal.
+ else
+ for(j = 0; j != l; j++)
+ Product[j + sr] += a[j]*x[j], //Contribution from lower...
+ Product[j] += a[j]*x[j + sr]; //...and upper triangle.
+ }
+}
+
+bool MultiDiagonalSymmetricMatrix::CreateIncompleteCholeskyFactorization(unsigned int MaxFillAbove){
+ if(m == 1){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateIncompleteCholeskyFactorization: just one diagonal? Can you divide?\n");
+ return false;
+ }
+ if(StartRows[0] != 0){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateIncompleteCholeskyFactorization: main diagonal required to exist for this math.\n");
+ return false;
+ }
+
+ //How many diagonals in the decomposition?
+ MaxFillAbove++; //Conceptually, now "fill" includes an existing diagonal. Simpler in the math that follows.
+ unsigned int i, j, mic;
+ for(mic = i = 1; i != m; i++)
+ mic += min(StartRows[i] - StartRows[i - 1], MaxFillAbove); //Guarunteed positive since StartRows must be created in increasing order.
+
+ //Initialize the decomposition - setup memory, start rows, etc.
+ MultiDiagonalSymmetricMatrix *ic = new MultiDiagonalSymmetricMatrix(n, mic);
+ ic->CreateDiagonal(0, 0); //There's always a main diagonal in this type of decomposition.
+ for(mic = i = 1; i != m; i++){
+ //Set j to the number of diagonals to be created corresponding to a diagonal on this source matrix...
+ j = min(StartRows[i] - StartRows[i - 1], MaxFillAbove);
+
+ //...and create those diagonals. I want to take a moment to tell you about how much I love minimalistic loops: very much.
+ while(j-- != 0)
+ if(!ic->CreateDiagonal(mic++, StartRows[i] - j)){
+ //Beware of out of memory, possible for large, sparse problems if you ask for too much fill.
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateIncompleteCholeskyFactorization: Out of memory. Ask for less fill?\n");
+ delete ic;
+ return false;
+ }
+ }
+
+
+ //It's all initialized? Uhkay. Do the actual math then.
+ int sss, ss, s;
+ unsigned int k, MaxStartRow = StartRows[m - 1]; //Handy number.
+ float **l = ic->Diagonals;
+ float *d = ic->Diagonals[0]; //Describes D in LDLt.
+
+ //Loop over the columns.
+ for(j = 0; j != n; j++){
+ //Calculate d for this column.
+ d[j] = Diagonals[0][j];
+
+ //This is a loop over k from 1 to j, inclusive. We'll cover that by looping over the index of the diagonals (s), and get k from it.
+ //The first diagonal is d (k = 0), so skip that and have s start at 1. Cover all available s but stop if k exceeds j.
+ for(s = 1; s != ic->m; s++){
+ k = ic->StartRows[s];
+ if(k > j) break;
+ d[j] -= l[s][j - k]*l[s][j - k]*d[j - k];
+ }
+
+ if(d[j] == 0.0f){
+ printf("Error in MultiDiagonalSymmetricMatrix::CreateIncompleteCholeskyFactorization: division by zero. Matrix not decomposable.\n");
+ delete ic;
+ return false;
+ }
+ float id = 1.0f/d[j];
+
+ //Now, calculate l from top down along this column.
+ for(s = 1; s != ic->m; s++){
+ i = ic->StartRows[s]; //Start row for this entry.
+ if(j >= ic->n - i) break; //Possible values of j are limited.
+
+ //Quicker access for an element of l.
+ float *lij = &l[s][j];
+ sss = FindIndex(i); //Find element in same spot in the source matrix. It might be a zero.
+ *lij = sss < 0 ? 0.0f : Diagonals[sss][j];
+
+ //Similar to the loop involving d, convoluted by the fact that two l are involved.
+ for(ss = 1; ss != ic->m; ss++){
+ k = ic->StartRows[ss];
+ if(k > j) break;
+ if(i + k > MaxStartRow) break; //Quick exit once k to big.
+
+ int sss = ic->FindIndex(i + k);
+ if(sss < 0) continue; //Asked for diagonal nonexistant. But there may be something later, so don't break.
+
+ /* Let's think about the factors in the term below for a moment.
+ j varies from 0 to n - 1, so j - k is bounded inclusive by 0 and j - 1. So d[j - k] is always in the matrix.
+
+ l[sss] and l[ss] are diagonals with corresponding start rows i + k and k.
+ For l[sss][j - k] to exist, we must have j - k < n - (i + k) -> j < n - i, which was checked outside this loop and true at this point.
+ For l[ ss][j - k] to exist, we must have j - k < n - k -> j < n, which is true straight from definition.
+
+ So, no additional checks, all is good and within bounds at this point.*/
+ *lij -= l[sss][j - k]*l[ss][j - k]*d[j - k];
+ }
+
+ *lij *= id;
+ }
+ }
+
+ IncompleteCholeskyFactorization = ic;
+ return true;
+}
+
+void MultiDiagonalSymmetricMatrix::KillIncompleteCholeskyFactorization(void){
+ delete IncompleteCholeskyFactorization;
+}
+
+void MultiDiagonalSymmetricMatrix::CholeskyBackSolve(float *x, float *b){
+ //We want to solve L D Lt x = b where D is a diagonal matrix described by Diagonals[0] and L is a unit lower triagular matrix described by the rest of the diagonals.
+ //Let D Lt x = y. Then, first solve L y = b.
+ float *y = new float[n];
+ float **d = IncompleteCholeskyFactorization->Diagonals;
+ unsigned int *s = IncompleteCholeskyFactorization->StartRows;
+ unsigned int M = IncompleteCholeskyFactorization->m, N = IncompleteCholeskyFactorization->n;
+ unsigned int i, j;
+ for(j = 0; j != N; j++){
+ y[j] = b[j];
+
+ for(i = 1; i != M; i++){ //Start at 1 because zero is D.
+ int c = (int)j - (int)s[i];
+ if(c < 0) break; //Due to ordering of StartRows, no further contributions.
+ y[j] -= d[i][c]*y[c];
+ }
+ }
+
+ //Now, solve x from D Lt x = y -> Lt x = D^-1 y
+ while(j-- != 0){
+ x[j] = y[j]/d[0][j];
+
+ for(i = 1; i != M; i++){
+ if(j + s[i] >= N) break;
+ x[j] -= d[i][j]*x[j + s[i]];
+ }
+ }
+
+ delete[] y;
+}
+
+
+
+
+EdgePreservingDecomposition::EdgePreservingDecomposition(unsigned int width, unsigned int height){
+ w = width;
+ h = height;
+ n = w*h;
+
+ //Initialize the matrix just once at construction.
+ A = new MultiDiagonalSymmetricMatrix(n, 5);
+ if(!(
+ A->CreateDiagonal(0, 0) &&
+ A->CreateDiagonal(1, 1) &&
+ A->CreateDiagonal(2, w - 1) &&
+ A->CreateDiagonal(3, w) &&
+ A->CreateDiagonal(4, w + 1))){
+ delete A;
+ A = NULL;
+ printf("Error in EdgePreservingDecomposition construction: out of memory.\n");
+ }else{
+ a0 = A->Diagonals[0];
+ a_1 = A->Diagonals[1];
+ a_w1 = A->Diagonals[2];
+ a_w = A->Diagonals[3];
+ a_w_1 = A->Diagonals[4];
+ }
+}
+
+EdgePreservingDecomposition::~EdgePreservingDecomposition(){
+ delete A;
+}
+
+float *EdgePreservingDecomposition::CreateBlur(float *Source, float Scale, float EdgeStopping, unsigned int Iterates, float *Blur, bool UseBlurForEdgeStop){
+ if(Blur == NULL)
+ UseBlurForEdgeStop = false, //Use source if there's no supplied Blur.
+ Blur = new float[n];
+ if(Scale == 0.0f){
+ memcpy(Blur, Source, n*sizeof(float));
+ return Blur;
+ }
+
+ //Create the edge stopping function a, rotationally symmetric and just one instead of (ax, ay). Maybe don't need Blur yet, so use its memory.
+ float *a, *g;
+ if(UseBlurForEdgeStop) a = new float[n], g = Blur;
+ else a = Blur, g = Source;
+
+ unsigned int x, y, i;
+ unsigned int w1 = w - 1, h1 = h - 1;
+ float eps = 0.02f;
+ for(y = 0; y != h1; y++){
+ float *rg = &g[w*y];
+ for(x = 0; x != w1; x++){
+ //Estimate the central difference gradient in the center of a four pixel square. (gx, gy) is actually 2*gradient.
+ float gx = (rg[x + 1] - rg[x]) + (rg[x + w + 1] - rg[x + w]);
+ float gy = (rg[x + w] - rg[x]) + (rg[x + w + 1] - rg[x + 1]);
+
+ //Apply power to the magnitude of the gradient to get the edge stopping function.
+ a[x + w*y] = Scale*powf(0.5f*sqrtf(gx*gx + gy*gy + eps*eps), -EdgeStopping);
+ }
+ }
+
+ /* Now setup the linear problem. I use the Maxima CAS, here's code for making an FEM formulation for the smoothness term:
+ p(x, y) := (1 - x)*(1 - y);
+ P(m, n) := A[m][n]*p(x, y) + A[m + 1][n]*p(1 - x, y) + A[m + 1][n + 1]*p(1 - x, 1 - y) + A[m][n + 1]*p(x, 1 - y);
+ Integrate(f) := integrate(integrate(f, x, 0, 1), y, 0, 1);
+
+ Integrate(diff(P(u, v), x)*diff(p(x, y), x) + diff(P(u, v), y)*diff(p(x, y), y));
+ Integrate(diff(P(u - 1, v), x)*diff(p(1 - x, y), x) + diff(P(u - 1, v), y)*diff(p(1 - x, y), y));
+ Integrate(diff(P(u - 1, v - 1), x)*diff(p(1 - x, 1 - y), x) + diff(P(u - 1, v - 1), y)*diff(p(1 - x, 1 - y), y));
+ Integrate(diff(P(u, v - 1), x)*diff(p(x, 1 - y), x) + diff(P(u, v - 1), y)*diff(p(x, 1 - y), y));
+ So yeah. Use the numeric results of that to fill the matrix A.*/
+ memset(a_1, 0, A->DiagonalLength(1)*sizeof(float));
+ memset(a_w1, 0, A->DiagonalLength(w - 1)*sizeof(float));
+ memset(a_w, 0, A->DiagonalLength(w)*sizeof(float));
+ memset(a_w_1, 0, A->DiagonalLength(w + 1)*sizeof(float));
+ for(i = y = 0; y != h; y++){
+ for(x = 0; x != w; x++, i++){
+ float ac;
+ a0[i] = 1.0;
+
+ //Remember, only fill the lower triangle. Memory for upper is never made. It's symmetric. Trust.
+ if(x > 0 && y > 0)
+ ac = a[i - w - 1]/6.0f,
+ a_w_1[i - w - 1] -= 2.0f*ac, a_w[i - w] -= ac,
+ a_1[i - 1] -= ac, a0[i] += 4.0f*ac;
+
+ if(x < w1 && y > 0)
+ ac = a[i - w]/6.0f,
+ a_w[i - w] -= ac, a_w1[i - w + 1] -= 2.0f*ac,
+ a0[i] += 4.0f*ac;
+
+ if(x > 0 && y < h1)
+ ac = a[i - 1]/6.0f,
+ a_1[i - 1] -= ac, a0[i] += 4.0f*ac;
+
+ if(x < w1 && y < h1)
+ a0[i] += 4.0f*a[i]/6.0f;
+ }
+ }
+ if(UseBlurForEdgeStop) delete[] a;
+
+ //Solve & return.
+ A->CreateIncompleteCholeskyFactorization(1); //Fill-in of 1 seems to work really good. More doesn't really help and less hurts (slightly).
+ if(!UseBlurForEdgeStop) memcpy(Blur, Source, n*sizeof(float));
+ SparseConjugateGradient(A->PassThroughVectorProduct, Source, n, false, Blur, 0.0f, (void *)A, Iterates, A->PassThroughCholeskyBackSolve);
+ A->KillIncompleteCholeskyFactorization();
+ return Blur;
+}
+
+float *EdgePreservingDecomposition::CreateIteratedBlur(float *Source, float Scale, float EdgeStopping, unsigned int Iterates, unsigned int Reweightings, float *Blur){
+ //Simpler outcome?
+ if(Reweightings == 0) return CreateBlur(Source, Scale, EdgeStopping, Iterates, Blur);
+
+ //Create a blur here, initialize.
+ if(Blur == NULL) Blur = new float[n];
+ memcpy(Blur, Source, n*sizeof(float));
+
+ //Iteratively improve the blur.
+ Reweightings++;
+ for(unsigned int i = 0; i != Reweightings; i++)
+ CreateBlur(Source, Scale, EdgeStopping, Iterates, Blur, true);
+
+ return Blur;
+}
+
+float *EdgePreservingDecomposition::CompressDynamicRange(float *Source, float Scale, float EdgeStopping, float CompressionExponent, float DetailBoost, unsigned int Iterates, unsigned int Reweightings, float *Compressed){
+ //Small number intended to prevent division by zero. This is different from the eps in CreateBlur.
+ const float eps = 0.0001f;
+
+ //We're working with luminance, which does better logarithmic.
+ unsigned int i;
+ for(i = 0; i != n; i++)
+ Source[i] = logf(Source[i] + eps);
+
+ //Blur. Also setup memory for Compressed (we can just use u since each element of u is used in one calculation).
+ float *u = CreateIteratedBlur(Source, Scale, EdgeStopping, Iterates, Reweightings);
+ if(Compressed == NULL) Compressed = u;
+
+ //Apply compression, detail boost, unlogging. Compression is done on the logged data and detail boost on unlogged.
+ for(i = 0; i != n; i++){
+ float ce = expf(Source[i] + u[i]*(CompressionExponent - 1.0f)) - eps;
+ float ue = expf(u[i]) - eps;
+ Source[i] = expf(Source[i]) - eps;
+ Compressed[i] = ce + DetailBoost*(Source[i] - ue);
+ }
+
+ if(Compressed != u) delete[] u;
+ return Compressed;
+}
+
diff --git a/rtengine/EdgePreservingDecomposition.h b/rtengine/EdgePreservingDecomposition.h
new file mode 100644
index 000000000..490783990
--- /dev/null
+++ b/rtengine/EdgePreservingDecomposition.h
@@ -0,0 +1,135 @@
+#pragma once
+/*
+The EdgePreservingDecomposition files contain standard C++ (standard except the first line) code for creating and, to a
+limited extent (create your own uses!), messing with multi scale edge preserving decompositions of a 32 bit single channel
+image. As a byproduct it contains a lot of linear algebra which can be useful for optimization problems that
+you want to solve in rectangles on rectangular grids.
+
+Anyway. Basically, this is an implementation of what's presented in the following papers:
+ Edge-Preserving Decompositions for Multi-Scale Tone and Detail Manipulation
+ An Iterative Solution Method for Linear Systems of Which the Coefficient Matrix is a Symetric M-Matrix
+ Color correction for tone mapping
+ Wikipedia, the free encyclopedia
+
+First one is most of what matters, next two are details, last everything else. I did a few things differently, especially:
+ Reformulated the minimization with finite elements instead of finite differences. This results in better conditioning,
+ slightly better accuracy (less artifacts), the possibility of a better picked edge stopping function, but more memory consumption.
+
+ A single rotationally invariant edge stopping function is used instead of two non-invariant ones.
+
+ Incomplete Cholseky factorization instead of Szeliski's LAHBF. Slower, but not subject to any patents.
+
+ For tone mapping, original images are decomposed instead of their logarithms, and just one decomposition is made;
+ I find that this way works plenty good (theirs isn't better or worse... just different) and is simpler.
+
+Written by ben_pcc in Portland, Oregon, USA. Some history:
+ Late April 2010, I develop interest in this stuff because photos of my ceramics lack local contrast.
+ Mid 2010, it works but is too slow to be useful.
+ Fall 2010, various unsuccessful attempts at speeding up are tried.
+ Early December 2010, I get off the path of least resistance and write a matrix storage class with incomplete Cholesky decomposition.
+ 31 December 2010, the FEM reformulation works very well.
+ 1 January 2011, I'm cleaning up this file and readying it for initial release.
+ 12 - 14 November 2011, further cleanup, improvements, bug fixes, integration into Raw Therapee.
+
+It's likely that I'll take apart and rerelease contents of this file (in the distant future) as most of it isn't edge preserving decomposition
+and rather supporting material. SparseConjugateGradient alone is a workhorse I and a few others have been exploiting for a few years.
+
+EdgePreservingDecomposition.h and EdgePreservingDecomposition.cpp are released under the following licence:
+ • It's free.
+ • You may not incorporate this code as part of proprietary or commercial software, but via freeware you may use its output for profit.
+ • You may modify and redistribute, but keep this big comment block intact and not for profit in any way unless I give specific permission.
+ • If you're unsure about anything else, treat as public domain.
+ • Don't be a dick.
+
+My email address is my screen name followed by @yahoo.com. I'm also known as ben_s or nonbasketless. Enjoy!
+*/
+
+
+
+#include
+#include
+#include
+
+
+//This is for solving big symmetric positive definite linear problems.
+float *SparseConjugateGradient(void Ax(float *Product, float *x, void *Pass), float *b, unsigned int n, bool OkToModify_b = true, float *x = NULL, float RMSResidual = 0.0f, void *Pass = NULL, unsigned int MaximumIterates = 0, void Preconditioner(float *Product, float *x, void *Pass) = NULL);
+
+//Storage and use class for symmetric matrices, the nonzero contents of which are confined to diagonals.
+class MultiDiagonalSymmetricMatrix{
+public:
+ MultiDiagonalSymmetricMatrix(unsigned int Dimension, unsigned int NumberOfDiagonalsInLowerTriangle);
+ ~MultiDiagonalSymmetricMatrix();
+
+ /* Storage of matrix data, and a function to create memory for Diagonals[index].
+ Here's the storage scheme, designed to be simple both on paper and in C++:
+
+ Let's say you have some diagonal. The StartRows is the row on which, at the left edge of the matrix, the diagonal "starts",
+ and StartRows must strictly increase with its index. The main diagonal for example has start row 0, its subdiagonal has 1, etc.
+ Then, Diagonal[j] is the matrix entry on the diagonal at column j. For efficiency, you're expected to learn this and fill in
+ public Diagonals manually. Symmetric matrices are represented by this class, and all symmetry is handled internally, you
+ only every worry or think about the lower trianglular (including main diagonal) part of the matrix.
+ */
+ float **Diagonals;
+ unsigned int *StartRows;
+ bool CreateDiagonal(unsigned int index, unsigned int StartRow);
+ unsigned int n, m; //The matrix is n x n, with m diagonals on the lower triangle. Don't change these. They should be private but aren't for convenience.
+ inline unsigned int DiagonalLength(unsigned int StartRow){ //Gives number of elements in a diagonal.
+ return n - StartRow;
+ };
+
+ //Not efficient, but you can use it if you're lazy, or for early tests. Returns false if the row + column falls on no loaded diagonal, true otherwise.
+ bool LazySetEntry(float value, unsigned int row, unsigned int column);
+
+ //Calculates the matrix-vector product of the matrix represented by this class onto the vector x.
+ void VectorProduct(float *Product, float *x);
+
+ //Given the start row, attempts to find the corresponding index, or -1 if the StartRow doesn't exist.
+ int FindIndex(unsigned int StartRow);
+
+ //This is the same as above, but designed to take this class as a pass through variable. By this way you can feed
+ //the meat of this class into an independent function, such as SparseConjugateGradient.
+ static void PassThroughVectorProduct(float *Product, float *x, void *Pass){
+ ((MultiDiagonalSymmetricMatrix *)Pass)->VectorProduct(Product, x);
+ };
+
+ /* CreateIncompleteCholeskyFactorization creates another matrix which is an incomplete (or complete if MaxFillAbove is big enough)
+ LDLt factorization of this matrix. Storage is like this: the first diagonal is the diagonal matrix D and the remaining diagonals
+ describe all of L except its main diagonal, which is a bunch of ones. Read up on the LDLt Cholesky factorization for what all this means.
+ Note that VectorProduct is nonsense. More useful to you is CholeskyBackSolve which fills x, where LDLt x = b. */
+ bool CreateIncompleteCholeskyFactorization(unsigned int MaxFillAbove = 0);
+ void KillIncompleteCholeskyFactorization(void);
+ void CholeskyBackSolve(float *x, float *b);
+ MultiDiagonalSymmetricMatrix *IncompleteCholeskyFactorization;
+
+ static void PassThroughCholeskyBackSolve(float *Product, float *x, void *Pass){
+ ((MultiDiagonalSymmetricMatrix *)Pass)->CholeskyBackSolve(Product, x);
+ };
+
+};
+
+class EdgePreservingDecomposition{
+public:
+ EdgePreservingDecomposition(unsigned int width, unsigned int height);
+ ~EdgePreservingDecomposition();
+
+ //Create an edge preserving blur of Source. Will create and return, or fill into Blur if not NULL. In place not ok.
+ //If UseBlurForEdgeStop is true, supplied not NULL Blur is used to calculate the edge stopping function instead of Source.
+ float *CreateBlur(float *Source, float Scale, float EdgeStopping, unsigned int Iterates, float *Blur = NULL, bool UseBlurForEdgeStop = false);
+
+ //Iterates CreateBlur such that the smoothness term approaches a specific norm via iteratively reweighted least squares. In place not ok.
+ float *CreateIteratedBlur(float *Source, float Scale, float EdgeStopping, unsigned int Iterates, unsigned int Reweightings, float *Blur = NULL);
+
+ /*Lowers global contrast while preserving or boosting local contrast. Can fill into Compressed. The smaller Compression
+ the more compression is applied, with Compression = 1 giving no effect and above 1 the opposite effect. You can totally
+ use Compression = 1 and play with DetailBoost for some really sweet unsharp masking. If working on luma/grey, consider giving it a logarithm.
+ In place calculation to save memory (Source == Compressed) is totally ok. Reweightings > 0 invokes CreateIteratedBlur instead of CreateBlur. */
+ float *CompressDynamicRange(float *Source, float Scale = 1.0f, float EdgeStopping = 1.4f, float CompressionExponent = 0.8f, float DetailBoost = 0.1f, unsigned int Iterates = 20, unsigned int Reweightings = 0, float *Compressed = NULL);
+
+private:
+ MultiDiagonalSymmetricMatrix *A; //The equations are simple enough to not mandate a matrix class, but fast solution NEEDS a complicated preconditioner.
+ unsigned int w, h, n;
+
+ //Convenient access to the data in A.
+ float *a0, *a_1, *a_w, *a_w_1, *a_w1;
+};
+
diff --git a/rtengine/dcrop.cc b/rtengine/dcrop.cc
index 932f5b920..60986517b 100644
--- a/rtengine/dcrop.cc
+++ b/rtengine/dcrop.cc
@@ -160,13 +160,18 @@ void Crop::update (int todo) {
}
// apply luminance operations
- if (todo & (M_LUMINANCE+M_COLOR)) {
- parent->ipf.luminanceCurve (laboCrop, labnCrop, parent->lumacurve);
- parent->ipf.chrominanceCurve (laboCrop, labnCrop, parent->chroma_acurve, parent->chroma_bcurve, parent->satcurve);
+ if (todo & (M_LUMINANCE+M_COLOR)) {
+ //I made a little change here. Rather than have luminanceCurve (and others) use in/out lab images, we can do more if we copy right here.
+ labnCrop->CopyFrom(laboCrop);
+
+ parent->ipf.EPDToneMap(labnCrop, 5, 1); //Go with much fewer than normal iterates for fast redisplay.
+
+ parent->ipf.luminanceCurve (labnCrop, labnCrop, parent->lumacurve);
+ parent->ipf.chrominanceCurve (labnCrop, labnCrop, parent->chroma_acurve, parent->chroma_bcurve, parent->satcurve);
//parent->ipf.colorCurve (labnCrop, labnCrop);
parent->ipf.vibrance (labnCrop);
- if (skip==1) {
+ if (skip==1) {
parent->ipf.impulsedenoise (labnCrop);
parent->ipf.defringe (labnCrop);
parent->ipf.dirpyrdenoise (labnCrop);
@@ -175,9 +180,8 @@ void Crop::update (int todo) {
//parent->ipf.MLmicrocontrast (labnCrop);
parent->ipf.sharpening (labnCrop, (float**)cbuffer);
parent->ipf.dirpyrequalizer (labnCrop);
- }
-
- }
+ }
+ }
// switch back to rgb
parent->ipf.lab2rgb (labnCrop, cropImg);
diff --git a/rtengine/improccoordinator.cc b/rtengine/improccoordinator.cc
index f944a65d8..9edca5e51 100644
--- a/rtengine/improccoordinator.cc
+++ b/rtengine/improccoordinator.cc
@@ -258,17 +258,21 @@ void ImProcCoordinator::updatePreviewImage (int todo, Crop* cropCall) {
params.labCurve.acurve, params.labCurve.bcurve, chroma_acurve, chroma_bcurve, satcurve, scale==1 ? 1 : 16);
}
- if (todo & (M_LUMINANCE+M_COLOR) ) {
- progress ("Applying Luminance Curve...",100*readyphase/numofphases);
+ if (todo & (M_LUMINANCE+M_COLOR) ) {
+ nprevl->CopyFrom(oprevl);
- ipf.luminanceCurve (oprevl, nprevl, lumacurve);
+ ipf.EPDToneMap(nprevl,0,scale);
- readyphase++;
+ progress ("Applying Luminance Curve...",100*readyphase/numofphases);
+
+ ipf.luminanceCurve (nprevl, nprevl, lumacurve);
+
+ readyphase++;
progress ("Applying Color Boost...",100*readyphase/numofphases);
- ipf.chrominanceCurve (oprevl, nprevl, chroma_acurve, chroma_bcurve, satcurve/*, params.labCurve.saturation*/);
- //ipf.colorCurve (nprevl, nprevl);
+ ipf.chrominanceCurve (nprevl, nprevl, chroma_acurve, chroma_bcurve, satcurve/*, params.labCurve.saturation*/);
+ //ipf.colorCurve (nprevl, nprevl);
ipf.vibrance(nprevl);
- readyphase++;
+ readyphase++;
if (scale==1) {
progress ("Denoising luminance impulse...",100*readyphase/numofphases);
ipf.impulsedenoise (nprevl);
diff --git a/rtengine/improcfun.cc b/rtengine/improcfun.cc
index 83da67776..d2b2c87d8 100644
--- a/rtengine/improcfun.cc
+++ b/rtengine/improcfun.cc
@@ -34,6 +34,7 @@
#include
#include
+
#ifdef _OPENMP
#include
#endif
@@ -615,6 +616,58 @@ void ImProcFunctions::colorCurve (LabImage* lold, LabImage* lnew) {
}
}
+
+//Map tones by way of edge preserving decomposition. Is this the right way to include source?
+#include "EdgePreservingDecomposition.cc"
+void ImProcFunctions::EPDToneMap(LabImage *lab, unsigned int Iterates, int skip){
+ //Hasten access to the parameters.
+ EPDParams *p = (EPDParams *)(¶ms->edgePreservingDecompositionUI);
+
+ //Enabled? Leave now if not.
+ if(!p->enabled) return;
+
+ //Pointers to whole data and size of it.
+ float *L = lab->L[0];
+ float *a = lab->a[0];
+ float *b = lab->b[0];
+ unsigned int i, N = lab->W*lab->H;
+
+ EdgePreservingDecomposition epd = EdgePreservingDecomposition(lab->W, lab->H);
+
+ //Due to the taking of logarithms, L must be nonnegative. Further, scale to 0 to 1 using nominal range of L, 0 to 15 bit.
+ float minL = FLT_MAX;
+ for(i = 0; i != N; i++)
+ if(L[i] < minL) minL = L[i];
+ if(minL > 0.0f) minL = 0.0f; //Disable the shift if there are no negative numbers. I wish there were just no negative numbers to begin with.
+
+ for(i = 0; i != N; i++)
+ L[i] = (L[i] - minL)/32767.0f;
+
+ //Some interpretations.
+ float Compression = expf(-p->Strength); //This modification turns numbers symmetric around 0 into exponents.
+ float DetailBoost = p->Strength;
+ if(p->Strength < 0.0f) DetailBoost = 0.0f; //Go with effect of exponent only if uncompressing.
+
+ //Auto select number of iterates. Note that p->EdgeStopping = 0 makes a Gaussian blur.
+ if(Iterates == 0) Iterates = (unsigned int)(p->EdgeStopping*15.0);
+
+/* Debuggery. Saves L for toying with outside of RT.
+char nm[64];
+sprintf(nm, "%ux%ufloat.bin", lab->W, lab->H);
+FILE *f = fopen(nm, "wb");
+fwrite(L, N, sizeof(float), f);
+fclose(f);*/
+
+ epd.CompressDynamicRange(L, (float)p->Scale/skip, (float)p->EdgeStopping, Compression, DetailBoost, Iterates, p->ReweightingIterates, L);
+
+ //Restore past range, also desaturate a bit per Mantiuk's Color correction for tone mapping.
+ float s = (1.0f + 38.7889f)*powf(Compression, 1.5856f)/(1.0f + 38.7889f*powf(Compression, 1.5856f));
+ for(i = 0; i != N; i++)
+ a[i] *= s,
+ b[i] *= s,
+ L[i] = L[i]*32767.0f + minL;
+}
+
void ImProcFunctions::getAutoExp (LUTu & histogram, int histcompr, double defgain, double clip, \
double& expcomp, int& bright, int& contr, int& black, int& hlcompr, int& hlcomprthresh) {
diff --git a/rtengine/improcfun.h b/rtengine/improcfun.h
index 41e5c2eaa..04deff004 100644
--- a/rtengine/improcfun.h
+++ b/rtengine/improcfun.h
@@ -131,6 +131,8 @@ class ImProcFunctions {
void dirpyrdenoise (LabImage* lab);//Emil's pyramid denoise
void dirpyrequalizer (LabImage* lab);//Emil's equalizer
+ void EPDToneMap(LabImage *lab, unsigned int Iterates = 0, int skip = 1);
+
procparams::DirPyrDenoiseParams dnparams;
void dirpyrLab_denoise(LabImage * src, LabImage * dst, const procparams::DirPyrDenoiseParams & dnparams );//Emil's directional pyramid denoise
void dirpyr (LabImage* data_fine, LabImage* data_coarse, int level, LUTf &rangefn_L, LUTf &rangefn_ab, \
diff --git a/rtengine/labimage.cc b/rtengine/labimage.cc
index dbdb96d34..94f46c432 100644
--- a/rtengine/labimage.cc
+++ b/rtengine/labimage.cc
@@ -1,4 +1,5 @@
#include
+#include
namespace rtengine {
LabImage::LabImage (int w, int h) : fromImage(false), W(w), H(h) {
@@ -29,4 +30,9 @@ LabImage::~LabImage () {
delete [] data;
}
}
+
+void LabImage::CopyFrom(LabImage *Img){
+ memcpy(data, Img->data, W*H*3*sizeof(float));
+}
+
}
diff --git a/rtengine/labimage.h b/rtengine/labimage.h
index aee74ce46..4fc5e5b50 100644
--- a/rtengine/labimage.h
+++ b/rtengine/labimage.h
@@ -24,17 +24,22 @@
namespace rtengine {
class LabImage {
- private:
- bool fromImage;
- float * data;
- public:
- int W, H;
- float** L;
- float** a;
- float** b;
+private:
+ bool fromImage;
+ float * data;
- LabImage (int w, int h);
- ~LabImage ();
+public:
+ int W, H;
+ float** L;
+ float** a;
+ float** b;
+
+ LabImage (int w, int h);
+ ~LabImage ();
+
+ //Copies image data in Img into this instance.
+ void CopyFrom(LabImage *Img);
};
+
}
#endif
diff --git a/rtengine/procevents.h b/rtengine/procevents.h
index 4bcf70e76..838af9b99 100644
--- a/rtengine/procevents.h
+++ b/rtengine/procevents.h
@@ -179,7 +179,12 @@ enum ProcEvent {
EvVibranceAvoidColorShift=154,
EvVibrancePastSatTog=155,
EvVibrancePastSatThreshold=156,
- NUMOFEVENTS=157
+ EvEPDStrength=157,
+ EvEPDEdgeStopping=158,
+ EvEPDScale=159,
+ EvEPDReweightingIterates=160,
+ EvEPDEnabled=161,
+ NUMOFEVENTS=162
};
}
#endif
diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc
index 1401aed2f..90c4b1604 100644
--- a/rtengine/procparams.cc
+++ b/rtengine/procparams.cc
@@ -161,7 +161,13 @@ void ProcParams::setDefaults () {
dirpyrDenoise.lumcurve.push_back (DCT_Linear);
dirpyrDenoise.chromcurve.clear ();
dirpyrDenoise.chromcurve.push_back (DCT_Linear);
-
+
+ edgePreservingDecompositionUI.enabled = false;
+ edgePreservingDecompositionUI.Strength = 0.25;
+ edgePreservingDecompositionUI.EdgeStopping = 1.4;
+ edgePreservingDecompositionUI.Scale = 1.0;
+ edgePreservingDecompositionUI.ReweightingIterates = 0;
+
sh.enabled = false;
sh.hq = false;
sh.highlights = 0;
@@ -407,6 +413,13 @@ int ProcParams::save (Glib::ustring fname, Glib::ustring fname2) const {
keyFile.set_double_list("Directional Pyramid Denoising", "LumCurve", lumcurve);
keyFile.set_double_list("Directional Pyramid Denoising", "ChromCurve", chromcurve);
+ //Save edgePreservingDecompositionUI.
+ keyFile.set_boolean ("EPD", "Enabled", edgePreservingDecompositionUI.enabled);
+ keyFile.set_double ("EPD", "Strength", edgePreservingDecompositionUI.Strength);
+ keyFile.set_double ("EPD", "EdgeStopping", edgePreservingDecompositionUI.EdgeStopping);
+ keyFile.set_double ("EPD", "Scale", edgePreservingDecompositionUI.Scale);
+ keyFile.set_integer ("EPD", "ReweightingIterates", edgePreservingDecompositionUI.ReweightingIterates);
+
// save lumaDenoise
keyFile.set_boolean ("Luminance Denoising", "Enabled", lumaDenoise.enabled);
keyFile.set_double ("Luminance Denoising", "Radius", lumaDenoise.radius);
@@ -746,6 +759,15 @@ if (keyFile.has_group ("Directional Pyramid Denoising")) {
if (keyFile.has_key ("Directional Pyramid Denoising", "LumCurve")) dirpyrDenoise.lumcurve = keyFile.get_double_list ("Directional Pyramid Denoising", "LumCurve");
if (keyFile.has_key ("Directional Pyramid Denoising", "ChromCurve")) dirpyrDenoise.chromcurve = keyFile.get_double_list ("Directional Pyramid Denoising", "ChromCurve");
}
+
+//Load EPD.
+if (keyFile.has_group ("EPD")) {
+ if(keyFile.has_key("EPD", "Enabled")) edgePreservingDecompositionUI.enabled = keyFile.get_boolean ("EPD", "Enabled");
+ if(keyFile.has_key("EPD", "Strength")) edgePreservingDecompositionUI.Strength = keyFile.get_double ("EPD", "Strength");
+ if(keyFile.has_key("EPD", "EdgeStopping")) edgePreservingDecompositionUI.EdgeStopping = keyFile.get_double ("EPD", "EdgeStopping");
+ if(keyFile.has_key("EPD", "Scale")) edgePreservingDecompositionUI.Scale = keyFile.get_double ("EPD", "Scale");
+ if(keyFile.has_key("EPD", "ReweightingIterates")) edgePreservingDecompositionUI.ReweightingIterates = keyFile.get_integer ("EPD", "ReweightingIterates");
+}
// load lumaDenoise
if (keyFile.has_group ("Luminance Denoising")) {
@@ -1033,6 +1055,11 @@ bool ProcParams::operator== (const ProcParams& other) {
&& dirpyrDenoise.gamma == other.dirpyrDenoise.gamma
&& dirpyrDenoise.lumcurve == other.dirpyrDenoise.lumcurve
&& dirpyrDenoise.chromcurve == other.dirpyrDenoise.chromcurve
+ && edgePreservingDecompositionUI.enabled == other.edgePreservingDecompositionUI.enabled
+ && edgePreservingDecompositionUI.Strength == other.edgePreservingDecompositionUI.Strength
+ && edgePreservingDecompositionUI.EdgeStopping == other.edgePreservingDecompositionUI.EdgeStopping
+ && edgePreservingDecompositionUI.Scale == other.edgePreservingDecompositionUI.Scale
+ && edgePreservingDecompositionUI.ReweightingIterates == other.edgePreservingDecompositionUI.ReweightingIterates
&& defringe.enabled == other.defringe.enabled
&& defringe.radius == other.defringe.radius
&& defringe.threshold == other.defringe.threshold
diff --git a/rtengine/procparams.h b/rtengine/procparams.h
index 7a6f5dba9..528a91324 100644
--- a/rtengine/procparams.h
+++ b/rtengine/procparams.h
@@ -206,6 +206,16 @@ class ColorDenoiseParams {
std::vector chromcurve;
};
+//EPD related parameters.
+class EPDParams{
+public:
+ bool enabled;
+ double Strength;
+ double EdgeStopping;
+ double Scale;
+ int ReweightingIterates;
+};
+
/**
* Parameters of the shadow/highlight enhancement
*/
@@ -409,6 +419,8 @@ class HSVEqualizerParams {
std::vector vcurve;
};
+
+
/**
* Parameters for RAW demosaicing
*/
@@ -476,6 +488,7 @@ class ProcParams {
DefringeParams defringe; ///< Defringing parameters
ImpulseDenoiseParams impulseDenoise; ///< Impulse denoising parameters
DirPyrDenoiseParams dirpyrDenoise; ///< Directional Pyramid denoising parameters
+ EPDParams edgePreservingDecompositionUI;
SHParams sh; ///< Shadow/highlight enhancement parameters
CropParams crop; ///< Crop parameters
CoarseTransformParams coarse; ///< Coarse transformation (90, 180, 270 deg rotation, h/v flipping) parameters
diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc
index f6057eada..05b020e93 100644
--- a/rtengine/refreshmap.cc
+++ b/rtengine/refreshmap.cc
@@ -176,7 +176,12 @@ RGBCURVE, // EvVibranceSaturated
RGBCURVE, // EvVibranceProtectSkins
RGBCURVE, // EvVibranceAvoidColorShift
RGBCURVE, // EvVibrancePastSatTog
-RGBCURVE // EvVibrancePastSatThreshold
+RGBCURVE, // EvVibrancePastSatThreshold
+SHARPENING, // EvEPDStrength
+SHARPENING, // EvEPDEdgeStopping
+SHARPENING, // EvEPDScale
+SHARPENING, // EvEPDReweightingIterates
+SHARPENING // EvEPDEnabled
};
diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc
index 4fc7cafe7..54a95c376 100644
--- a/rtengine/rtthumbnail.cc
+++ b/rtengine/rtthumbnail.cc
@@ -757,6 +757,8 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rhei
hist16[CLIP((int)((labView->L[i][j])))]++;
// luminance processing
+ ipf.EPDToneMap(labView,0,6);
+
CurveFactory::complexLCurve (params.labCurve.brightness, params.labCurve.contrast, params.labCurve.lcurve,
hist16, hist16, curve, dummy, 16);
CurveFactory::complexsgnCurve (params.labCurve.saturation, params.labCurve.enable_saturationlimiter, params.labCurve.saturationlimit, \
diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc
index 8e868df33..ce629952c 100644
--- a/rtengine/simpleprocess.cc
+++ b/rtengine/simpleprocess.cc
@@ -188,6 +188,8 @@ IImage16* processImage (ProcessingJob* pjob, int& errorCode, ProgressListener* p
// luminance processing
+ ipf.EPDToneMap(labView);
+
CurveFactory::complexLCurve (params.labCurve.brightness, params.labCurve.contrast, params.labCurve.lcurve, hist16, hist16, curve, dummy, 1);
CurveFactory::complexsgnCurve (params.labCurve.saturation, params.labCurve.enable_saturationlimiter, params.labCurve.saturationlimit, \
diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt
index e5dba9632..42f70afa0 100644
--- a/rtgui/CMakeLists.txt
+++ b/rtgui/CMakeLists.txt
@@ -7,7 +7,7 @@ set (BASESOURCEFILES
clipboard.cc thumbimageupdater.cc bqentryupdater.cc lensgeom.cc
coarsepanel.cc cacorrection.cc hlrec.cc chmixer.cc
resize.cc icmpanel.cc crop.cc shadowshighlights.cc
- impulsedenoise.cc dirpyrdenoise.cc
+ impulsedenoise.cc dirpyrdenoise.cc epd.cc
exifpanel.cc toolpanel.cc
sharpening.cc vibrance.cc
whitebalance.cc vignetting.cc rotate.cc distortion.cc
diff --git a/rtgui/epd.cc b/rtgui/epd.cc
new file mode 100644
index 000000000..1c9586667
--- /dev/null
+++ b/rtgui/epd.cc
@@ -0,0 +1,163 @@
+/*
+ * 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
+#include
+#include
+
+using namespace rtengine;
+using namespace rtengine::procparams;
+
+EdgePreservingDecompositionUI::EdgePreservingDecompositionUI () : Gtk::VBox(), FoldableToolPanel(this){
+ enabled = Gtk::manage (new Gtk::CheckButton (M("GENERAL_ENABLED")));
+ enabled->set_active (false);
+ enabled->show ();
+ pack_start (*enabled);
+
+ Gtk::HSeparator *hsep1 = Gtk::manage (new Gtk::HSeparator());
+ hsep1->show ();
+ pack_start (*hsep1);
+
+ enaConn = enabled->signal_toggled().connect( sigc::mem_fun(*this, &EdgePreservingDecompositionUI::enabledChanged) );
+
+ Strength = Gtk::manage(new Adjuster (M("TP_EPD_STRENGTH"), -2.0, 2.0, 0.01, 0.25));
+ EdgeStopping = Gtk::manage(new Adjuster (M("TP_EPD_EDGESTOPPING"), 0.1, 4.0, 0.01, 1.4));
+ Scale = Gtk::manage(new Adjuster (M("TP_EPD_SCALE"), 0.1, 10.0, 0.01, 1.0));
+ ReweightingIterates = Gtk::manage(new Adjuster (M("TP_EPD_REWEIGHTINGITERATES"), 0, 9, 1, 0));
+
+ Strength->setAdjusterListener(this);
+ EdgeStopping->setAdjusterListener(this);
+ Scale->setAdjusterListener(this);
+ ReweightingIterates->setAdjusterListener(this);
+
+ Strength->show();
+ EdgeStopping->show();
+ Scale->show();
+ ReweightingIterates->show();
+
+ pack_start(*Strength);
+ pack_start(*EdgeStopping);
+ pack_start(*Scale);
+ pack_start(*ReweightingIterates);
+}
+
+void EdgePreservingDecompositionUI::read(const ProcParams *pp, const ParamsEdited *pedited){
+ disableListener();
+
+ if(pedited){
+ Strength->setEditedState(pedited->edgePreservingDecompositionUI.Strength ? Edited : UnEdited);
+ EdgeStopping->setEditedState(pedited->edgePreservingDecompositionUI.EdgeStopping ? Edited : UnEdited);
+ Scale->setEditedState(pedited->edgePreservingDecompositionUI.Scale ? Edited : UnEdited);
+ ReweightingIterates->setEditedState(pedited->edgePreservingDecompositionUI.ReweightingIterates ? Edited : UnEdited);
+
+ enabled->set_inconsistent(!pedited->edgePreservingDecompositionUI.enabled);
+ }
+
+ enaConn.block(true);
+ enabled->set_active(pp->edgePreservingDecompositionUI.enabled);
+ enaConn.block (false);
+
+ lastEnabled = pp->edgePreservingDecompositionUI.enabled;
+
+ Strength->setValue(pp->edgePreservingDecompositionUI.Strength);
+ EdgeStopping->setValue(pp->edgePreservingDecompositionUI.EdgeStopping);
+ Scale->setValue(pp->edgePreservingDecompositionUI.Scale);
+ ReweightingIterates->setValue(pp->edgePreservingDecompositionUI.ReweightingIterates);
+
+ enableListener();
+}
+
+void EdgePreservingDecompositionUI::write(ProcParams *pp, ParamsEdited *pedited){
+ pp->edgePreservingDecompositionUI.Strength = Strength->getValue();
+ pp->edgePreservingDecompositionUI.EdgeStopping = EdgeStopping->getValue();
+ pp->edgePreservingDecompositionUI.Scale = Scale->getValue();
+ pp->edgePreservingDecompositionUI.ReweightingIterates = ReweightingIterates->getValue();
+ pp->edgePreservingDecompositionUI.enabled = enabled->get_active();
+
+ if(pedited){
+ pedited->edgePreservingDecompositionUI.Strength = Strength->getEditedState();
+ pedited->edgePreservingDecompositionUI.EdgeStopping = EdgeStopping->getEditedState();
+ pedited->edgePreservingDecompositionUI.Scale = Scale->getEditedState();
+ pedited->edgePreservingDecompositionUI.ReweightingIterates = ReweightingIterates->getEditedState();
+ pedited->edgePreservingDecompositionUI.enabled = !enabled->get_inconsistent();
+ }
+}
+
+void EdgePreservingDecompositionUI::setDefaults(const ProcParams *defParams, const ParamsEdited *pedited){
+ Strength->setDefault(defParams->edgePreservingDecompositionUI.Strength);
+ EdgeStopping->setDefault(defParams->edgePreservingDecompositionUI.EdgeStopping);
+ Scale->setDefault(defParams->edgePreservingDecompositionUI.Scale);
+ ReweightingIterates->setDefault(defParams->edgePreservingDecompositionUI.ReweightingIterates);
+
+ if(pedited){
+ Strength->setDefaultEditedState(pedited->edgePreservingDecompositionUI.Strength ? Edited : UnEdited);
+ EdgeStopping->setDefaultEditedState(pedited->edgePreservingDecompositionUI.EdgeStopping ? Edited : UnEdited);
+ Scale->setDefaultEditedState(pedited->edgePreservingDecompositionUI.Scale ? Edited : UnEdited);
+ ReweightingIterates->setDefaultEditedState(pedited->edgePreservingDecompositionUI.ReweightingIterates ? Edited : UnEdited);
+ }else{
+ Strength->setDefaultEditedState(Irrelevant);
+ EdgeStopping->setDefaultEditedState(Irrelevant);
+ Scale->setDefaultEditedState(Irrelevant);
+ ReweightingIterates->setDefaultEditedState(Irrelevant);
+ }
+}
+
+void EdgePreservingDecompositionUI::adjusterChanged(Adjuster* a, double newval){
+ if(listener && enabled->get_active()){
+ if(a == Strength)
+ listener->panelChanged(EvEPDStrength, Glib::ustring::format(std::setw(2), std::fixed, std::setprecision(2), a->getValue()));
+ else if(a == EdgeStopping)
+ listener->panelChanged(EvEPDEdgeStopping, Glib::ustring::format(std::setw(2), std::fixed, std::setprecision(2), a->getValue()));
+ else if(a == Scale)
+ listener->panelChanged(EvEPDScale, Glib::ustring::format(std::setw(2), std::fixed, std::setprecision(2), a->getValue()));
+ else if(a == ReweightingIterates)
+ listener->panelChanged(EvEPDReweightingIterates, Glib::ustring::format((int)a->getValue()));
+ }
+}
+
+void EdgePreservingDecompositionUI::enabledChanged(){
+ if(batchMode){
+ if(enabled->get_inconsistent()){
+ enabled->set_inconsistent (false);
+ enaConn.block (true);
+ enabled->set_active (false);
+ enaConn.block (false);
+ }
+ else if(lastEnabled)
+ enabled->set_inconsistent (true);
+
+ lastEnabled = enabled->get_active ();
+ }
+
+ if(listener){
+ if(enabled->get_active ())
+ listener->panelChanged (EvEPDEnabled, M("GENERAL_ENABLED"));
+ else
+ listener->panelChanged (EvEPDEnabled, M("GENERAL_DISABLED"));
+ }
+}
+
+void EdgePreservingDecompositionUI::setBatchMode(bool batchMode){
+ ToolPanel::setBatchMode(batchMode);
+
+ Strength->showEditedCB();
+ EdgeStopping->showEditedCB();
+ Scale->showEditedCB();
+ ReweightingIterates->showEditedCB();
+}
+
diff --git a/rtgui/epd.h b/rtgui/epd.h
new file mode 100644
index 000000000..439cbff46
--- /dev/null
+++ b/rtgui/epd.h
@@ -0,0 +1,50 @@
+/*
+ * 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 _EPD_H_
+#define _EPD_H_
+
+#include
+#include
+#include
+
+class EdgePreservingDecompositionUI : public Gtk::VBox, public AdjusterListener, public FoldableToolPanel {
+protected:
+ Adjuster *Strength;
+ Adjuster *EdgeStopping;
+ Adjuster *Scale;
+ Adjuster *ReweightingIterates;
+
+ Gtk::CheckButton* enabled;
+ bool lastEnabled;
+ sigc::connection enaConn;
+
+public:
+
+ EdgePreservingDecompositionUI();
+
+ void read (const rtengine::procparams::ProcParams* pp, const ParamsEdited* pedited=NULL);
+ void write (rtengine::procparams::ProcParams* pp, ParamsEdited* pedited=NULL);
+ void setDefaults (const rtengine::procparams::ProcParams* defParams, const ParamsEdited* pedited=NULL);
+ void setBatchMode (bool batchMode);
+
+ void adjusterChanged (Adjuster* a, double newval);
+ void enabledChanged ();
+};
+
+#endif
diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc
index d99ae2307..29cc1d41b 100644
--- a/rtgui/paramsedited.cc
+++ b/rtgui/paramsedited.cc
@@ -100,6 +100,11 @@ void ParamsEdited::set (bool v) {
dirpyrDenoise.luma = v;
dirpyrDenoise.chroma = v;
dirpyrDenoise.gamma = v;
+ edgePreservingDecompositionUI.enabled = v;
+ edgePreservingDecompositionUI.Strength = v;
+ edgePreservingDecompositionUI.EdgeStopping = v;
+ edgePreservingDecompositionUI.Scale = v;
+ edgePreservingDecompositionUI.ReweightingIterates = v;
sh.enabled = v;
sh.hq = v;
sh.highlights = v;
@@ -282,6 +287,12 @@ void ParamsEdited::initFrom (const std::vector
dirpyrDenoise.chroma = dirpyrDenoise.chroma && p.dirpyrDenoise.chroma == other.dirpyrDenoise.chroma;
dirpyrDenoise.gamma = dirpyrDenoise.gamma && p.dirpyrDenoise.gamma == other.dirpyrDenoise.gamma;
+ edgePreservingDecompositionUI.enabled = edgePreservingDecompositionUI.enabled && p.edgePreservingDecompositionUI.enabled == other.edgePreservingDecompositionUI.enabled;
+ edgePreservingDecompositionUI.Strength = edgePreservingDecompositionUI.Strength && p.edgePreservingDecompositionUI.Strength == other.edgePreservingDecompositionUI.Strength;
+ edgePreservingDecompositionUI.EdgeStopping = edgePreservingDecompositionUI.EdgeStopping && p.edgePreservingDecompositionUI.EdgeStopping == other.edgePreservingDecompositionUI.EdgeStopping;
+ edgePreservingDecompositionUI.Scale = edgePreservingDecompositionUI.Scale && p.edgePreservingDecompositionUI.Scale == other.edgePreservingDecompositionUI.Scale;
+ edgePreservingDecompositionUI.ReweightingIterates = edgePreservingDecompositionUI.ReweightingIterates && p.edgePreservingDecompositionUI.ReweightingIterates == other.edgePreservingDecompositionUI.ReweightingIterates;
+
sh.enabled = sh.enabled && p.sh.enabled == other.sh.enabled;
sh.hq = sh.hq && p.sh.hq == other.sh.hq;
sh.highlights = sh.highlights && p.sh.highlights == other.sh.highlights;
@@ -463,6 +474,12 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten
if (dirpyrDenoise.chroma) toEdit.dirpyrDenoise.chroma = dontforceSet && options.baBehav[ADDSET_DIRPYRDN_CHLUM] ? toEdit.dirpyrDenoise.chroma + mods.dirpyrDenoise.chroma : mods.dirpyrDenoise.chroma;
if (dirpyrDenoise.gamma) toEdit.dirpyrDenoise.gamma = dontforceSet && options.baBehav[ADDSET_DIRPYRDN_GAMMA] ? toEdit.dirpyrDenoise.gamma + mods.dirpyrDenoise.gamma : mods.dirpyrDenoise.gamma;
+ if (edgePreservingDecompositionUI.enabled) toEdit.edgePreservingDecompositionUI.enabled = mods.edgePreservingDecompositionUI.enabled;
+ if (edgePreservingDecompositionUI.Strength) toEdit.edgePreservingDecompositionUI.Strength = mods.edgePreservingDecompositionUI.Strength;
+ if (edgePreservingDecompositionUI.EdgeStopping) toEdit.edgePreservingDecompositionUI.EdgeStopping = mods.edgePreservingDecompositionUI.EdgeStopping;
+ if (edgePreservingDecompositionUI.Scale) toEdit.edgePreservingDecompositionUI.Scale = mods.edgePreservingDecompositionUI.Scale;
+ if (edgePreservingDecompositionUI.ReweightingIterates) toEdit.edgePreservingDecompositionUI.ReweightingIterates = mods.edgePreservingDecompositionUI.ReweightingIterates;
+
if (sh.enabled) toEdit.sh.enabled = mods.sh.enabled;
if (sh.hq) toEdit.sh.hq = mods.sh.hq;
if (sh.highlights) toEdit.sh.highlights = dontforceSet && options.baBehav[ADDSET_SH_HIGHLIGHTS] ? toEdit.sh.highlights + mods.sh.highlights : mods.sh.highlights;
diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h
index c12f49c47..c436c9e82 100644
--- a/rtgui/paramsedited.h
+++ b/rtgui/paramsedited.h
@@ -167,6 +167,16 @@ public:
bool gamma;
};
+class EPDParamsEdited{
+public:
+ bool enabled;
+ bool Strength;
+ bool EdgeStopping;
+ bool Scale;
+ bool ReweightingIterates;
+};
+
+
class SHParamsEdited {
public:
@@ -362,6 +372,7 @@ class ParamsEdited {
ColorDenoiseParamsEdited colorDenoise;
DefringeParamsEdited defringe;
DirPyrDenoiseParamsEdited dirpyrDenoise;
+ EPDParamsEdited edgePreservingDecompositionUI;
ImpulseDenoiseParamsEdited impulseDenoise;
SHParamsEdited sh;
CropParamsEdited crop;
diff --git a/rtgui/partialpastedlg.cc b/rtgui/partialpastedlg.cc
index 76e178076..9f9dce5c3 100644
--- a/rtgui/partialpastedlg.cc
+++ b/rtgui/partialpastedlg.cc
@@ -56,6 +56,7 @@ PartialPasteDlg::PartialPasteDlg () {
impden = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_IMPULSEDENOISE")));
dirpyreq = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DIRPYREQUALIZER")));
defringe = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_DEFRINGE")));
+ edgePreservingDecompositionUI = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_EPD")));
// options in color:
vibrance = Gtk::manage (new Gtk::CheckButton (M("PARTIALPASTE_VIBRANCE")));
@@ -130,6 +131,7 @@ PartialPasteDlg::PartialPasteDlg () {
vboxes[1]->pack_start (*impden, Gtk::PACK_SHRINK, 2);
vboxes[1]->pack_start (*dirpyrden, Gtk::PACK_SHRINK, 2);
vboxes[1]->pack_start (*defringe, Gtk::PACK_SHRINK, 2);
+ vboxes[1]->pack_start (*edgePreservingDecompositionUI, Gtk::PACK_SHRINK, 2);
vboxes[1]->pack_start (*dirpyreq, Gtk::PACK_SHRINK, 2);
//vboxes[1]->pack_start (*waveq, Gtk::PACK_SHRINK, 2);
@@ -245,6 +247,7 @@ PartialPasteDlg::PartialPasteDlg () {
dirpyreqConn = dirpyreq->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true));
//waveqConn = waveq->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true));
defringeConn = defringe->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true));
+ edgePreservingDecompositionUIConn = edgePreservingDecompositionUI->signal_toggled().connect (sigc::bind (sigc::mem_fun(*detail, &Gtk::CheckButton::set_inconsistent), true));
vibranceConn = vibrance->signal_toggled().connect (sigc::bind (sigc::mem_fun(*color, &Gtk::CheckButton::set_inconsistent), true));
chmixerConn = chmixer->signal_toggled().connect (sigc::bind (sigc::mem_fun(*color, &Gtk::CheckButton::set_inconsistent), true));
@@ -433,6 +436,7 @@ void PartialPasteDlg::detailToggled () {
impdenConn.block (true);
dirpyrdenConn.block (true);
defringeConn.block (true);
+ edgePreservingDecompositionUIConn.block(true);
dirpyreqConn.block (true);
//waveqConn.block (true);
@@ -453,6 +457,7 @@ void PartialPasteDlg::detailToggled () {
impdenConn.block (false);
dirpyrdenConn.block (false);
defringeConn.block (false);
+ edgePreservingDecompositionUIConn.block (false);
dirpyreqConn.block (false);
//waveqConn.block (false);
}
diff --git a/rtgui/partialpastedlg.h b/rtgui/partialpastedlg.h
index b2e30f7e3..87b8d1b7e 100644
--- a/rtgui/partialpastedlg.h
+++ b/rtgui/partialpastedlg.h
@@ -52,6 +52,7 @@ class PartialPasteDlg : public Gtk::Dialog {
Gtk::CheckButton* waveq;
Gtk::CheckButton* dirpyrden;
Gtk::CheckButton* defringe;
+ Gtk::CheckButton* edgePreservingDecompositionUI;
Gtk::CheckButton* dirpyreq;
// options in color:
@@ -105,7 +106,7 @@ class PartialPasteDlg : public Gtk::Dialog {
sigc::connection everythingConn, basicConn, detailConn, colorConn, lensConn, compositionConn, metaicmConn, rawConn;;
sigc::connection wbConn, exposureConn, hlrecConn, shConn, labcurveConn;
- sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, waveqConn, defringeConn, dirpyreqConn;
+ sigc::connection sharpenConn, gradsharpenConn, microcontrastConn, impdenConn, dirpyrdenConn, waveqConn, defringeConn, edgePreservingDecompositionUIConn, dirpyreqConn;
sigc::connection vibranceConn, chmixerConn, hsveqConn;
sigc::connection distortionConn, cacorrConn, vignettingConn;
sigc::connection coarserotConn, finerotConn, cropConn, resizeConn, perspectiveConn, commonTransConn;
diff --git a/rtgui/toolpanelcoord.cc b/rtgui/toolpanelcoord.cc
index 7cecc0b4f..a5279fe1f 100644
--- a/rtgui/toolpanelcoord.cc
+++ b/rtgui/toolpanelcoord.cc
@@ -45,6 +45,7 @@ ToolPanelCoordinator::ToolPanelCoordinator () : ipc(NULL) {
impulsedenoise = Gtk::manage (new ImpulseDenoise ());
defringe = Gtk::manage (new Defringe ());
dirpyrdenoise = Gtk::manage (new DirPyrDenoise ());
+ edgePreservingDecompositionUI = Gtk::manage (new EdgePreservingDecompositionUI ());
sharpening = Gtk::manage (new Sharpening ());
sharpenEdge = Gtk::manage (new SharpenEdge ());
sharpenMicro = Gtk::manage (new SharpenMicro ());
@@ -83,6 +84,7 @@ ToolPanelCoordinator::ToolPanelCoordinator () : ipc(NULL) {
addPanel (detailsPanel, sharpenEdge, M("TP_SHARPENEDGE_LABEL")); toolPanels.push_back (sharpenEdge);
addPanel (detailsPanel, sharpenMicro, M("TP_SHARPENMICRO_LABEL")); toolPanels.push_back (sharpenMicro);
addPanel (colorPanel, hsvequalizer, M("TP_HSVEQUALIZER_LABEL")); toolPanels.push_back (hsvequalizer);
+ addPanel (exposurePanel, edgePreservingDecompositionUI, M("TP_EPD_LABEL")); toolPanels.push_back (edgePreservingDecompositionUI);
addPanel (exposurePanel, lcurve, M("TP_LABCURVE_LABEL")); toolPanels.push_back (lcurve);
addPanel (detailsPanel, impulsedenoise, M("TP_IMPULSEDENOISE_LABEL")); toolPanels.push_back (impulsedenoise);
addPanel (detailsPanel, dirpyrdenoise, M("TP_DIRPYRDENOISE_LABEL")); toolPanels.push_back (dirpyrdenoise);
diff --git a/rtgui/toolpanelcoord.h b/rtgui/toolpanelcoord.h
index e5ff5f23b..348f4dadd 100644
--- a/rtgui/toolpanelcoord.h
+++ b/rtgui/toolpanelcoord.h
@@ -34,6 +34,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -95,6 +96,7 @@ class ToolPanelCoordinator : public ToolPanelListener,
Defringe* defringe;
ImpulseDenoise* impulsedenoise;
DirPyrDenoise* dirpyrdenoise;
+ EdgePreservingDecompositionUI *edgePreservingDecompositionUI;
Sharpening* sharpening;
SharpenEdge* sharpenEdge;
SharpenMicro* sharpenMicro;