diff --git a/rtdata/languages/Deutsch b/rtdata/languages/Deutsch index 50414ec6c..cd095e766 100644 --- a/rtdata/languages/Deutsch +++ b/rtdata/languages/Deutsch @@ -926,8 +926,8 @@ TP_HSVEQUALIZER_LABEL;HSV-Equalizer TP_HSVEQUALIZER_NEUTRAL;Neutral TP_HSVEQUALIZER_SAT;S TP_HSVEQUALIZER_VAL;V -TP_ICM_BLENDCMSMATRIX;Lichter aus Matrix einmischen -TP_ICM_BLENDCMSMATRIX_TOOLTIP;Stellt bei Verwendung von LUT-basierten\nICC-Profilen die Lichter wieder her +TP_ICM_BLENDCMSMATRIX;ICC Lichter aus Matrix einmischen +TP_ICM_BLENDCMSMATRIX_TOOLTIP;Stellt bei Verwendung von LUT-basierten\nICC-Profilen die Lichter wieder her. TP_ICM_FILEDLGFILTERANY;Alle Dateien TP_ICM_FILEDLGFILTERICM;Profildateien TP_ICM_INPUTCAMERAICC;Kameraspezifisches Profil @@ -952,6 +952,8 @@ TP_ICM_PREFERREDPROFILE_2;Glühlampe TP_ICM_PREFERREDPROFILE_3;Leuchtstofflampe TP_ICM_PREFERREDPROFILE_4;Blitz TP_ICM_SAVEREFERENCE;Referenzbild für Profil speichern +TP_ICM_TONECURVE;DCP Tonwertkurve verwenden +TP_ICM_TONECURVE_TOOLTIP;Verwendet die Tonwertkurve, die in DCP profilen eingebetten sein kann. TP_ICM_WORKINGPROFILE;Arbeitsfarbraum TP_IMPULSEDENOISE_LABEL;Impulsrauschminderung TP_IMPULSEDENOISE_THRESH;Schwellenwert diff --git a/rtdata/languages/default b/rtdata/languages/default index 7e075c835..2b3d524aa 100644 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -950,7 +950,7 @@ TP_HSVEQUALIZER_LABEL;HSV Equalizer TP_HSVEQUALIZER_NEUTRAL;Neutral TP_HSVEQUALIZER_SAT;S TP_HSVEQUALIZER_VAL;V -TP_ICM_BLENDCMSMATRIX;Blend highlights with matrix +TP_ICM_BLENDCMSMATRIX;Blend ICC highlights with matrix TP_ICM_BLENDCMSMATRIX_TOOLTIP;Enable to recover blown highlights when using LUT based ICC profiles TP_ICM_FILEDLGFILTERANY;Any files TP_ICM_FILEDLGFILTERICM;Color profiles @@ -976,6 +976,8 @@ TP_ICM_PREFERREDPROFILE_2;Tungsten TP_ICM_PREFERREDPROFILE_3;Fluorescent TP_ICM_PREFERREDPROFILE_4;Flash TP_ICM_SAVEREFERENCE;Save reference image for profiling +TP_ICM_TONECURVE;Use DCP tone curve +TP_ICM_TONECURVE_TOOLTIP;Enable to use tone curves that may be contained in DCP profiles. TP_ICM_WORKINGPROFILE;Working Profile TP_IMPULSEDENOISE_LABEL;Impulse Noise Reduction TP_IMPULSEDENOISE_THRESH;Impulse NR Threshold diff --git a/rtengine/LUT.h b/rtengine/LUT.h index 9c497a7cc..3eb5f4ef0 100644 --- a/rtengine/LUT.h +++ b/rtengine/LUT.h @@ -135,7 +135,7 @@ public: return *this; } // use with integer indices - T& operator[](int index) { + T& operator[](int index) const { if (((unsigned int)index) maxs) { if (idx<0) @@ -185,7 +185,7 @@ public: #endif - operator bool (void) + operator bool (void) const { return size>0; } diff --git a/rtengine/dcp.cc b/rtengine/dcp.cc index a490b0090..5dc3b2dbf 100644 --- a/rtengine/dcp.cc +++ b/rtengine/dcp.cc @@ -25,17 +25,19 @@ #include "rawimagesource.h" #include "improcfun.h" #include "rt_math.h" +#include "curves.h" using namespace std; using namespace rtengine; using namespace rtexif; -DCPProfile::DCPProfile(Glib::ustring fname) { +DCPProfile::DCPProfile(Glib::ustring fname, bool isRTProfile) { const int TIFFFloatSize=4; const int TagColorMatrix1=50721, TagColorMatrix2=50722, TagProfileHueSatMapDims=50937; const int TagProfileHueSatMapData1=50938, TagProfileHueSatMapData2=50939; const int TagCalibrationIlluminant1=50778, TagCalibrationIlluminant2=50779; const int TagProfileLookTableData=50982, TagProfileLookTableDims=50981; // ProfileLookup is the low quality variant + const int TagProfileToneCurve=50940; aDeltas1=aDeltas2=NULL; iHueDivisions=iSatDivisions=iValDivisions=iArrayCount=0; @@ -115,6 +117,24 @@ DCPProfile::DCPProfile(Glib::ustring fname) { } } + // Read tone curve points, if any, but disable to RTs own profiles + // the DCP tone curve is subjective and of low quality in comparison to RTs tone curves + tag = tagDir->getTag(TagProfileToneCurve); + if (tag!=NULL && !isRTProfile) { + std::vector cPoints; + cPoints.push_back(double(DCT_Spline)); // The first value is the curve type + + // push back each X/Y coordinates in a loop + for (int i=0;igetCount();i++) cPoints.push_back( tag->toDouble(i*TIFFFloatSize) ); + + // Create the curve + DiagonalCurve toneCurve(cPoints, CURVES_MIN_POLY_POINTS); + + // Fill a LUT with X/Y, ranged 0xffff + lutToneCurve(0xffff); + for (int i=0;i<0xffff;i++) lutToneCurve[i] = toneCurve.getVal(i/(double)0xffff) * 0xffff; + } + if (pFile!=NULL) fclose(pFile); delete tagDir; } @@ -123,42 +143,42 @@ DCPProfile::~DCPProfile() { delete[] aDeltas1; delete[] aDeltas2; } - // Convert DNG color matrix to xyz_cam compatible matrix +// Convert DNG color matrix to xyz_cam compatible matrix void DCPProfile::ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) { - int i,j,k; + int i,j,k; - double cam_xyz[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; - for (i=0; i<3; i++) - for (j=0; j<3; j++) - for (k=0; k<3; k++) - cam_xyz[i][j] += mColorMatrix[j][k] * (i==k); + double cam_xyz[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + for (i=0; i<3; i++) + for (j=0; j<3; j++) + for (k=0; k<3; k++) + cam_xyz[i][j] += mColorMatrix[j][k] * (i==k); - // Multiply out XYZ colorspace - double cam_rgb[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; - for (i=0; i < 3; i++) - for (j=0; j < 3; j++) - for (k=0; k < 3; k++) - cam_rgb[i][j] += cam_xyz[i][k] * xyz_sRGB[k][j]; + // Multiply out XYZ colorspace + double cam_rgb[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + for (i=0; i < 3; i++) + for (j=0; j < 3; j++) + for (k=0; k < 3; k++) + cam_rgb[i][j] += cam_xyz[i][k] * xyz_sRGB[k][j]; - // Normalize cam_rgb so that: cam_rgb * (1,1,1) is (1,1,1,1) - double num; - for (i=0; i<3; i++) { - for (num=j=0; j<3; j++) num += cam_rgb[i][j]; - for (j=0; j<3; j++) cam_rgb[i][j] /= num; - } + // Normalize cam_rgb so that: cam_rgb * (1,1,1) is (1,1,1,1) + double num; + for (i=0; i<3; i++) { + for (num=j=0; j<3; j++) num += cam_rgb[i][j]; + for (j=0; j<3; j++) cam_rgb[i][j] /= num; + } - double rgb_cam[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; - RawImageSource::inverse33 (cam_rgb, rgb_cam); + double rgb_cam[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + RawImageSource::inverse33 (cam_rgb, rgb_cam); for (i=0; i<3; i++) for (j=0; j<3; j++) mXYZCAM[i][j]=0; - for (i=0; i<3; i++) - for (j=0; j<3; j++) - for (k=0; k<3; k++) - mXYZCAM[i][j] += xyz_sRGB[i][k] * rgb_cam[k][j]; - } + for (i=0; i<3; i++) + for (j=0; j<3; j++) + for (k=0; k<3; k++) + mXYZCAM[i][j] += xyz_sRGB[i][k] * rgb_cam[k][j]; +} const DCPProfile::HSBModify* DCPProfile::GetBestProfile(DCPLightType preferredProfile, double (*mXYZCAM)[3]) const { @@ -171,21 +191,21 @@ const DCPProfile::HSBModify* DCPProfile::GetBestProfile(DCPLightType preferredPr if (t2==Daylight) use2=true; switch (preferredProfile) { - case Tungsten: - if (t1==Tungsten) use2=false; else if (t2==Tungsten) use2=true; - break; + case Tungsten: + if (t1==Tungsten) use2=false; else if (t2==Tungsten) use2=true; + break; - case Fluorescent: - if (t1==Fluorescent) use2=false; else if (t2==Fluorescent) use2=true; - break; + case Fluorescent: + if (t1==Fluorescent) use2=false; else if (t2==Fluorescent) use2=true; + break; - case Flash: - if (t1==Flash) use2=false; else if (t2==Flash) use2=true; - break; + case Flash: + if (t1==Flash) use2=false; else if (t2==Flash) use2=true; + break; - default: break; // e.g. Daylight + default: break; // e.g. Daylight } -} + } // printf("DCP using LightSource %i: %i for requested %i\n", use2?2:1, use2?iLightSource2:iLightSource1, (int)preferredProfile); @@ -193,7 +213,7 @@ const DCPProfile::HSBModify* DCPProfile::GetBestProfile(DCPLightType preferredPr for (int col=0;col<3;col++) { mXYZCAM[col][row]= (use2 ? mXYZCAM2[col][row] : mXYZCAM1[col][row]); } -} + } return use2?aDeltas2:aDeltas1; } @@ -205,14 +225,16 @@ DCPLightType DCPProfile::GetLightType(short iLightSource) const { return Daylight; } -void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, float rawWhiteFac) const { +void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, float rawWhiteFac, bool useToneCurve) const { TMatrix mWork = iccStore->workingSpaceInverseMatrix (workingSpace); double mXYZCAM[3][3]; const HSBModify* tableBase=GetBestProfile(preferredProfile,mXYZCAM); - if (iArrayCount==0) { - //===== No LUT- Calculate matrix for direct conversion raw>working space + bool hasLUT=(iArrayCount>0); useToneCurve&=lutToneCurve; + + if (!hasLUT && !useToneCurve) { + //===== The fast path: no LUT and not tone curve- Calculate matrix for direct conversion raw>working space double mat[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; for (int i=0; i<3; i++) for (int j=0; j<3; j++) @@ -270,160 +292,163 @@ void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::us newb = m2ProPhoto[2][0]*pImg->r[y][x] + m2ProPhoto[2][1]*pImg->g[y][x] + m2ProPhoto[2][2]*pImg->b[y][x]; // if point is in negative area, just the matrix, but not the LUT - if (newr>=0 && newg>=0 && newb>=0) { - Color::rgb2hsv(newr, newg, newb, h , s, v); - h*=6.f; // RT calculates in [0,1] + if (hasLUT && newr>=0 && newg>=0 && newb>=0) { + Color::rgb2hsv(newr, newg, newb, h , s, v); + h*=6.f; // RT calculates in [0,1] - if (useRawWhite) { - // Retro-calculate what the point was like before RAW white came in - Color::rgb2hsv(newr/rawWhiteFac, newg/rawWhiteFac, newb/rawWhiteFac, hs, ss, vs); - hs*=6.f; // RT calculates in [0,1] - } else { - hs=h; ss=s; vs=v; - } - - // Apply the HueSatMap. Ported from Adobes reference implementation - float hueShift, satScale, valScale; - - if (iValDivisions < 2) // Optimize most common case of "2.5D" table. - { - float hScaled = hs * hScale; - float sScaled = ss * sScale; - - int hIndex0 = max((int)hScaled, 0); - int sIndex0 = max(min((int)sScaled,maxSatIndex0),0); - - int hIndex1 = hIndex0 + 1; - - if (hIndex0 >= maxHueIndex0) - { - hIndex0 = maxHueIndex0; - hIndex1 = 0; + if (useRawWhite) { + // Retro-calculate what the point was like before RAW white came in + Color::rgb2hsv(newr/rawWhiteFac, newg/rawWhiteFac, newb/rawWhiteFac, hs, ss, vs); + hs*=6.f; // RT calculates in [0,1] + } else { + hs=h; ss=s; vs=v; } - float hFract1 = hScaled - (float) hIndex0; - float sFract1 = sScaled - (float) sIndex0; + // Apply the HueSatMap. Ported from Adobes reference implementation + float hueShift, satScale, valScale; - float hFract0 = 1.0f - hFract1; - float sFract0 = 1.0f - sFract1; - - const HSBModify *entry00 = tableBase + hIndex0 * hueStep + - sIndex0; - - const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * hueStep; - - float hueShift0 = hFract0 * entry00->fHueShift + - hFract1 * entry01->fHueShift; - - float satScale0 = hFract0 * entry00->fSatScale + - hFract1 * entry01->fSatScale; - - float valScale0 = hFract0 * entry00->fValScale + - hFract1 * entry01->fValScale; - - entry00++; - entry01++; - - float hueShift1 = hFract0 * entry00->fHueShift + - hFract1 * entry01->fHueShift; - - float satScale1 = hFract0 * entry00->fSatScale + - hFract1 * entry01->fSatScale; - - float valScale1 = hFract0 * entry00->fValScale + - hFract1 * entry01->fValScale; - - hueShift = sFract0 * hueShift0 + sFract1 * hueShift1; - satScale = sFract0 * satScale0 + sFract1 * satScale1; - valScale = sFract0 * valScale0 + sFract1 * valScale1; - - } else { - - float hScaled = hs * hScale; - float sScaled = ss * sScale; - float vScaled = vs * vScale; - - int hIndex0 = (int) hScaled; - int sIndex0 = max(min((int)sScaled,maxSatIndex0),0); - int vIndex0 = max(min((int)vScaled,maxValIndex0),0); - - int hIndex1 = hIndex0 + 1; - - if (hIndex0 >= maxHueIndex0) + if (iValDivisions < 2) // Optimize most common case of "2.5D" table. { - hIndex0 = maxHueIndex0; - hIndex1 = 0; + float hScaled = hs * hScale; + float sScaled = ss * sScale; + + int hIndex0 = max((int)hScaled, 0); + int sIndex0 = max(min((int)sScaled,maxSatIndex0),0); + + int hIndex1 = hIndex0 + 1; + + if (hIndex0 >= maxHueIndex0) + { + hIndex0 = maxHueIndex0; + hIndex1 = 0; + } + + float hFract1 = hScaled - (float) hIndex0; + float sFract1 = sScaled - (float) sIndex0; + + float hFract0 = 1.0f - hFract1; + float sFract0 = 1.0f - sFract1; + + const HSBModify *entry00 = tableBase + hIndex0 * hueStep + + sIndex0; + + const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * hueStep; + + float hueShift0 = hFract0 * entry00->fHueShift + + hFract1 * entry01->fHueShift; + + float satScale0 = hFract0 * entry00->fSatScale + + hFract1 * entry01->fSatScale; + + float valScale0 = hFract0 * entry00->fValScale + + hFract1 * entry01->fValScale; + + entry00++; + entry01++; + + float hueShift1 = hFract0 * entry00->fHueShift + + hFract1 * entry01->fHueShift; + + float satScale1 = hFract0 * entry00->fSatScale + + hFract1 * entry01->fSatScale; + + float valScale1 = hFract0 * entry00->fValScale + + hFract1 * entry01->fValScale; + + hueShift = sFract0 * hueShift0 + sFract1 * hueShift1; + satScale = sFract0 * satScale0 + sFract1 * satScale1; + valScale = sFract0 * valScale0 + sFract1 * valScale1; + + } else { + + float hScaled = hs * hScale; + float sScaled = ss * sScale; + float vScaled = vs * vScale; + + int hIndex0 = (int) hScaled; + int sIndex0 = max(min((int)sScaled,maxSatIndex0),0); + int vIndex0 = max(min((int)vScaled,maxValIndex0),0); + + int hIndex1 = hIndex0 + 1; + + if (hIndex0 >= maxHueIndex0) + { + hIndex0 = maxHueIndex0; + hIndex1 = 0; + } + + float hFract1 = hScaled - (float) hIndex0; + float sFract1 = sScaled - (float) sIndex0; + float vFract1 = vScaled - (float) vIndex0; + + float hFract0 = 1.0f - hFract1; + float sFract0 = 1.0f - sFract1; + float vFract0 = 1.0f - vFract1; + + const HSBModify *entry00 = tableBase + vIndex0 * valStep + + hIndex0 * hueStep + + sIndex0; + + const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * hueStep; + + const HSBModify *entry10 = entry00 + valStep; + const HSBModify *entry11 = entry01 + valStep; + + float hueShift0 = vFract0 * (hFract0 * entry00->fHueShift + + hFract1 * entry01->fHueShift) + + vFract1 * (hFract0 * entry10->fHueShift + + hFract1 * entry11->fHueShift); + + float satScale0 = vFract0 * (hFract0 * entry00->fSatScale + + hFract1 * entry01->fSatScale) + + vFract1 * (hFract0 * entry10->fSatScale + + hFract1 * entry11->fSatScale); + + float valScale0 = vFract0 * (hFract0 * entry00->fValScale + + hFract1 * entry01->fValScale) + + vFract1 * (hFract0 * entry10->fValScale + + hFract1 * entry11->fValScale); + + entry00++; + entry01++; + entry10++; + entry11++; + + float hueShift1 = vFract0 * (hFract0 * entry00->fHueShift + + hFract1 * entry01->fHueShift) + + vFract1 * (hFract0 * entry10->fHueShift + + hFract1 * entry11->fHueShift); + + float satScale1 = vFract0 * (hFract0 * entry00->fSatScale + + hFract1 * entry01->fSatScale) + + vFract1 * (hFract0 * entry10->fSatScale + + hFract1 * entry11->fSatScale); + + float valScale1 = vFract0 * (hFract0 * entry00->fValScale + + hFract1 * entry01->fValScale) + + vFract1 * (hFract0 * entry10->fValScale + + hFract1 * entry11->fValScale); + + hueShift = sFract0 * hueShift0 + sFract1 * hueShift1; + satScale = sFract0 * satScale0 + sFract1 * satScale1; + valScale = sFract0 * valScale0 + sFract1 * valScale1; } - float hFract1 = hScaled - (float) hIndex0; - float sFract1 = sScaled - (float) sIndex0; - float vFract1 = vScaled - (float) vIndex0; + hueShift *= (6.0f / 360.0f); // Convert to internal hue range. - float hFract0 = 1.0f - hFract1; - float sFract0 = 1.0f - sFract1; - float vFract0 = 1.0f - vFract1; + h += hueShift; + s *= satScale; // no clipping here, we are RT float :-) + v *= valScale; - const HSBModify *entry00 = tableBase + vIndex0 * valStep + - hIndex0 * hueStep + - sIndex0; - - const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * hueStep; - - const HSBModify *entry10 = entry00 + valStep; - const HSBModify *entry11 = entry01 + valStep; - - float hueShift0 = vFract0 * (hFract0 * entry00->fHueShift + - hFract1 * entry01->fHueShift) + - vFract1 * (hFract0 * entry10->fHueShift + - hFract1 * entry11->fHueShift); - - float satScale0 = vFract0 * (hFract0 * entry00->fSatScale + - hFract1 * entry01->fSatScale) + - vFract1 * (hFract0 * entry10->fSatScale + - hFract1 * entry11->fSatScale); - - float valScale0 = vFract0 * (hFract0 * entry00->fValScale + - hFract1 * entry01->fValScale) + - vFract1 * (hFract0 * entry10->fValScale + - hFract1 * entry11->fValScale); - - entry00++; - entry01++; - entry10++; - entry11++; - - float hueShift1 = vFract0 * (hFract0 * entry00->fHueShift + - hFract1 * entry01->fHueShift) + - vFract1 * (hFract0 * entry10->fHueShift + - hFract1 * entry11->fHueShift); - - float satScale1 = vFract0 * (hFract0 * entry00->fSatScale + - hFract1 * entry01->fSatScale) + - vFract1 * (hFract0 * entry10->fSatScale + - hFract1 * entry11->fSatScale); - - float valScale1 = vFract0 * (hFract0 * entry00->fValScale + - hFract1 * entry01->fValScale) + - vFract1 * (hFract0 * entry10->fValScale + - hFract1 * entry11->fValScale); - - hueShift = sFract0 * hueShift0 + sFract1 * hueShift1; - satScale = sFract0 * satScale0 + sFract1 * satScale1; - valScale = sFract0 * valScale0 + sFract1 * valScale1; + // RT range correction + if (h < 0.0f) h += 6.0f; + if (h >= 6.0f) h -= 6.0f; + h/=6.f; + Color::hsv2rgb( h, s, v, newr, newg, newb); } - hueShift *= (6.0f / 360.0f); // Convert to internal hue range. - - h += hueShift; - s *= satScale; // no clipping here, we are RT float :-) - v *= valScale; - - // RT range correction - if (h < 0.0f) h += 6.0f; - if (h >= 6.0f) h -= 6.0f; - h/=6.f; - Color::hsv2rgb( h, s, v, newr, newg, newb); - } + // tone curve + if (useToneCurve) ApplyToneCurve(newr, newg, newb); pImg->r[y][x] = m2Work[0][0]*newr + m2Work[0][1]*newg + m2Work[0][2]*newb; pImg->g[y][x] = m2Work[1][0]*newr + m2Work[1][1]*newg + m2Work[1][2]*newb; @@ -433,14 +458,73 @@ void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::us } } +// Tone curve according to Adobes reference implementation +void DCPProfile::ApplyToneCurve (float& r, float& g, float& b) const { + if (r >= g) + { + if (g > b) + { + // Case 1: r >= g > b + RGBTone (r, g, b); + } + + else if (b > r) + { + // Case 2: b > r >= g + RGBTone (b, r, g); + } + + else if (b > g) + { + // Case 3: r >= b > g + RGBTone (r, b, g); + } + + else + { + // Case 4: r >= g == b + r = lutToneCurve[r]; + g = lutToneCurve[g]; + b = g; + } + } + else + { + if (r >= b) + { + // Case 5: g > r >= b + RGBTone (g, r, b); + } + else if (b > g) + { + // Case 6: b > g > r + RGBTone (b, g, r); + } + else + { + // Case 7: g >= b > r + RGBTone (g, b, r); + } + } +} + +void DCPProfile::RGBTone (float& r, float& g, float& b) const { + float rold=r,gold=g,bold=b; + + r = lutToneCurve[rold]; + b = lutToneCurve[bold]; + g = b + ((r - b) * (gold - bold) / (rold - bold)); +} // Integer variant is legacy, only used for thumbs. Simply take the matrix here -void DCPProfile::Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace) const { +void DCPProfile::Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, bool useToneCurve) const { TMatrix mWork = iccStore->workingSpaceInverseMatrix (workingSpace); double mXYZCAM[3][3]; const HSBModify* tableBase=GetBestProfile(preferredProfile,mXYZCAM); + useToneCurve&=lutToneCurve; + // Calculate matrix for direct conversion raw>working space double mat[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; for (int i=0; i<3; i++) @@ -457,6 +541,9 @@ void DCPProfile::Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustri newg = mat[1][0]*pImg->r[y][x] + mat[1][1]*pImg->g[y][x] + mat[1][2]*pImg->b[y][x]; newb = mat[2][0]*pImg->r[y][x] + mat[2][1]*pImg->g[y][x] + mat[2][2]*pImg->b[y][x]; + // tone curve + if (useToneCurve) ApplyToneCurve(newr, newg, newb); + pImg->r[y][x] = CLIP((int)newr); pImg->g[y][x] = CLIP((int)newg); pImg->b[y][x] = CLIP((int)newb); } } @@ -523,14 +610,14 @@ void DCPStore::init (Glib::ustring rtProfileDir) { } } -DCPProfile* DCPStore::getProfile (Glib::ustring filename) { +DCPProfile* DCPStore::getProfile (Glib::ustring filename, bool isRTProfile) { Glib::Mutex::Lock lock(mtx); std::map::iterator r = profileCache.find (filename); if (r!=profileCache.end()) return r->second; // Add profile - profileCache[filename]=new DCPProfile(filename); + profileCache[filename]=new DCPProfile(filename, isRTProfile); return profileCache[filename]; } @@ -540,7 +627,7 @@ DCPProfile* DCPStore::getStdProfile(Glib::ustring camShortName) { // Warning: do NOT use map.find(), since it does not seem to work reliably here for (std::map::iterator i=fileStdProfiles.begin();i!=fileStdProfiles.end();i++) - if (name2==(*i).first) return getProfile((*i).second); + if (name2==(*i).first) return getProfile((*i).second, true); return NULL; } @@ -548,5 +635,5 @@ DCPProfile* DCPStore::getStdProfile(Glib::ustring camShortName) { bool DCPStore::isValidDCPFileName(Glib::ustring filename) const { if (!safe_file_test (filename, Glib::FILE_TEST_EXISTS) || safe_file_test (filename, Glib::FILE_TEST_IS_DIR)) return false; size_t pos=filename.find_last_of ('.'); - return pos>0 && !filename.casefold().compare (pos, 4, ".dcp"); + return pos>0 && (!filename.casefold().compare (pos, 4, ".dcp") || !filename.casefold().compare (pos, 4, ".dng")); } \ No newline at end of file diff --git a/rtengine/dcp.h b/rtengine/dcp.h index bb3ed2c88..67e6af3a3 100644 --- a/rtengine/dcp.h +++ b/rtengine/dcp.h @@ -47,6 +47,10 @@ namespace rtengine { int iHueStep, iValStep, iArrayCount; + LUTf lutToneCurve; // 0..0xffff values to 0..1 + void ApplyToneCurve(float& r, float& g, float& b) const; + void RGBTone(float& r, float& g, float& b) const; // helper for tone curve + void ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]); const HSBModify* GetBestProfile(DCPLightType preferredProfile, double (*mXYZCAM)[3]) const; @@ -54,11 +58,11 @@ namespace rtengine { DCPLightType GetLightType(short iLightSource) const; public: - DCPProfile(Glib::ustring fname); + DCPProfile(Glib::ustring fname, bool isRTProfile); ~DCPProfile(); - void Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, float rawWhiteFac=1) const; - void Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace) const; + void Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, float rawWhiteFac=1, bool useToneCurve=false) const; + void Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, bool useToneCurve) const; }; class DCPStore { @@ -75,7 +79,7 @@ namespace rtengine { bool isValidDCPFileName(Glib::ustring filename) const; - DCPProfile* getProfile(Glib::ustring filename); + DCPProfile* getProfile(Glib::ustring filename, bool isRTProfile=false); DCPProfile* getStdProfile(Glib::ustring camShortName); static DCPStore* getInstance(); diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 2ce504b6a..606d9a78d 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -69,7 +69,7 @@ enum ProcEvent { EvLDNEdgeTolerance=44, // obsolete EvCDNEnabled=45, // obsolete EvCDNRadius=46, // obsolete - EvCDNEdgeTolerance=47, // obsolete + EvDCPToneCurve=47, EvPrefProfile=48, EvSHEnabled=49, EvSHHighlights=50, diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index 0c26c0076..50fe16a37 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -309,6 +309,7 @@ void ProcParams::setDefaults () { icm.input = ""; icm.blendCMSMatrix = false; + icm.toneCurve = false; icm.preferredProfile = (short)rtengine::Daylight; icm.working = "sRGB"; icm.output = "sRGB"; @@ -614,6 +615,7 @@ int ProcParams::save (Glib::ustring fname, Glib::ustring fname2, ParamsEdited* p // save color management settings if (!pedited || pedited->icm.input) keyFile.set_string ("Color Management", "InputProfile", icm.input); + if (!pedited || pedited->icm.toneCurve) keyFile.set_boolean ("Color Management", "ToneCurve", icm.toneCurve); if (!pedited || pedited->icm.blendCMSMatrix) keyFile.set_boolean ("Color Management", "BlendCMSMatrix", icm.blendCMSMatrix); if (!pedited || pedited->icm.preferredProfile) keyFile.set_boolean ("Color Management", "PreferredProfile", icm.preferredProfile); if (!pedited || pedited->icm.working) keyFile.set_string ("Color Management", "WorkingProfile", icm.working); @@ -1068,6 +1070,7 @@ if (keyFile.has_group ("Resize")) { // load color management settings if (keyFile.has_group ("Color Management")) { if (keyFile.has_key ("Color Management", "InputProfile")) { icm.input = keyFile.get_string ("Color Management", "InputProfile"); if (pedited) pedited->icm.input = true; } + if (keyFile.has_key ("Color Management", "ToneCurve")) { icm.toneCurve = keyFile.get_boolean ("Color Management", "ToneCurve"); if (pedited) pedited->icm.toneCurve = true; } if (keyFile.has_key ("Color Management", "BlendCMSMatrix")) { icm.blendCMSMatrix = keyFile.get_boolean ("Color Management", "BlendCMSMatrix"); if (pedited) pedited->icm.blendCMSMatrix = true; } if (keyFile.has_key ("Color Management", "PreferredProfile")) { icm.preferredProfile = keyFile.get_boolean ("Color Management", "PreferredProfile"); if (pedited) pedited->icm.preferredProfile = true; } if (keyFile.has_key ("Color Management", "WorkingProfile")) { icm.working = keyFile.get_string ("Color Management", "WorkingProfile"); if (pedited) pedited->icm.working = true; } @@ -1369,6 +1372,7 @@ bool ProcParams::operator== (const ProcParams& other) { && raw.greenthresh == other.raw.greenthresh && raw.linenoise == other.raw.linenoise && icm.input == other.icm.input + && icm.toneCurve == other.icm.toneCurve && icm.blendCMSMatrix == other.icm.blendCMSMatrix && icm.preferredProfile == other.icm.preferredProfile && icm.working == other.icm.working diff --git a/rtengine/procparams.h b/rtengine/procparams.h index ac2e553d4..2586d6e67 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -569,6 +569,7 @@ class ColorManagementParams { public: Glib::ustring input; + bool toneCurve; bool blendCMSMatrix; short preferredProfile; Glib::ustring working; diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 878b7d2bb..4a44a5750 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -1650,7 +1650,7 @@ void RawImageSource::colorSpaceConversion (Imagefloat* im, ColorManagementParams if (!findInputProfile(cmp.input, embedded, camName, &dcpProf, in)) return; if (dcpProf!=NULL) { - dcpProf->Apply(im, (DCPLightType)cmp.preferredProfile, cmp.working, (float)raw.expos); + dcpProf->Apply(im, (DCPLightType)cmp.preferredProfile, cmp.working, (float)raw.expos, cmp.toneCurve); } else { // Calculate matrix for direct conversion raw>working space TMatrix work = iccStore->workingSpaceInverseMatrix (cmp.working); @@ -1874,7 +1874,7 @@ void RawImageSource::colorSpaceConversion16 (Image16* im, ColorManagementParams if (!findInputProfile(cmp.input, embedded, camName, &dcpProf, in)) return; if (dcpProf!=NULL) { - dcpProf->Apply(im, (DCPLightType)cmp.preferredProfile, cmp.working); + dcpProf->Apply(im, (DCPLightType)cmp.preferredProfile, cmp.working, cmp.toneCurve); } else { if (in==NULL) { // Take camprofile from DCRAW diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index cffd431bb..fda350356 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -67,7 +67,7 @@ WHITEBALANCE, // EvWBGreen, 0, // EvLDNEdgeTolerance: obsolete, 0, // EvCDNEnabled:obsolete, 0, // EvCDNRadius: obsolete, -0, // EvCDNEdgeTolerance: obsolete, +ALL, // EvDCPToneCurve, ALL, // EvPrefProfile, RETINEX, // EvSHEnabled, RGBCURVE, // EvSHHighlights, diff --git a/rtgui/icmpanel.cc b/rtgui/icmpanel.cc index 14f87baec..3a69ba2d4 100644 --- a/rtgui/icmpanel.cc +++ b/rtgui/icmpanel.cc @@ -87,6 +87,11 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), hb->pack_start(*prefprof); pack_start (*hb, Gtk::PACK_SHRINK, 4); + ckbToneCurve = Gtk::manage (new Gtk::CheckButton (M("TP_ICM_TONECURVE"))); + ckbToneCurve->set_sensitive (false); + ckbToneCurve->set_tooltip_text (M("TP_ICM_TONECURVE_TOOLTIP")); + pack_start (*ckbToneCurve, Gtk::PACK_SHRINK, 4); + ckbBlendCMSMatrix = Gtk::manage (new Gtk::CheckButton (M("TP_ICM_BLENDCMSMATRIX"))); ckbBlendCMSMatrix->set_sensitive (false); ckbBlendCMSMatrix->set_tooltip_text (M("TP_ICM_BLENDCMSMATRIX_TOOLTIP")); @@ -176,6 +181,8 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), filter_icc.set_name(M("TP_ICM_FILEDLGFILTERICM")); filter_icc.add_pattern("*.dcp"); filter_icc.add_pattern("*.DCP"); + filter_icc.add_pattern("*.dng"); + filter_icc.add_pattern("*.DNG"); filter_icc.add_pattern("*.icc"); filter_icc.add_pattern("*.icm"); filter_icc.add_pattern("*.ICC"); @@ -199,6 +206,8 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), iembedded->signal_toggled().connect( sigc::mem_fun(*this, &ICMPanel::ipChanged) ); ifromfile->signal_toggled().connect( sigc::mem_fun(*this, &ICMPanel::ipChanged) ); ckbBlendCMSMatrix->signal_toggled().connect( sigc::mem_fun(*this, &ICMPanel::iccTogglesChanged) ); + ckbToneCurve->signal_toggled().connect( sigc::mem_fun(*this, &ICMPanel::toneCurveChanged) ); + ipc = ipDialog->signal_selection_changed().connect( sigc::mem_fun(*this, &ICMPanel::ipSelectionChanged) ); saveRef->signal_pressed().connect( sigc::mem_fun(*this, &ICMPanel::saveReferencePressed) ); @@ -211,33 +220,33 @@ void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { ipc.block (true); if (pp->icm.input == "(none)" && icamera->get_state()!=Gtk::STATE_INSENSITIVE) { - inone->set_active (true); prefprof->set_sensitive (false); + inone->set_active (true); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); ckbBlendCMSMatrix->set_sensitive (false); } else if (pp->icm.input == "(embedded)" || ((pp->icm.input == "(camera)" || pp->icm.input=="") && icamera->get_state()==Gtk::STATE_INSENSITIVE)) { - iembedded->set_active (true); prefprof->set_sensitive (false); + iembedded->set_active (true); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); ckbBlendCMSMatrix->set_sensitive (false); } else if ((pp->icm.input == "(cameraICC)") && icameraICC->get_state()!=Gtk::STATE_INSENSITIVE) { - icameraICC->set_active (true); prefprof->set_sensitive (true); + icameraICC->set_active (true); prefprof->set_sensitive (true); ckbToneCurve->set_sensitive (true); ckbBlendCMSMatrix->set_sensitive (true); } else if ((pp->icm.input == "(cameraICC)") && icameraICC->get_state()==Gtk::STATE_INSENSITIVE) { // this is the case when (cameraICC) is instructed by packaged profiles, but ICC file is not found // therefore falling back UI to explicitly reflect the (camera) option icamera->set_active (true); - prefprof->set_sensitive (false); // RT's own are always single-illuminant + prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); // RT's own are always single-illuminant and tone curve disabled ckbBlendCMSMatrix->set_sensitive (false); } else if ((pp->icm.input == "(camera)" || pp->icm.input=="") && icamera->get_state()!=Gtk::STATE_INSENSITIVE) { icamera->set_active (true); - ckbBlendCMSMatrix->set_sensitive (false); prefprof->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive (false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); } else { ifromfile->set_active (true); oldip = pp->icm.input.substr(5); // cut of "file:" ipDialog->set_filename (pp->icm.input.substr(5)); - ckbBlendCMSMatrix->set_sensitive (true); prefprof->set_sensitive (true); + ckbBlendCMSMatrix->set_sensitive (true); prefprof->set_sensitive (true); ckbToneCurve->set_sensitive (true); } wnames->set_active_text (pp->icm.working); @@ -253,12 +262,14 @@ void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { prefprof->set_active(pp->icm.preferredProfile-1); + ckbToneCurve->set_active (pp->icm.toneCurve); ckbBlendCMSMatrix->set_active (pp->icm.blendCMSMatrix); onames->set_sensitive(wgamma->get_active_row_number()==0 || freegamma->get_active()); //"default" wgamma->set_sensitive(!freegamma->get_active()); if (pedited) { iunchanged->set_active (!pedited->icm.input); + ckbToneCurve->set_sensitive (false); ckbBlendCMSMatrix->set_sensitive (false); if (!pedited->icm.working) wnames->set_active_text(M("GENERAL_UNCHANGED")); @@ -317,6 +328,7 @@ void ICMPanel::write (ProcParams* pp, ParamsEdited* pedited) { else pp->icm.output = onames->get_active_text(); pp->icm.freegamma = freegamma->get_active(); + pp->icm.toneCurve = ckbToneCurve->get_active (); pp->icm.blendCMSMatrix = ckbBlendCMSMatrix->get_active (); pp->icm.gampos =(double) gampos->getValue(); pp->icm.slpos =(double) slpos->getValue(); @@ -326,6 +338,7 @@ void ICMPanel::write (ProcParams* pp, ParamsEdited* pedited) { pedited->icm.working = wnames->get_active_text()!=M("GENERAL_UNCHANGED"); pedited->icm.output = onames->get_active_text()!=M("GENERAL_UNCHANGED"); pedited->icm.preferredProfile = prefprof->get_active_text()!=M("GENERAL_UNCHANGED"); + pedited->icm.toneCurve = !ckbToneCurve->get_inconsistent (); pedited->icm.blendCMSMatrix = !ckbBlendCMSMatrix->get_inconsistent (); pedited->icm.gamma = wgamma->get_active_text()!=M("GENERAL_UNCHANGED"); pedited->icm.freegamma =!freegamma->get_inconsistent(); @@ -389,28 +402,33 @@ void ICMPanel::prefProfChanged() { listener->panelChanged (EvPrefProfile, prefprof->get_active_text ()); } +void ICMPanel::toneCurveChanged() { + if (listener) + listener->panelChanged (EvDCPToneCurve, ""); +} + void ICMPanel::ipChanged () { std::string profname; if (inone->get_active()) { profname = "(none)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); } else if (iembedded->get_active ()) { profname = "(embedded)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); } else if (icamera->get_active ()) { profname = "(camera)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); } else if (icameraICC->get_active ()) { profname = "(cameraICC)"; - ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); } else { profname = ipDialog->get_filename (); - ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (true); + ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (true); ckbToneCurve->set_sensitive (true); } if (listener && profname!=oldip) diff --git a/rtgui/icmpanel.h b/rtgui/icmpanel.h index 63fef53d5..d4255a45d 100644 --- a/rtgui/icmpanel.h +++ b/rtgui/icmpanel.h @@ -51,6 +51,7 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP Gtk::RadioButton* icameraICC; Gtk::RadioButton* ifromfile; MyComboBoxText* prefprof; + Gtk::CheckButton* ckbToneCurve; Gtk::CheckButton* ckbBlendCMSMatrix; MyComboBoxText* wnames; MyComboBoxText* wgamma; @@ -88,6 +89,7 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP void ipSelectionChanged (); void iccTogglesChanged(); void prefProfChanged(); + void toneCurveChanged(); void setRawMeta (bool raw, const rtengine::ImageData* pMeta); void saveReferencePressed (); diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 18f6c3c4c..b1c3a2662 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -172,6 +172,7 @@ void ParamsEdited::set (bool v) { resize.height = v; resize.enabled = v; icm.input = v; + icm.toneCurve = v; icm.blendCMSMatrix = v; icm.preferredProfile = v; icm.working = v; @@ -373,6 +374,7 @@ void ParamsEdited::initFrom (const std::vector resize.height = resize.height && p.resize.height == other.resize.height; resize.enabled = resize.enabled && p.resize.enabled == other.resize.enabled; icm.input = icm.input && p.icm.input == other.icm.input; + icm.toneCurve = icm.toneCurve && p.icm.toneCurve == other.icm.toneCurve; icm.blendCMSMatrix = icm.blendCMSMatrix && p.icm.blendCMSMatrix == other.icm.blendCMSMatrix; icm.preferredProfile = icm.preferredProfile && p.icm.preferredProfile == other.icm.preferredProfile; icm.working = icm.working && p.icm.working == other.icm.working; @@ -578,6 +580,7 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten if (resize.height) toEdit.resize.height = mods.resize.height; if (resize.enabled) toEdit.resize.enabled = mods.resize.enabled; if (icm.input) toEdit.icm.input = mods.icm.input; + if (icm.toneCurve) toEdit.icm.toneCurve = mods.icm.toneCurve; if (icm.blendCMSMatrix) toEdit.icm.blendCMSMatrix = mods.icm.blendCMSMatrix; if (icm.preferredProfile) toEdit.icm.preferredProfile = mods.icm.preferredProfile; if (icm.working) toEdit.icm.working = mods.icm.working; diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index 32d4e6dd4..3bf865083 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -313,6 +313,7 @@ class ColorManagementParamsEdited { public: bool input; + bool toneCurve; bool blendCMSMatrix; bool preferredProfile; bool working;