Edge-preserving decomposition and tone-mapping tool. Committed on behalf of Ben_pcc.
This commit is contained in:
parent
ba7dddd663
commit
6595bf18d1
45
WindowsEnvironmentSetup.html
Normal file
45
WindowsEnvironmentSetup.html
Normal file
@ -0,0 +1,45 @@
|
||||
<H1>Building Raw Therapee in Windows 64</H1>
|
||||
12 November 2011<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
This short guide outlines setting up a development environment for building RawTherapee (64 bit) in Windows.<BR>
|
||||
Apology and warning: the procedure is convoluted. Prepare for frustration if you deviate even one bit. Sorry.<BR>
|
||||
<BR>
|
||||
<B>Step 0:</B><BR>
|
||||
Use <A HREF=http://tortoisehg.bitbucket.org/>TortoiseHg</A> 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.<BR>
|
||||
<BR>
|
||||
<B>Step 1:</B><BR>
|
||||
Download the latest <A HREF=http://cmake.org/cmake/resources/software.html>CMake</A>. Install it into C:\CMake.<BR>
|
||||
<BR>
|
||||
<B>Step 2:</B><BR>
|
||||
Download <A HREF=http://ftp.gnome.org/pub/GNOME/binaries/win64/gtkmm/2.22/gtkmm-win64-devel-2.22.0-2.exe>a rather specific version of gtkmm64</A>. Install it into C:\gtkmm64.<BR>
|
||||
<BR>
|
||||
<B>Step 3:</B><BR>
|
||||
Download <A HREF=http://sourceforge.net/projects/tdm-gcc/files/TDM-GCC%20Installer/Previous/1.1006.0/tdm64-gcc-4.5.2.exe/download>a rather specific build of MinGW</A>. When installing, uncheck "Check for updated files...", check "Experimental (32 and 64 bit)", check the "openmp" component under Components->gcc, install into C:\MinGW64.<BR>
|
||||
<BR>
|
||||
<B>Step 4:</B><BR>
|
||||
Download the latest <A HREF=http://www.visualbakery.com/rawtherapee/Downloads.aspx>Precompiled package for 64bit Windows</A>. Unzip the file contents (already organized into several subdirectories) into C:\MinGW64.<BR>
|
||||
<BR>
|
||||
<B>Step 5:</B><BR>
|
||||
Download <A HREF=http://sourceforge.net/projects/mingw/files/MSYS/Base/msys-core/msys-1.0.11/MSYS-1.0.11.exe/download>a rather specific build</A> of MSYS. Install to C:\msys. At the end it asks for your MinGW directory, <u>make sure</u> you give it C:/MinGW64. That's a forward slash, and it matters.<BR>
|
||||
<BR>
|
||||
<B>Step 6:</B><BR>
|
||||
Create a file named build.bat in your raw therapee source directory with the following content:<BR>
|
||||
<BR>
|
||||
set GTKMM_BASEPATH=C:\gtkmm64<BR>
|
||||
set GTKMM64_BASEPATH=C:\gtkmm64<BR>
|
||||
set MINGW_BASEPATH=C:\MinGW64<BR>
|
||||
set PATH=%PATH%;C:\gtkmm64\bin;C:\MinGW64\bin;C:\CMake\bin<BR>
|
||||
set PKG_CONFIG_PATH=C:\MinGW64\lib\pkgconfig;c:\gtkmm64\lib\pkgconfig<BR>
|
||||
<BR>
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" -DPROC_TARGET_NUMBER:STRING=2<BR>
|
||||
mingw32-make -j2 install<BR>
|
||||
pause<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
Running the batch file above after completing all steps properly should slowly but flawlessly build Raw Therapee.<BR>
|
||||
<B>If it doesn't work, <U>please</U> tell us about it at the <A HREF=http://www.rawtherapee.com/forum/viewforum.php?f=10>Raw Therapee forum</A>. Maybe this document needs updating.</B><BR>
|
||||
Do not rest until it builds. A complicated build sucks, but out of date or inaccurate build documentation is unacceptable.<BR>
|
||||
<BR>
|
||||
<BR>
|
@ -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
|
||||
|
461
rtengine/EdgePreservingDecomposition.cc
Normal file
461
rtengine/EdgePreservingDecomposition.cc
Normal file
@ -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;
|
||||
}
|
||||
|
135
rtengine/EdgePreservingDecomposition.h
Normal file
135
rtengine/EdgePreservingDecomposition.h
Normal file
@ -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 <cmath>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
//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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <utils.h>
|
||||
#include <iccmatrices.h>
|
||||
|
||||
|
||||
#ifdef _OPENMP
|
||||
#include <omp.h>
|
||||
#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) {
|
||||
|
@ -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, \
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <labimage.h>
|
||||
#include <memory.h>
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -206,6 +206,16 @@ class ColorDenoiseParams {
|
||||
std::vector<double> 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<double> 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
|
||||
|
@ -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
|
||||
|
||||
};
|
||||
|
||||
|
@ -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, \
|
||||
|
@ -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, \
|
||||
|
@ -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
|
||||
|
163
rtgui/epd.cc
Normal file
163
rtgui/epd.cc
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* This file is part of RawTherapee.
|
||||
*
|
||||
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <epd.h>
|
||||
#include <iomanip>
|
||||
#include <math.h>
|
||||
|
||||
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();
|
||||
}
|
||||
|
50
rtgui/epd.h
Normal file
50
rtgui/epd.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of RawTherapee.
|
||||
*
|
||||
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef _EPD_H_
|
||||
#define _EPD_H_
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include <adjuster.h>
|
||||
#include <toolpanel.h>
|
||||
|
||||
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
|
@ -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<rtengine::procparams::ProcParams>
|
||||
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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <impulsedenoise.h>
|
||||
#include <defringe.h>
|
||||
#include <dirpyrdenoise.h>
|
||||
#include <epd.h>
|
||||
#include <sharpening.h>
|
||||
#include <labcurve.h>
|
||||
#include <exifpanel.h>
|
||||
@ -95,6 +96,7 @@ class ToolPanelCoordinator : public ToolPanelListener,
|
||||
Defringe* defringe;
|
||||
ImpulseDenoise* impulsedenoise;
|
||||
DirPyrDenoise* dirpyrdenoise;
|
||||
EdgePreservingDecompositionUI *edgePreservingDecompositionUI;
|
||||
Sharpening* sharpening;
|
||||
SharpenEdge* sharpenEdge;
|
||||
SharpenMicro* sharpenMicro;
|
||||
|
Loading…
x
Reference in New Issue
Block a user