Edge-preserving decomposition and tone-mapping tool. Committed on behalf of Ben_pcc.

This commit is contained in:
Emil Martinec 2011-11-28 14:29:26 -06:00
parent ba7dddd663
commit 6595bf18d1
25 changed files with 1059 additions and 29 deletions

View 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>

View File

@ -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

View 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;
}

View 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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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 *)(&params->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) {

View File

@ -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, \

View File

@ -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));
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -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, \

View File

@ -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, \

View File

@ -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
View 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
View 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

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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;