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;