diff --git a/rtdata/languages/default b/rtdata/languages/default index f4719f45b..6834c59ef 100755 --- a/rtdata/languages/default +++ b/rtdata/languages/default @@ -283,7 +283,7 @@ HISTORY_MSG_45;Lum. Denoising Edge Tolerance HISTORY_MSG_46;Color Denoising HISTORY_MSG_47;Blend ICC highlights with matrix HISTORY_MSG_48;Use DCP's tone curve -HISTORY_MSG_49;Edge Sensitive Color Denoising +HISTORY_MSG_49;DCP Illuminant HISTORY_MSG_50;Shadow/Highlight HISTORY_MSG_51;Highlights HISTORY_MSG_52;Shadows @@ -1234,6 +1234,9 @@ TP_HSVEQUALIZER_SAT;S TP_HSVEQUALIZER_VAL;V 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_DCPILLUMINANT;Illuminant +TP_ICM_DCPILLUMINANT_TOOLTIP;Select which embedded DCP illuminant to employ. Default is "interpolated" which is a mix between the two based on white balance. The setting is only enabled if a Dual-Illuminant DCP with interpolation support is selected. +TP_ICM_DCPILLUMINANT_INTERPOLATED;Interpolated TP_ICM_FILEDLGFILTERANY;Any files TP_ICM_FILEDLGFILTERICM;Color profiles TP_ICM_INPUTCAMERAICC;Auto-matched camera-specific color profile @@ -1252,14 +1255,9 @@ TP_ICM_LABEL;Color Management TP_ICM_NOICM;No ICM: sRGB Output TP_ICM_OUTPUTDLGLABEL;Select Output ICC Profile... TP_ICM_OUTPUTPROFILE;Output Profile -TP_ICM_PREFERREDPROFILE;Preferred DCP profile -TP_ICM_PREFERREDPROFILE_1;Daylight -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's tone curve -TP_ICM_TONECURVE_TOOLTIP;Enable to use tone curves that may be contained in DCP profiles. +TP_ICM_TONECURVE_TOOLTIP;Employ the embedded DCP tone curve. The setting is only enabled if the selected DCP has a tone curve. TP_ICM_WORKINGPROFILE;Working Profile TP_IMPULSEDENOISE_LABEL;Impulse Noise Reduction TP_IMPULSEDENOISE_THRESH;Threshold diff --git a/rtengine/colortemp.h b/rtengine/colortemp.h index 7a35069d2..45ff7a7b1 100644 --- a/rtengine/colortemp.h +++ b/rtengine/colortemp.h @@ -62,6 +62,7 @@ class ColorTemp { void update (const double rmul, const double gmul, const double bmul, const double equal) { this->equal = equal; mul2temp (rmul, gmul, bmul, this->equal, temp, green); } void useDefaults (const double equal) { temp = 6504; green = 1.0; this->equal = equal; } // Values copied from procparams.cc + inline Glib::ustring getMethod() { return method; } inline double getTemp () { return temp; } inline double getGreen () { return green; } inline double getEqual () { return equal; } diff --git a/rtengine/dcp.cc b/rtengine/dcp.cc index 461c52f97..dad3e2019 100644 --- a/rtengine/dcp.cc +++ b/rtengine/dcp.cc @@ -30,15 +30,319 @@ using namespace std; using namespace rtengine; using namespace rtexif; +static void Invert3x3(const double (*A)[3], double (*B)[3]) { + + double a00 = A[0][0]; + double a01 = A[0][1]; + double a02 = A[0][2]; + double a10 = A[1][0]; + double a11 = A[1][1]; + double a12 = A[1][2]; + double a20 = A[2][0]; + double a21 = A[2][1]; + double a22 = A[2][2]; + double temp [3][3]; + + temp[0][0] = a11 * a22 - a21 * a12; + temp[0][1] = a21 * a02 - a01 * a22; + temp[0][2] = a01 * a12 - a11 * a02; + temp[1][0] = a20 * a12 - a10 * a22; + temp[1][1] = a00 * a22 - a20 * a02; + temp[1][2] = a10 * a02 - a00 * a12; + temp[2][0] = a10 * a21 - a20 * a11; + temp[2][1] = a20 * a01 - a00 * a21; + temp[2][2] = a00 * a11 - a10 * a01; + + double det = a00 * temp[0][0] + a01 * temp[1][0] + a02 * temp[2][0]; + + if (fabs(det) < 1.0E-10) { + abort(); // can't be inverted, we shouldn't be dealing with such matrices + } + + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + B[j][k] = temp[j][k] / det; + } + } +} + +static void Multiply3x3(const double (*A)[3], const double (*B)[3], double (*C)[3]) { + + // use temp to support having output same as input + double M[3][3]; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + M[i][j] = 0; + for (int k = 0; k < 3; k++) { + M[i][j] += A[i][k] * B[k][j]; + } + } + } + memcpy(C, M, 3 * 3 * sizeof(double)); +} + +static void Multiply3x3_v3(const double (*A)[3], const double B[3], double C[3]) { + + // use temp to support having output same as input + double M[3] = { 0, 0, 0 }; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + M[i] += A[i][j] * B[j]; + } + } + memcpy(C, M, 3 * sizeof(double)); +} + +static void Mix3x3(const double (*A)[3], double mulA, const double (*B)[3], double mulB, double (*C)[3]) { + + double M[3][3]; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + M[i][j] = A[i][j] * mulA + B[i][j] * mulB; + } + } + memcpy(C, M, 3 * 3 * sizeof(double)); +} + +static void MapWhiteMatrix(const double white1[3], const double white2[3], double (*B)[3]) { + + // code adapted from dng_color_spec::MapWhiteMatrix + + // Use the linearized Bradford adaptation matrix. + double Mb[3][3] = { { 0.8951, 0.2664, -0.1614 }, { -0.7502, 1.7135, 0.0367 }, { 0.0389, -0.0685, 1.0296 }}; + + double w1[3]; + Multiply3x3_v3(Mb, white1, w1); + double w2[3]; + Multiply3x3_v3(Mb, white2, w2); + + // Negative white coordinates are kind of meaningless. + w1[0] = std::max(w1[0], 0.0); + w1[1] = std::max(w1[1], 0.0); + w1[2] = std::max(w1[2], 0.0); + w2[0] = std::max(w2[0], 0.0); + w2[1] = std::max(w2[1], 0.0); + w2[2] = std::max(w2[2], 0.0); + + // Limit scaling to something reasonable. + double A[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + A[0][0] = std::max(0.1, std::min(w1[0] > 0.0 ? w2[0] / w1[0] : 10.0, 10.0)); + A[1][1] = std::max(0.1, std::min(w1[1] > 0.0 ? w2[1] / w1[1] : 10.0, 10.0)); + A[2][2] = std::max(0.1, std::min(w1[2] > 0.0 ? w2[2] / w1[2] : 10.0, 10.0)); + + double temp[3][3]; + Invert3x3(Mb, temp); + Multiply3x3(temp, A, temp); + Multiply3x3(temp, Mb, B); +} + +enum dngCalibrationIlluminant { + lsUnknown = 0, + lsDaylight = 1, + lsFluorescent = 2, + lsTungsten = 3, + lsFlash = 4, + lsFineWeather = 9, + lsCloudyWeather = 10, + lsShade = 11, + lsDaylightFluorescent = 12, // D 5700 - 7100K + lsDayWhiteFluorescent = 13, // N 4600 - 5500K + lsCoolWhiteFluorescent = 14, // W 3800 - 4500K + lsWhiteFluorescent = 15, // WW 3250 - 3800K + lsWarmWhiteFluorescent = 16, // L 2600 - 3250K + lsStandardLightA = 17, + lsStandardLightB = 18, + lsStandardLightC = 19, + lsD55 = 20, + lsD65 = 21, + lsD75 = 22, + lsD50 = 23, + lsISOStudioTungsten = 24, + lsOther = 255 +}; + +// should probably be moved to colortemp.cc +static double calibrationIlluminantToTemperature(int light) { + + // these temperatures are those found in DNG SDK reference code. + switch (light) { + case lsStandardLightA: + case lsTungsten: + return 2850.0; + case lsISOStudioTungsten: + return 3200.0; + case lsD50: + return 5000.0; + case lsD55: + case lsDaylight: + case lsFineWeather: + case lsFlash: + case lsStandardLightB: + return 5500.0; + case lsD65: + case lsStandardLightC: + case lsCloudyWeather: + return 6500.0; + case lsD75: + case lsShade: + return 7500.0; + case lsDaylightFluorescent: + return (5700.0 + 7100.0) * 0.5; + case lsDayWhiteFluorescent: + return (4600.0 + 5500.0) * 0.5; + case lsCoolWhiteFluorescent: + case lsFluorescent: + return (3800.0 + 4500.0) * 0.5; + case lsWhiteFluorescent: + return (3250.0 + 3800.0) * 0.5; + case lsWarmWhiteFluorescent: + return (2600.0 + 3250.0) * 0.5; + default: + return 0.0; + } +} + +void DCPProfile::MakeXYZCAM(ColorTemp &wb, int preferredIlluminant, double (*mXYZCAM)[3]) const +{ + // code adapted from dng_color_spec::FindXYZtoCamera + // note that we do not support monochrome or colorplanes > 3 (no reductionMatrix support) + // we do not support cameracalibration either + + bool hasFwd1 = hasForwardMatrix1; + bool hasFwd2 = hasForwardMatrix2; + bool hasCol1 = hasColorMatrix1; + bool hasCol2 = hasColorMatrix2; + if (preferredIlluminant == 1) { + if (hasFwd1) hasFwd2 = false; + if (hasCol1) hasCol2 = false; + } else if (preferredIlluminant == 2) { + if (hasFwd2) hasFwd1 = false; + if (hasCol2) hasCol1 = false; + } + + // mix if we have two matrices + double mix; + if (wb.getTemp() <= temperature1) { + mix = 1.0; + } else if (wb.getTemp() >= temperature2) { + mix = 0.0; + } else { + double invT = 1.0 / wb.getTemp(); + mix = (invT - (1.0 / temperature2)) / ((1.0 / temperature1) - (1.0 / temperature2)); + } + + if (hasFwd1 || hasFwd2) { + // always prefer ForwardMatrix ahead of ColorMatrix + double mFwd[3][3]; + if (hasFwd1 && hasFwd2) { + // interpolate + if (mix >= 1.0) { + memcpy(mFwd, mForwardMatrix1, sizeof(mFwd)); + } else if (mix <= 0.0) { + memcpy(mFwd, mForwardMatrix2, sizeof(mFwd)); + } else { + Mix3x3(mForwardMatrix1, mix, mForwardMatrix2, 1.0 - mix, mFwd); + } + } else if (hasFwd1) { + memcpy(mFwd, mForwardMatrix1, sizeof(mFwd)); + } else { + memcpy(mFwd, mForwardMatrix2, sizeof(mFwd)); + } + ConvertDNGForwardMatrix2XYZCAM(mFwd,mXYZCAM,wb); + } else { + // Colormatrix + double mCol[3][3]; + if (hasCol1 && hasCol2) { + // interpolate + if (mix >= 1.0) { + memcpy(mCol, mColorMatrix1, sizeof(mCol)); + } else if (mix <= 0.0) { + memcpy(mCol, mColorMatrix2, sizeof(mCol)); + } else { + Mix3x3(mColorMatrix1, mix, mColorMatrix2, 1.0 - mix, mCol); + } + } else if (hasCol1) { + memcpy(mCol, mColorMatrix1, sizeof(mCol)); + } else { + memcpy(mCol, mColorMatrix2, sizeof(mCol)); + } + ConvertDNGMatrix2XYZCAM(mCol,mXYZCAM); + } +} + +const DCPProfile::HSBModify* DCPProfile::MakeHueSatMap(ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const { + + *deleteHandle = NULL; + if (!aDeltas1) { + return NULL; + } + if (!aDeltas2) { + return aDeltas1; + } + + if (preferredIlluminant == 1) { + return aDeltas1; + } else if (preferredIlluminant == 2) { + return aDeltas2; + } + + // Interpolate based on color temperature. + if (temperature1 <= 0.0 || temperature2 <= 0.0 || temperature1 == temperature2) { + return aDeltas1; + } + bool reverseOrder = temperature1 > temperature2; + double t1, t2; + if (reverseOrder) { + t1 = temperature2; + t2 = temperature1; + } else { + t1 = temperature1; + t2 = temperature2; + } + + double mix; + if (wb.getTemp() <= t1) { + mix = 1.0; + } else if (wb.getTemp() >= t2) { + mix = 0.0; + } else { + double invT = 1.0 / wb.getTemp(); + mix = (invT - (1.0 / t2)) / ((1.0 / t1) - (1.0 / t2)); + } + + if (reverseOrder) { + mix = 1.0 - mix; + } + + if (mix >= 1.0) { + return aDeltas1; + } else if (mix <= 0.0) { + return aDeltas2; + } + + // Interpolate between the tables. + HSBModify *aDeltas = new HSBModify[DeltaInfo.iArrayCount]; + *deleteHandle = aDeltas; + float w1 = (float)mix; + float w2 = 1.0f - (float)mix; + for (int i = 0; i < DeltaInfo.iArrayCount; i++) { + aDeltas[i].fHueShift = w1 * aDeltas1[i].fHueShift + w2 * aDeltas2[i].fHueShift; + aDeltas[i].fSatScale = w1 * aDeltas1[i].fSatScale + w2 * aDeltas2[i].fSatScale; + aDeltas[i].fValScale = w1 * aDeltas1[i].fValScale + w2 * aDeltas2[i].fValScale; + } + return aDeltas; +} + DCPProfile::DCPProfile(Glib::ustring fname, bool isRTProfile) { const int TIFFFloatSize=4; const int TagColorMatrix1=50721, TagColorMatrix2=50722, TagProfileHueSatMapDims=50937; + const int TagForwardMatrix1=50964, TagForwardMatrix2=50965; 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; + aDeltas1=aDeltas2=aLookTable=NULL; FILE *pFile = safe_g_fopen(fname, "rb"); @@ -46,47 +350,106 @@ DCPProfile::DCPProfile(Glib::ustring fname, bool isRTProfile) { Tag* tag = tagDir->getTag(TagCalibrationIlluminant1); iLightSource1 = (tag!=NULL ? tag->toInt(0,rtexif::SHORT) : -1); tag = tagDir->getTag(TagCalibrationIlluminant2); iLightSource2 = (tag!=NULL ? tag->toInt(0,rtexif::SHORT) : -1); + temperature1 = calibrationIlluminantToTemperature(iLightSource1); + temperature2 = calibrationIlluminantToTemperature(iLightSource2); bool hasSecondHueSat = tagDir->getTag(TagProfileHueSatMapData2)!=NULL; // some profiles have two matrices, but just one huesat + // Fetch Forward Matrices, if any + hasForwardMatrix1 = false; + hasForwardMatrix2 = false; + hasColorMatrix1 = false; + hasColorMatrix2 = false; + hasToneCurve = false; + tag = tagDir->getTag(TagForwardMatrix1); + if (tag) { + hasForwardMatrix1 = true; + for (int row=0;row<3;row++) { + for (int col=0;col<3;col++) { + mForwardMatrix1[col][row]=(float)tag->toDouble((col+row*3)*8); + } + } + } + tag = tagDir->getTag(TagForwardMatrix2); + if (tag) { + hasForwardMatrix2 = true; + for (int row=0;row<3;row++) { + for (int col=0;col<3;col++) { + mForwardMatrix2[col][row]=(float)tag->toDouble((col+row*3)*8); + } + } + } + // Color Matrix (1 is always there) tag = tagDir->getTag(TagColorMatrix1); + if (!tag) { + // FIXME: better error handling + fprintf(stderr, "Bad DCP, no ColorMatrix1\n"); + abort(); + } + hasColorMatrix1 = true; for (int row=0;row<3;row++) { for (int col=0;col<3;col++) { mColorMatrix1[col][row]=(float)tag->toDouble((col+row*3)*8); } } - ConvertDNGMatrix2XYZCAM(mColorMatrix1,mXYZCAM1); - // LUT profile? Divisions counts - bool useSimpleLookup=false; - tag = tagDir->getTag(TagProfileHueSatMapDims); - if (tag==NULL) { - tag=tagDir->getTag(TagProfileLookTableDims); - useSimpleLookup=true; + tag=tagDir->getTag(TagProfileLookTableDims); + if (tag!=NULL) { + LookInfo.iHueDivisions=tag->toInt(0); LookInfo.iSatDivisions=tag->toInt(4); LookInfo.iValDivisions=tag->toInt(8); + + tag = tagDir->getTag(TagProfileLookTableData); + LookInfo.iArrayCount = tag->getCount()/3; + + aLookTable =new HSBModify[LookInfo.iArrayCount]; + + for (int i=0;itoDouble((i*3)*TIFFFloatSize); + aLookTable[i].fSatScale=tag->toDouble((i*3+1)*TIFFFloatSize); + aLookTable[i].fValScale=tag->toDouble((i*3+2)*TIFFFloatSize); + } + + // precalculated constants for table application + LookInfo.pc.hScale = (LookInfo.iHueDivisions < 2) ? 0.0f : (LookInfo.iHueDivisions * (1.0f / 6.0f)); + LookInfo.pc.sScale = (float) (LookInfo.iSatDivisions - 1); + LookInfo.pc.vScale = (float) (LookInfo.iValDivisions - 1); + LookInfo.pc.maxHueIndex0 = LookInfo.iHueDivisions - 1; + LookInfo.pc.maxSatIndex0 = LookInfo.iSatDivisions - 2; + LookInfo.pc.maxValIndex0 = LookInfo.iValDivisions - 2; + LookInfo.pc.hueStep = LookInfo.iSatDivisions; + LookInfo.pc.valStep = LookInfo.iHueDivisions * LookInfo.pc.hueStep; } + tag = tagDir->getTag(TagProfileHueSatMapDims); if (tag!=NULL) { - iHueDivisions=tag->toInt(0); iSatDivisions=tag->toInt(4); iValDivisions=tag->toInt(8); + DeltaInfo.iHueDivisions=tag->toInt(0); DeltaInfo.iSatDivisions=tag->toInt(4); DeltaInfo.iValDivisions=tag->toInt(8); - // Saturation maps. Need to be unwinded. - tag = tagDir->getTag(useSimpleLookup ? TagProfileLookTableData : TagProfileHueSatMapData1); - iArrayCount = tag->getCount()/3; + tag = tagDir->getTag(TagProfileHueSatMapData1); + DeltaInfo.iArrayCount = tag->getCount()/3; - aDeltas1=new HSBModify[iArrayCount]; + aDeltas1=new HSBModify[DeltaInfo.iArrayCount]; - for (int i=0;itoDouble((i*3)*TIFFFloatSize); aDeltas1[i].fSatScale=tag->toDouble((i*3+1)*TIFFFloatSize); aDeltas1[i].fValScale=tag->toDouble((i*3+2)*TIFFFloatSize); } + + DeltaInfo.pc.hScale = (DeltaInfo.iHueDivisions < 2) ? 0.0f : (DeltaInfo.iHueDivisions * (1.0f / 6.0f)); + DeltaInfo.pc.sScale = (float) (DeltaInfo.iSatDivisions - 1); + DeltaInfo.pc.vScale = (float) (DeltaInfo.iValDivisions - 1); + DeltaInfo.pc.maxHueIndex0 = DeltaInfo.iHueDivisions - 1; + DeltaInfo.pc.maxSatIndex0 = DeltaInfo.iSatDivisions - 2; + DeltaInfo.pc.maxValIndex0 = DeltaInfo.iValDivisions - 2; + DeltaInfo.pc.hueStep = DeltaInfo.iSatDivisions; + DeltaInfo.pc.valStep = DeltaInfo.iHueDivisions * DeltaInfo.pc.hueStep; } - // For second profile, copy everything from first profile is no better data is available if (iLightSource2!=-1) { // Second matrix tag = tagDir->getTag(TagColorMatrix2); + hasColorMatrix2 = true; for (int row=0;row<3;row++) { for (int col=0;col<3;col++) { @@ -94,25 +457,18 @@ DCPProfile::DCPProfile(Glib::ustring fname, bool isRTProfile) { } } - ConvertDNGMatrix2XYZCAM(mColorMatrix2,mXYZCAM2); - - // Second huesatmap, or copy of first + // Second huesatmap if (hasSecondHueSat) { - aDeltas2=new HSBModify[iArrayCount]; + aDeltas2=new HSBModify[DeltaInfo.iArrayCount]; // Saturation maps. Need to be unwinded. tag = tagDir->getTag(TagProfileHueSatMapData2); - for (int i=0;itoDouble((i*3)*TIFFFloatSize); aDeltas2[i].fSatScale=tag->toDouble((i*3+1)*TIFFFloatSize); aDeltas2[i].fValScale=tag->toDouble((i*3+2)*TIFFFloatSize); } - } else { - if (aDeltas1!=NULL) { - aDeltas2=new HSBModify[iArrayCount]; - for (int i=0;igetCount();i++) cPoints.push_back( tag->toDouble(i*TIFFFloatSize) ); + bool curve_is_linear = true; + for (int i=0;igetCount(); i+= 2) { + double x = tag->toDouble((i+0)*TIFFFloatSize); + double y = tag->toDouble((i+1)*TIFFFloatSize); + if (x != y) { + curve_is_linear = false; + } + cPoints.push_back( x ); + cPoints.push_back( y ); + } - // Create the curve - DiagonalCurve rawCurve(cPoints, CURVES_MIN_POLY_POINTS); + if (!curve_is_linear) { + // Create the curve + DiagonalCurve rawCurve(cPoints, CURVES_MIN_POLY_POINTS); - toneCurve.Set((Curve*)&rawCurve); + toneCurve.Set((Curve*)&rawCurve); + hasToneCurve = true; + } + } + + willInterpolate = false; + if (hasForwardMatrix1) { + if (hasForwardMatrix2) { + if (memcmp(mForwardMatrix1, mForwardMatrix2, sizeof(mForwardMatrix1)) != 0) { + // common that forward matrices are the same! + willInterpolate = true; + } + if (aDeltas1 && aDeltas2) { + // we assume tables are different + willInterpolate = true; + } + } + } else if (hasColorMatrix1 && hasColorMatrix2) { + if (memcmp(mColorMatrix1, mColorMatrix2, sizeof(mColorMatrix1)) != 0) { + willInterpolate = true; + } + if (aDeltas1 && aDeltas2) { + willInterpolate = true; + } } if (pFile!=NULL) fclose(pFile); @@ -141,7 +530,7 @@ DCPProfile::~DCPProfile() { } // Convert DNG color matrix to xyz_cam compatible matrix -void DCPProfile::ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) { +void DCPProfile::ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) const { int i,j,k; double cam_xyz[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; @@ -177,60 +566,181 @@ void DCPProfile::ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double mXYZCAM[i][j] += xyz_sRGB[i][k] * rgb_cam[k][j]; } +void DCPProfile::HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, const float hs, const float ss, const float vs, float &h, float &s, float &v) const { -const DCPProfile::HSBModify* DCPProfile::GetBestProfile(DCPLightType preferredProfile, double (*mXYZCAM)[3]) const { - bool use2=false; + // Apply the HueSatMap. Ported from Adobes reference implementation + float hueShift, satScale, valScale; - if (iLightSource2!=-1) { - DCPLightType t1=GetLightType(iLightSource1); DCPLightType t2=GetLightType(iLightSource2); + if (ti.iValDivisions < 2) // Optimize most common case of "2.5D" table. + { + float hScaled = hs * ti.pc.hScale; + float sScaled = ss * ti.pc.sScale; - // usually second is the daylight (default if nothing else found) - if (t2==Daylight) use2=true; + int hIndex0 = max((int)hScaled, 0); + int sIndex0 = max(min((int)sScaled,ti.pc.maxSatIndex0),0); - switch (preferredProfile) { - case Tungsten: - if (t1==Tungsten) use2=false; else if (t2==Tungsten) use2=true; - break; + int hIndex1 = hIndex0 + 1; - 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; - - default: break; // e.g. Daylight + if (hIndex0 >= ti.pc.maxHueIndex0) + { + hIndex0 = ti.pc.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 * ti.pc.hueStep + sIndex0; + const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * ti.pc.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 * ti.pc.hScale; + float sScaled = ss * ti.pc.sScale; + float vScaled = vs * ti.pc.vScale; + + int hIndex0 = (int) hScaled; + int sIndex0 = max(min((int)sScaled,ti.pc.maxSatIndex0),0); + int vIndex0 = max(min((int)vScaled,ti.pc.maxValIndex0),0); + + int hIndex1 = hIndex0 + 1; + + if (hIndex0 >= ti.pc.maxHueIndex0) + { + hIndex0 = ti.pc.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 * ti.pc.valStep + hIndex0 * ti.pc.hueStep + sIndex0; + + const HSBModify *entry01 = entry00 + (hIndex1 - hIndex0) * ti.pc.hueStep; + + const HSBModify *entry10 = entry00 + ti.pc.valStep; + const HSBModify *entry11 = entry01 + ti.pc.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; } - // printf("DCP using LightSource %i: %i for requested %i\n", use2?2:1, use2?iLightSource2:iLightSource1, (int)preferredProfile); + hueShift *= (6.0f / 360.0f); // Convert to internal hue range. - for (int row=0;row<3;row++) { - for (int col=0;col<3;col++) { - mXYZCAM[col][row]= (use2 ? mXYZCAM2[col][row] : mXYZCAM1[col][row]); - } - } - - return use2?aDeltas2:aDeltas1; + h += hueShift; + s *= satScale; // no clipping here, we are RT float :-) + v *= valScale; } -DCPLightType DCPProfile::GetLightType(short iLightSource) const { - if (iLightSource==3 || iLightSource==17 || iLightSource==24) return Tungsten; - if (iLightSource==2 || (iLightSource>=12 && iLightSource<=15)) return Fluorescent; - if (iLightSource==4) return Flash; - return Daylight; +// Convert DNG forward matrix to xyz_cam compatible matrix +void DCPProfile::ConvertDNGForwardMatrix2XYZCAM(const double (*mForwardMatrix)[3], double (*mXYZCAM)[3], ColorTemp &wb) const { + + // Convert ForwardMatrix (white-balanced CameraRGB -> XYZ D50 matrix) + // into a ColorMatrix (XYZ -> CameraRGB) + + double X, Z; + ColorTemp::temp2mulxyz(wb.getTemp(), wb.getGreen(), wb.getMethod(), X, Z); + + const double white_xyz[3] = { X, 1, Z }; + const double white_d50[3] = { 0.3457, 0.3585, 0.2958 }; // D50 + + // Cancel out the white balance to get a CameraRGB -> XYZ D50 matrixx (CameraToPCS in dng terminology) + double whiteDiag[3][3] = {{white_xyz[0], 0, 0}, {0, white_xyz[1], 0}, {0, 0, white_xyz[2]}}; + double whiteDiagInv[3][3]; + Invert3x3(whiteDiag, whiteDiagInv); + double rgb2xyzD50[3][3]; + Multiply3x3(mForwardMatrix, whiteDiagInv, rgb2xyzD50); + + // Through chromatic adaptation convert XYZ D50 to XYZ camera white + double whiteMatrix[3][3]; + MapWhiteMatrix(white_d50, white_xyz, whiteMatrix); + double rgb2xyz[3][3]; + Multiply3x3(rgb2xyzD50, whiteMatrix, rgb2xyz); + + // Now we have CameraRGB -> XYZ, invert so we get XYZ -> CameraRGB (ColorMatrix format) + double dngColorMatrix[3][3]; + Invert3x3(rgb2xyz, dngColorMatrix); + + // now we can run the ordinary ColorMatrix conversion + ConvertDNGMatrix2XYZCAM(dngColorMatrix, mXYZCAM); } -void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, float rawWhiteFac, bool useToneCurve) const { +void DCPProfile::Apply(Imagefloat *pImg, int preferredIlluminant, Glib::ustring workingSpace, ColorTemp &wb, float rawWhiteFac, bool useToneCurve) const { + TMatrix mWork = iccStore->workingSpaceInverseMatrix (workingSpace); - double mXYZCAM[3][3]; - const HSBModify* tableBase=GetBestProfile(preferredProfile,mXYZCAM); + double mXYZCAM[3][3]; + MakeXYZCAM(wb, preferredIlluminant, mXYZCAM); + HSBModify *deleteTableHandle; + const HSBModify *deltaBase = MakeHueSatMap(wb, preferredIlluminant, &deleteTableHandle); - bool hasLUT=(iArrayCount>0); useToneCurve&=toneCurve; + useToneCurve&=toneCurve; - if (!hasLUT && !useToneCurve) { + if (deltaBase == NULL && aLookTable == NULL && !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++) @@ -265,18 +775,6 @@ void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::us for (int k=0; k<3; k++) m2Work[i][j] += mWork[i][k] * xyz_prophoto[k][j]; - // Preperations for LUT - float hScale = (iHueDivisions < 2) ? 0.0f : (iHueDivisions * (1.0f / 6.0f)); - float sScale = (float) (iSatDivisions - 1); - float vScale = (float) (iValDivisions - 1); - - int maxHueIndex0 = iHueDivisions - 1; - int maxSatIndex0 = iSatDivisions - 2; - int maxValIndex0 = iValDivisions - 2; - - int hueStep = iSatDivisions; - int valStep = iHueDivisions * hueStep; - bool useRawWhite=fabs(rawWhiteFac)>0.001; // Convert to prophoto and apply LUT @@ -289,7 +787,7 @@ 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 (hasLUT && newr>=0 && newg>=0 && newb>=0) { + if ((deltaBase || aLookTable) && newr>=0 && newg>=0 && newb>=0) { Color::rgb2hsv(newr, newg, newb, h , s, v); h*=6.f; // RT calculates in [0,1] @@ -301,141 +799,12 @@ void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::us 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; - } - - 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; + if (deltaBase) { + HSDApply(DeltaInfo, deltaBase, hs, ss, vs, h, s, v); + } + if (aLookTable) { + HSDApply(LookInfo, aLookTable, hs, ss, vs, h, s, v); } - - 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; @@ -453,14 +822,16 @@ void DCPProfile::Apply(Imagefloat *pImg, DCPLightType preferredProfile, Glib::us } } } + + if (deleteTableHandle) delete deleteTableHandle; } // Integer variant is legacy, only used for thumbs. Simply take the matrix here -void DCPProfile::Apply(Image16 *pImg, DCPLightType preferredProfile, Glib::ustring workingSpace, bool useToneCurve) const { +void DCPProfile::Apply(Image16 *pImg, int preferredIlluminant, Glib::ustring workingSpace, ColorTemp &wb, bool useToneCurve) const { TMatrix mWork = iccStore->workingSpaceInverseMatrix (workingSpace); - double mXYZCAM[3][3]; - // unused //const HSBModify* tableBase=GetBestProfile(preferredProfile,mXYZCAM); + double mXYZCAM[3][3]; + MakeXYZCAM(wb, preferredIlluminant, mXYZCAM); useToneCurve&=toneCurve; diff --git a/rtengine/dcp.h b/rtengine/dcp.h index b7466dbec..b7a3fdbbc 100644 --- a/rtengine/dcp.h +++ b/rtengine/dcp.h @@ -22,15 +22,13 @@ #include "imagefloat.h" #include "curves.h" +#include "colortemp.h" #include "../rtgui/threadutils.h" #include #include #include namespace rtengine { - enum DCPLightType { - Daylight=1, Tungsten=2, Fluorescent=3, Flash=4 - }; class DCPProfile { struct HSBModify @@ -39,30 +37,42 @@ namespace rtengine { float fSatScale; float fValScale; }; + struct HSDTableInfo + { + int iHueDivisions, iSatDivisions, iValDivisions; + int iHueStep, iValStep, iArrayCount; + struct + { + float hScale, sScale, vScale; + int maxHueIndex0, maxSatIndex0, maxValIndex0; + int hueStep, valStep; + } pc; + }; double mColorMatrix1[3][3],mColorMatrix2[3][3]; - double mXYZCAM1[3][3],mXYZCAM2[3][3]; // compatible to RTs xyz_cam - HSBModify *aDeltas1,*aDeltas2; + bool hasColorMatrix1, hasColorMatrix2, hasForwardMatrix1, hasForwardMatrix2, hasToneCurve, willInterpolate; + double mForwardMatrix1[3][3],mForwardMatrix2[3][3]; + double temperature1, temperature2; + HSBModify *aDeltas1,*aDeltas2,*aLookTable; + HSDTableInfo DeltaInfo,LookInfo; short iLightSource1,iLightSource2; - int iHueDivisions, iSatDivisions, iValDivisions; - - int iHueStep, iValStep, iArrayCount; - AdobeToneCurve toneCurve; - void ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]); - - const HSBModify* GetBestProfile(DCPLightType preferredProfile, double (*mXYZCAM)[3]) const; - - DCPLightType GetLightType(short iLightSource) const; + void MakeXYZCAM(ColorTemp &wb, int preferredIlluminant, double (*mXYZCAM)[3]) const; + const HSBModify* MakeHueSatMap(ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const; + void ConvertDNGMatrix2XYZCAM(const double (*mColorMatrix)[3], double (*mXYZCAM)[3]) const; + void ConvertDNGForwardMatrix2XYZCAM(const double (*mForwardMatrix)[3], double (*mXYZCAM)[3], ColorTemp &wb) const; + void HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, const float hs, const float ss, const float vs, float &h, float &s, float &v) const; public: DCPProfile(Glib::ustring fname, bool isRTProfile); ~DCPProfile(); - 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; + bool getHasToneCurve() { return hasToneCurve; } + void getIlluminants(int &i1, double &temp1, int &i2, double &temp2, bool &willInterpolate_) { i1 = iLightSource1; i2 = iLightSource2; temp1 = temperature1, temp2 = temperature2; willInterpolate_ = willInterpolate; }; + void Apply(Imagefloat *pImg, int preferredIlluminant, Glib::ustring workingSpace, ColorTemp &wb, float rawWhiteFac=1, bool useToneCurve=false) const; + void Apply(Image16 *pImg, int preferredIlluminant, Glib::ustring workingSpace, ColorTemp &wb, bool useToneCurve) const; }; class DCPStore { diff --git a/rtengine/procevents.h b/rtengine/procevents.h index 019fce0bc..0bcd296f5 100644 --- a/rtengine/procevents.h +++ b/rtengine/procevents.h @@ -70,7 +70,7 @@ enum ProcEvent { EvCDNEnabled=45, // obsolete EvBlendCMSMatrix=46, EvDCPToneCurve=47, - EvPrefProfile=48, + EvDCPIlluminant=48, EvSHEnabled=49, EvSHHighlights=50, EvSHShadows=51, diff --git a/rtengine/procparams.cc b/rtengine/procparams.cc index d78d429d8..3ad9245d2 100644 --- a/rtengine/procparams.cc +++ b/rtengine/procparams.cc @@ -408,7 +408,7 @@ void ProcParams::setDefaults () { icm.input = "(cameraICC)"; icm.blendCMSMatrix = false; icm.toneCurve = false; - icm.preferredProfile = (short)rtengine::Daylight; + icm.dcpIlluminant = 0; icm.working = "sRGB"; icm.output = "sRGB"; icm.gamma = "default"; @@ -966,7 +966,7 @@ int ProcParams::save (Glib::ustring fname, Glib::ustring fname2, bool fnameAbsol if (!pedited || pedited->icm.input) keyFile.set_string ("Color Management", "InputProfile", relativePathIfInside(fname, fnameAbsolute, 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.dcpIlluminant) keyFile.set_integer ("Color Management", "DCPIlluminant", icm.dcpIlluminant); if (!pedited || pedited->icm.working) keyFile.set_string ("Color Management", "WorkingProfile", icm.working); if (!pedited || pedited->icm.output) keyFile.set_string ("Color Management", "OutputProfile", icm.output); if (!pedited || pedited->icm.gamma) keyFile.set_string ("Color Management", "Gammafree", icm.gamma); @@ -1573,7 +1573,7 @@ if (keyFile.has_group ("Color Management")) { if (keyFile.has_key ("Color Management", "InputProfile")) { icm.input = expandRelativePath(fname, "file:", 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", "DCPIlluminant")) { icm.dcpIlluminant = keyFile.get_integer ("Color Management", "DCPIlluminant"); if (pedited) pedited->icm.dcpIlluminant = true; } if (keyFile.has_key ("Color Management", "WorkingProfile")) { icm.working = keyFile.get_string ("Color Management", "WorkingProfile"); if (pedited) pedited->icm.working = true; } if (keyFile.has_key ("Color Management", "OutputProfile")) { icm.output = keyFile.get_string ("Color Management", "OutputProfile"); if (pedited) pedited->icm.output = true; } if (keyFile.has_key ("Color Management", "Gammafree")) { icm.gamma = keyFile.get_string ("Color Management", "Gammafree"); if (pedited) pedited->icm.gamfree = true; } @@ -1941,7 +1941,7 @@ bool ProcParams::operator== (const ProcParams& other) { && icm.input == other.icm.input && icm.toneCurve == other.icm.toneCurve && icm.blendCMSMatrix == other.icm.blendCMSMatrix - && icm.preferredProfile == other.icm.preferredProfile + && icm.dcpIlluminant == other.icm.dcpIlluminant && icm.working == other.icm.working && icm.output == other.icm.output && icm.gamma == other.icm.gamma diff --git a/rtengine/procparams.h b/rtengine/procparams.h index ef50f6b2a..20e12e6e1 100644 --- a/rtengine/procparams.h +++ b/rtengine/procparams.h @@ -706,7 +706,7 @@ class ColorManagementParams { Glib::ustring input; bool toneCurve; bool blendCMSMatrix; - short preferredProfile; + int dcpIlluminant; Glib::ustring working; Glib::ustring output; static const Glib::ustring NoICMString; diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 08676c803..9d0bddfc7 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -426,7 +426,7 @@ void RawImageSource::getImage (ColorTemp ctemp, int tran, Imagefloat* image, Pre } void RawImageSource::convertColorSpace(Imagefloat* image, ColorManagementParams cmp, RAWParams raw) { - colorSpaceConversion (image, cmp, raw, embProfile, camProfile, imatrices.xyz_cam, (static_cast(getMetaData()))->getCamera()); + colorSpaceConversion (image, cmp, wb, raw, embProfile, camProfile, imatrices.xyz_cam, (static_cast(getMetaData()))->getCamera()); } //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1647,7 +1647,7 @@ void RawImageSource::getProfilePreprocParams(cmsHPROFILE in, float& gammaFac, fl //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // Converts raw image including ICC input profile to working space - floating point version -void RawImageSource::colorSpaceConversion (Imagefloat* im, ColorManagementParams &cmp, float rawWhitePoint, cmsHPROFILE embedded, cmsHPROFILE camprofile, double camMatrix[3][3], const std::string &camName) { +void RawImageSource::colorSpaceConversion (Imagefloat* im, ColorManagementParams &cmp, ColorTemp &wb, float rawWhitePoint, cmsHPROFILE embedded, cmsHPROFILE camprofile, double camMatrix[3][3], const std::string &camName) { //MyTime t1, t2, t3; //t1.set (); @@ -1657,7 +1657,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, rawWhitePoint, cmp.toneCurve); + dcpProf->Apply(im, cmp.dcpIlluminant, cmp.working, wb, rawWhitePoint, cmp.toneCurve); } else { // Calculate matrix for direct conversion raw>working space TMatrix work = iccStore->workingSpaceInverseMatrix (cmp.working); diff --git a/rtengine/rawimagesource.h b/rtengine/rawimagesource.h index b61ffd1fc..5ecf11b62 100644 --- a/rtengine/rawimagesource.h +++ b/rtengine/rawimagesource.h @@ -66,7 +66,7 @@ class RawImageSource : public ImageSource { static LUTf invGrad; // for fast_demosaic static LUTf initInvGrad (); static bool findInputProfile(Glib::ustring inProfile, cmsHPROFILE embedded, std::string camName, DCPProfile **dcpProf, cmsHPROFILE& in); - static void colorSpaceConversion (Imagefloat* im, ColorManagementParams &cmp, float rawWhitePoint, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], const std::string &camName); + static void colorSpaceConversion (Imagefloat* im, ColorManagementParams &cmp, ColorTemp &wb, float rawWhitePoint, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], const std::string &camName); protected: MyMutex getImageMutex; // locks getImage @@ -176,11 +176,11 @@ class RawImageSource : public ImageSource { void convertColorSpace(Imagefloat* image, ColorManagementParams cmp, RAWParams raw); //static void colorSpaceConversion16 (Image16* im, ColorManagementParams cmp, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], std::string camName); - static void colorSpaceConversion (Imagefloat* im, ColorManagementParams cmp, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], std::string camName) { - colorSpaceConversion (im, cmp, 0.0f, embedded, camprofile, cam, camName); + static void colorSpaceConversion (Imagefloat* im, ColorManagementParams cmp, ColorTemp &wb, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], std::string camName) { + colorSpaceConversion (im, cmp, wb, 0.0f, embedded, camprofile, cam, camName); } - static void colorSpaceConversion (Imagefloat* im, ColorManagementParams cmp, RAWParams raw, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], std::string camName) { - colorSpaceConversion (im, cmp, float(raw.expos), embedded, camprofile, cam, camName); + static void colorSpaceConversion (Imagefloat* im, ColorManagementParams cmp, ColorTemp &wb, RAWParams raw, cmsHPROFILE embedded, cmsHPROFILE camprofile, double cam[3][3], std::string camName) { + colorSpaceConversion (im, cmp, wb, float(raw.expos), embedded, camprofile, cam, camName); } static void inverse33 (const double (*coeff)[3], double (*icoeff)[3]); diff --git a/rtengine/refreshmap.cc b/rtengine/refreshmap.cc index 30bf4bc78..453b7bcea 100644 --- a/rtengine/refreshmap.cc +++ b/rtengine/refreshmap.cc @@ -68,7 +68,7 @@ RGBCURVE, // EvToneCurveMode2, 0, // EvCDNEnabled:obsolete, ALL, // EvBlendCMSMatrix, ALL, // EvDCPToneCurve, -ALL, // EvPrefProfile, +ALL, // EvDCPIlluminant, RETINEX, // EvSHEnabled, RGBCURVE, // EvSHHighlights, RGBCURVE, // EvSHShadows, diff --git a/rtengine/rtengine.h b/rtengine/rtengine.h index 79c18815e..1a7a9af91 100644 --- a/rtengine/rtengine.h +++ b/rtengine/rtengine.h @@ -420,6 +420,7 @@ namespace rtengine { * @param img is the result of the last ProcessingJob * @return the next ProcessingJob to process */ virtual ProcessingJob* imageReady (IImage16* img) =0; + virtual void error(Glib::ustring message) =0; }; /** This function performs all the image processinf steps corresponding to the given ProcessingJob. It runs in the background, thus it returns immediately, * When it finishes, it calls the BatchProcessingListener with the resulting image and asks for the next job. It the listener gives a new job, it goes on diff --git a/rtengine/rtthumbnail.cc b/rtengine/rtthumbnail.cc index 8b54c5711..fec6db7f4 100644 --- a/rtengine/rtthumbnail.cc +++ b/rtengine/rtthumbnail.cc @@ -679,7 +679,7 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, int rhei // perform color space transformation if (isRaw) - RawImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, camProfile, cam2xyz, camName ); + RawImageSource::colorSpaceConversion (baseImg, params.icm, currWB, embProfile, camProfile, cam2xyz, camName ); else StdImageSource::colorSpaceConversion (baseImg, params.icm, embProfile, thumbImg->getSampleFormat()); diff --git a/rtengine/simpleprocess.cc b/rtengine/simpleprocess.cc index 62635fac9..ffdff6162 100644 --- a/rtengine/simpleprocess.cc +++ b/rtengine/simpleprocess.cc @@ -681,9 +681,17 @@ void batchProcessingThread (ProcessingJob* job, BatchProcessingListener* bpl, bo while (currentJob) { int errorCode; IImage16* img = processImage (currentJob, errorCode, bpl, tunnelMetaData); - if (errorCode) + if (errorCode) { bpl->error ("Can not load input image."); - currentJob = bpl->imageReady (img); + currentJob = NULL; + } else { + try { + currentJob = bpl->imageReady (img); + } catch (Glib::Exception& ex) { + bpl->error (ex.what()); + currentJob = NULL; + } + } } } diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc index 5a3eabf51..37365e72e 100644 --- a/rtgui/batchqueue.cc +++ b/rtgui/batchqueue.cc @@ -594,7 +594,7 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) { err = img->saveAsJPEG (fname, saveFormat.jpegQuality, saveFormat.jpegSubSamp); img->free (); - if (err) throw "Unable to save output file"; + if (err) throw Glib::FileError(Glib::FileError::FAILED, "Unable to save output file"); if (saveFormat.saveParams) { // We keep the extension to avoid overwriting the profile when we have @@ -848,12 +848,14 @@ struct NLParams { BatchQueueListener* listener; int qsize; bool queueEmptied; + bool queueError; + Glib::ustring queueErrorMessage; }; int bqnotifylistenerUI (void* data) { GThreadLock lock; // All GUI acces from idle_add callbacks or separate thread HAVE to be protected NLParams* params = static_cast(data); - params->listener->queueSizeChanged (params->qsize, params->queueEmptied); + params->listener->queueSizeChanged (params->qsize, params->queueEmptied, params->queueError, params->queueErrorMessage); delete params; return 0; } @@ -871,6 +873,7 @@ void BatchQueue::notifyListener (bool queueEmptied) { params->qsize = fd.size(); } params->queueEmptied = queueEmptied; + params->queueError = false; g_idle_add (bqnotifylistenerUI, params); } } @@ -879,3 +882,15 @@ void BatchQueue::redrawNeeded (LWButton* button) { GThreadLock lock; queue_draw (); } + +void BatchQueue::error (Glib::ustring msg) { + + if (listener) { + NLParams* params = new NLParams; + params->listener = listener; + params->queueEmptied = false; + params->queueError = true; + params->queueErrorMessage = msg; + g_idle_add (bqnotifylistenerUI, params); + } +} diff --git a/rtgui/batchqueue.h b/rtgui/batchqueue.h old mode 100644 new mode 100755 index 68de4fbbf..9a3571b54 --- a/rtgui/batchqueue.h +++ b/rtgui/batchqueue.h @@ -30,7 +30,7 @@ class BatchQueueListener { public: virtual ~BatchQueueListener () {} - virtual void queueSizeChanged (int qsize, bool queueEmptied) =0; + virtual void queueSizeChanged (int qsize, bool queueEmptied, bool queueError, Glib::ustring queueErrorMessage) =0; virtual bool canStartNext () =0; }; @@ -86,6 +86,7 @@ class BatchQueue : public ThumbBrowserBase, } rtengine::ProcessingJob* imageReady (rtengine::IImage16* img); + void error (Glib::ustring msg); void setProgress (double p); void rightClicked (ThumbBrowserEntryBase* entry); bool keyPressed (GdkEventKey* event); diff --git a/rtgui/batchqueuepanel.cc b/rtgui/batchqueuepanel.cc index b874aa242..77bd3f690 100644 --- a/rtgui/batchqueuepanel.cc +++ b/rtgui/batchqueuepanel.cc @@ -223,17 +223,21 @@ void BatchQueuePanel::updateTab (int qsize) } } -void BatchQueuePanel::queueSizeChanged (int qsize, bool queueEmptied) +void BatchQueuePanel::queueSizeChanged (int qsize, bool queueEmptied, bool queueError, Glib::ustring queueErrorMessage) { updateTab ( qsize); - if (queueEmptied) { + if (queueEmptied || queueError) { stopBatchProc (); fdir->set_sensitive (true); fformat->set_sensitive (true); SoundManager::playSoundAsync(options.sndBatchQueueDone); } + if (queueError) { + Gtk::MessageDialog msgd (queueErrorMessage, true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + msgd.run (); + } } void BatchQueuePanel::startBatchProc () { diff --git a/rtgui/batchqueuepanel.h b/rtgui/batchqueuepanel.h index 782368872..1adb3c3be 100644 --- a/rtgui/batchqueuepanel.h +++ b/rtgui/batchqueuepanel.h @@ -59,7 +59,7 @@ class BatchQueuePanel : public Gtk::VBox, void addBatchQueueJobs (std::vector &entries , bool head=false); // batchqueuelistener interface - void queueSizeChanged (int qsize, bool queueEmptied); + void queueSizeChanged (int qsize, bool queueEmptied, bool queueError, Glib::ustring queueErrorMessage); bool canStartNext (); void startBatchProc (); diff --git a/rtgui/icmpanel.cc b/rtgui/icmpanel.cc index 6ae640614..5447e679f 100644 --- a/rtgui/icmpanel.cc +++ b/rtgui/icmpanel.cc @@ -82,16 +82,20 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), Gtk::HBox* hb = Gtk::manage (new Gtk::HBox ()); hb->show (); - Gtk::Label* ppl = Gtk::manage (new Gtk::Label (M("TP_ICM_PREFERREDPROFILE")+":")); - ppl->show (); - prefprof = Gtk::manage (new MyComboBoxText ()); - prefprof->append_text (M("TP_ICM_PREFERREDPROFILE_1")); - prefprof->append_text (M("TP_ICM_PREFERREDPROFILE_2")); - prefprof->append_text (M("TP_ICM_PREFERREDPROFILE_3")); - prefprof->append_text (M("TP_ICM_PREFERREDPROFILE_4")); - prefprof->show (); - hb->pack_start(*ppl, Gtk::PACK_SHRINK, 4); - hb->pack_start(*prefprof); + dcpIllLabel = Gtk::manage (new Gtk::Label ("DCP " + M("TP_ICM_DCPILLUMINANT")+":")); + dcpIllLabel->set_tooltip_text (M("TP_ICM_DCPILLUMINANT_TOOLTIP")); + dcpIllLabel->show (); + dcpIll = Gtk::manage (new MyComboBoxText ()); + dcpIll->set_tooltip_text (M("TP_ICM_DCPILLUMINANT_TOOLTIP")); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT_INTERPOLATED")); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 1"); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 2"); + dcpIll->show (); + dcpTemperatures[0] = 0; + dcpTemperatures[1] = 0; + ignoreDcpSignal = true; + hb->pack_start(*dcpIllLabel, Gtk::PACK_SHRINK, 4); + hb->pack_start(*dcpIll); iVBox->pack_start (*hb, Gtk::PACK_SHRINK, 2); ckbToneCurve = Gtk::manage (new Gtk::CheckButton (M("TP_ICM_TONECURVE"))); @@ -229,7 +233,7 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), wnames->signal_changed().connect( sigc::mem_fun(*this, &ICMPanel::wpChanged) ); onames->signal_changed().connect( sigc::mem_fun(*this, &ICMPanel::opChanged) ); wgamma->signal_changed().connect( sigc::mem_fun(*this, &ICMPanel::gpChanged) ); - prefprof->signal_changed().connect( sigc::mem_fun(*this, &ICMPanel::prefProfChanged) ); + dcpIll->signal_changed().connect( sigc::mem_fun(*this, &ICMPanel::dcpIlluminantChanged) ); gamcsconn = freegamma->signal_toggled().connect ( sigc::mem_fun(*this, &ICMPanel::GamChanged)); tcurveconn = ckbToneCurve->signal_toggled().connect ( sigc::mem_fun(*this, &ICMPanel::toneCurveChanged)); @@ -246,6 +250,100 @@ ICMPanel::ICMPanel () : Gtk::VBox(), FoldableToolPanel(this), iunchanged(NULL), show_all (); } +void ICMPanel::updateDCP (int dcpIlluminant, Glib::ustring dcp_name) { + + if (isBatchMode) { + ckbToneCurve->set_sensitive (true); + dcpIllLabel->set_sensitive (true); + dcpIll->set_sensitive (true); + if (dcpTemperatures[0] != 0 || dcpTemperatures[1] != 0) { + int curr_active = dcpIll->get_active_row_number(); + ignoreDcpSignal = true; + dcpIll->clear_items (); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT_INTERPOLATED")); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 1"); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 2"); + dcpIll->append_text (M("GENERAL_UNCHANGED")); + dcpTemperatures[0] = 0; + dcpTemperatures[1] = 0; + dcpIll->set_active (curr_active); + ignoreDcpSignal = false; + } + if (dcpIll->get_active_row_number() == -1 && dcpIlluminant == -1) { + dcpIll->set_active(0); + } else if (dcpIlluminant >= 0 && dcpIlluminant != dcpIll->get_active_row_number()) { + dcpIll->set_active(dcpIlluminant); + } + dcpIll->set_sensitive (true); + dcpIllLabel->set_sensitive (true); + return; + } + ckbToneCurve->set_sensitive (false); + dcpIllLabel->set_sensitive (false); + dcpIll->set_sensitive (false); + if (ifromfile->get_active() && dcpStore->isValidDCPFileName(dcp_name)) { + DCPProfile* dcp = dcpStore->getProfile(dcp_name, false); + if (dcp) { + if (dcp->getHasToneCurve()) { + ckbToneCurve->set_sensitive (true); + } else { + ckbToneCurve->set_active (false); + } + int i1, i2; + double temp1, temp2; + bool willInterpolate; + dcp->getIlluminants(i1, temp1, i2, temp2, willInterpolate); + if (willInterpolate) { + if (dcpTemperatures[0] != temp1 || dcpTemperatures[1] != temp2) { + char tempstr1[64], tempstr2[64]; + sprintf(tempstr1, "%.0fK", temp1); + sprintf(tempstr2, "%.0fK", temp2); + int curr_active = dcpIll->get_active_row_number(); + ignoreDcpSignal = true; + dcpIll->clear_items (); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT_INTERPOLATED")); + dcpIll->append_text (tempstr1); + dcpIll->append_text (tempstr2); + dcpTemperatures[0] = temp1; + dcpTemperatures[1] = temp2; + dcpIll->set_active (curr_active); + ignoreDcpSignal = false; + } + if (dcpIlluminant > 2) { + dcpIlluminant = 0; + } + if (dcpIll->get_active_row_number() == -1 && dcpIlluminant == -1) { + dcpIll->set_active(0); + } else if (dcpIlluminant >= 0 && dcpIlluminant != dcpIll->get_active_row_number()) { + dcpIll->set_active(dcpIlluminant); + } + dcpIll->set_sensitive (true); + dcpIllLabel->set_sensitive (true); + } else { + if (dcpIll->get_active_row_number() != -1) { + dcpIll->set_active(-1); + } + } + } + } + if (!dcpIllLabel->get_sensitive() && dcpIll->get_active_row_number() != 0) { + if (dcpTemperatures[0] != 0 || dcpTemperatures[1] != 0) { + int curr_active = dcpIll->get_active_row_number(); + ignoreDcpSignal = true; + dcpIll->clear_items (); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT_INTERPOLATED")); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 1"); + dcpIll->append_text (M("TP_ICM_DCPILLUMINANT") + " 2"); + if (isBatchMode) + dcpIll->append_text (M("GENERAL_UNCHANGED")); + dcpTemperatures[0] = 0; + dcpTemperatures[1] = 0; + dcpIll->set_active (curr_active); + ignoreDcpSignal = false; + } + } +} + void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { disableListener (); @@ -256,33 +354,38 @@ void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { blendcmsconn.block(true); if (pp->icm.input == "(none)" && icamera->get_state()!=Gtk::STATE_INSENSITIVE) { - inone->set_active (true); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); + inone->set_active (true); ckbBlendCMSMatrix->set_sensitive (false); + updateDCP(pp->icm.dcpIlluminant, ""); } 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); ckbToneCurve->set_sensitive (false); + iembedded->set_active (true); ckbBlendCMSMatrix->set_sensitive (false); + updateDCP(pp->icm.dcpIlluminant, ""); } else if ((pp->icm.input == "(cameraICC)") && icameraICC->get_state()!=Gtk::STATE_INSENSITIVE) { - icameraICC->set_active (true); prefprof->set_sensitive (true); ckbToneCurve->set_sensitive (true); + icameraICC->set_active (true); ckbBlendCMSMatrix->set_sensitive (true); + updateDCP(pp->icm.dcpIlluminant, ""); } 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); ckbToneCurve->set_sensitive (false); // RT's own are always single-illuminant and tone curve disabled ckbBlendCMSMatrix->set_sensitive (false); + updateDCP(pp->icm.dcpIlluminant, ""); } 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); ckbToneCurve->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive (false); + updateDCP(pp->icm.dcpIlluminant, ""); } 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); ckbToneCurve->set_sensitive (true); + ckbBlendCMSMatrix->set_sensitive (true); + updateDCP(pp->icm.dcpIlluminant, pp->icm.input.substr(5)); } wnames->set_active_text (pp->icm.working); @@ -296,8 +399,6 @@ void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { if (onames->get_active_row_number()==-1) onames->set_active_text (M("TP_ICM_NOICM")); - prefprof->set_active(pp->icm.preferredProfile-1); - ckbToneCurve->set_active (pp->icm.toneCurve); lastToneCurve = pp->icm.toneCurve; @@ -321,8 +422,8 @@ void ICMPanel::read (const ProcParams* pp, const ParamsEdited* pedited) { wnames->set_active_text(M("GENERAL_UNCHANGED")); if (!pedited->icm.output) onames->set_active_text(M("GENERAL_UNCHANGED")); - if (!pedited->icm.preferredProfile) - prefprof->set_active_text(M("GENERAL_UNCHANGED")); + if (!pedited->icm.dcpIlluminant) + dcpIll->set_active_text(M("GENERAL_UNCHANGED")); if (!pedited->icm.gamma){ wgamma->set_active_text(M("GENERAL_UNCHANGED")); wgamma->set_active_text(M("GENERAL_UNCHANGED")); @@ -361,7 +462,9 @@ void ICMPanel::write (ProcParams* pp, ParamsEdited* pedited) { pp->icm.working = wnames->get_active_text (); pp->icm.gamma = wgamma->get_active_text (); - pp->icm.preferredProfile = prefprof->get_active_row_number()+1; + pp->icm.dcpIlluminant = dcpIll->get_active_row_number(); + if (pp->icm.dcpIlluminant < 0) + pp->icm.dcpIlluminant = 0; if (onames->get_active_text()==M("TP_ICM_NOICM")) pp->icm.output = ColorManagementParams::NoICMString; @@ -377,7 +480,7 @@ void ICMPanel::write (ProcParams* pp, ParamsEdited* pedited) { pedited->icm.input = !iunchanged->get_active (); 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.dcpIlluminant = dcpIll->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"); @@ -433,9 +536,10 @@ void ICMPanel::gpChanged () { } } -void ICMPanel::prefProfChanged() { - if (listener) - listener->panelChanged (EvPrefProfile, prefprof->get_active_text ()); +void ICMPanel::dcpIlluminantChanged() { + if (listener && !ignoreDcpSignal) { + listener->panelChanged (EvDCPIlluminant, dcpIll->get_active_text ()); + } } void ICMPanel::toneCurveChanged() { @@ -458,27 +562,28 @@ void ICMPanel::toneCurveChanged() { void ICMPanel::ipChanged () { - std::string profname; + Glib::ustring profname; if (inone->get_active()) { profname = "(none)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); } else if (iembedded->get_active ()) { profname = "(embedded)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); } else if (icamera->get_active ()) { profname = "(camera)"; - ckbBlendCMSMatrix->set_sensitive(false); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(false); } else if (icameraICC->get_active ()) { profname = "(cameraICC)"; - ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (false); ckbToneCurve->set_sensitive (false); + ckbBlendCMSMatrix->set_sensitive(true); } else { profname = ipDialog->get_filename (); - ckbBlendCMSMatrix->set_sensitive(true); prefprof->set_sensitive (true); ckbToneCurve->set_sensitive (true); + ckbBlendCMSMatrix->set_sensitive(true); } + updateDCP(-1, profname); if (listener && profname!=oldip) listener->panelChanged (EvIProfile, profname); @@ -604,6 +709,8 @@ void ICMPanel::saveReferencePressed () { void ICMPanel::setBatchMode (bool batchMode) { + isBatchMode = true; + ignoreDcpSignal = false; ToolPanel::setBatchMode (batchMode); iunchanged = Gtk::manage (new Gtk::RadioButton (M("GENERAL_UNCHANGED"))); iunchanged->set_group (opts); @@ -613,9 +720,8 @@ void ICMPanel::setBatchMode (bool batchMode) { onames->append_text (M("GENERAL_UNCHANGED")); wnames->append_text (M("GENERAL_UNCHANGED")); wgamma->append_text (M("GENERAL_UNCHANGED")); - prefprof->append_text (M("GENERAL_UNCHANGED")); + dcpIll->append_text (M("GENERAL_UNCHANGED")); gampos->showEditedCB (); slpos->showEditedCB (); - } diff --git a/rtgui/icmpanel.h b/rtgui/icmpanel.h old mode 100644 new mode 100755 index 395a7bafa..32ed030ae --- a/rtgui/icmpanel.h +++ b/rtgui/icmpanel.h @@ -46,6 +46,7 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP bool lastToneCurve; sigc::connection tcurveconn; bool lastBlendCMSMatrix; + bool isBatchMode; sigc::connection blendcmsconn; private: @@ -58,7 +59,8 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP Gtk::RadioButton* icamera; Gtk::RadioButton* icameraICC; Gtk::RadioButton* ifromfile; - MyComboBoxText* prefprof; + Gtk::Label* dcpIllLabel; + MyComboBoxText* dcpIll; Gtk::CheckButton* ckbToneCurve; Gtk::CheckButton* ckbBlendCMSMatrix; MyComboBoxText* wnames; @@ -76,9 +78,11 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP Glib::ustring oldip; ICMPanelListener* icmplistener; + bool ignoreDcpSignal; + double dcpTemperatures[2]; bool enableLastICCWorkDirChange; Glib::ustring lastRefFilename; - + void updateDCP(int dcpIlluminant, Glib::ustring dcp_name); public: ICMPanel (); @@ -96,7 +100,7 @@ class ICMPanel : public Gtk::VBox, public AdjusterListener, public FoldableToolP void GamChanged (); void ipSelectionChanged (); void blendCMSMatrixChanged(); - void prefProfChanged(); + void dcpIlluminantChanged(); void toneCurveChanged(); void setRawMeta (bool raw, const rtengine::ImageData* pMeta); diff --git a/rtgui/paramsedited.cc b/rtgui/paramsedited.cc index 235d8a864..c5cc91a47 100644 --- a/rtgui/paramsedited.cc +++ b/rtgui/paramsedited.cc @@ -251,7 +251,7 @@ void ParamsEdited::set (bool v) { icm.input = v; icm.toneCurve = v; icm.blendCMSMatrix = v; - icm.preferredProfile = v; + icm.dcpIlluminant = v; icm.working = v; icm.output = v; icm.gamma = v; @@ -530,7 +530,7 @@ void ParamsEdited::initFrom (const std::vector 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.dcpIlluminant = icm.dcpIlluminant && p.icm.dcpIlluminant == other.icm.dcpIlluminant; icm.working = icm.working && p.icm.working == other.icm.working; icm.output = icm.output && p.icm.output == other.icm.output; icm.gamma = icm.gamma && p.icm.gamma == other.icm.gamma; @@ -825,7 +825,7 @@ void ParamsEdited::combine (rtengine::procparams::ProcParams& toEdit, const rten 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.dcpIlluminant) toEdit.icm.dcpIlluminant = mods.icm.dcpIlluminant; if (icm.working) toEdit.icm.working = mods.icm.working; if (icm.output) toEdit.icm.output = mods.icm.output; //if (icm.gampos) toEdit.icm.gampos = mods.icm.gampos; diff --git a/rtgui/paramsedited.h b/rtgui/paramsedited.h index b2ac51abc..4137cb9e7 100644 --- a/rtgui/paramsedited.h +++ b/rtgui/paramsedited.h @@ -412,7 +412,7 @@ class ColorManagementParamsEdited { bool input; bool toneCurve; bool blendCMSMatrix; - bool preferredProfile; + bool dcpIlluminant; bool working; bool output; bool gamma;