diff --git a/rtengine/dcp.cc b/rtengine/dcp.cc index 8e695e42b..fb672cd34 100644 --- a/rtengine/dcp.cc +++ b/rtengine/dcp.cc @@ -16,6 +16,7 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include #include #include "dcp.h" @@ -29,6 +30,9 @@ using namespace std; using namespace rtengine; using namespace rtexif; +namespace +{ + static const float adobe_camera_raw_default_curve[] = { 0.00000f, 0.00078f, 0.00160f, 0.00242f, 0.00314f, 0.00385f, 0.00460f, 0.00539f, @@ -289,41 +293,81 @@ static const float adobe_camera_raw_default_curve[] = { 1.00000f }; +struct ruvt { + double r; + double u; + double v; + double t; +}; + +static const ruvt kTempTable [] = { + { 0, 0.18006, 0.26352, -0.24341 }, + { 10, 0.18066, 0.26589, -0.25479 }, + { 20, 0.18133, 0.26846, -0.26876 }, + { 30, 0.18208, 0.27119, -0.28539 }, + { 40, 0.18293, 0.27407, -0.30470 }, + { 50, 0.18388, 0.27709, -0.32675 }, + { 60, 0.18494, 0.28021, -0.35156 }, + { 70, 0.18611, 0.28342, -0.37915 }, + { 80, 0.18740, 0.28668, -0.40955 }, + { 90, 0.18880, 0.28997, -0.44278 }, + { 100, 0.19032, 0.29326, -0.47888 }, + { 125, 0.19462, 0.30141, -0.58204 }, + { 150, 0.19962, 0.30921, -0.70471 }, + { 175, 0.20525, 0.31647, -0.84901 }, + { 200, 0.21142, 0.32312, -1.0182 }, + { 225, 0.21807, 0.32909, -1.2168 }, + { 250, 0.22511, 0.33439, -1.4512 }, + { 275, 0.23247, 0.33904, -1.7298 }, + { 300, 0.24010, 0.34308, -2.0637 }, + { 325, 0.24702, 0.34655, -2.4681 }, + { 350, 0.25591, 0.34951, -2.9641 }, + { 375, 0.26400, 0.35200, -3.5814 }, + { 400, 0.27218, 0.35407, -4.3633 }, + { 425, 0.28039, 0.35577, -5.3762 }, + { 450, 0.28863, 0.35714, -6.7262 }, + { 475, 0.29685, 0.35823, -8.5955 }, + { 500, 0.30505, 0.35907, -11.324 }, + { 525, 0.31320, 0.35968, -15.628 }, + { 550, 0.32129, 0.36011, -23.325 }, + { 575, 0.32931, 0.36038, -40.770 }, + { 600, 0.33724, 0.36051, -116.45 } +}; + // This sRGB gamma is taken from DNG reference code, with the added linear extension past 1.0, as we run clipless here -static float sRGBGammaForward (const float x) +float sRGBGammaForward(float x) { - if (x <= 0.0031308) { - return x * 12.92; - } else if (x > 1.0) { - return 1.0 + (x - 1.0) * (1.055 * (1.0 / 2.4)); // linear extension - } else { - return 1.055 * pow (x, 1.0 / 2.4) - 0.055; - } -} -static float sRGBGammaInverse (const float y) -{ - if (y <= 0.0031308 * 12.92) { - return y * (1.0 / 12.92); - } else if (y > 1.0) { - return 1.0 + (y - 1.0) / (1.055 * (1.0 / 2.4)); - } else { - return pow ((y + 0.055) * (1.0 / 1.055), 2.4); - } + return + x <= 0.0031308f + ? x * 12.92f + : x > 1.0f + ? 1.0f + (x - 1.0f) * (1.055f * (1.0f / 2.4f)) // Linear extension + : 1.055f * pow(x, 1.0f / 2.4f) - 0.055f; } -static void Invert3x3(const double (*A)[3], double (*B)[3]) +float sRGBGammaInverse(float y) { + return + y <= 0.0031308f * 12.92f + ? y * (1.0f / 12.92f) + : y > 1.0f + ? 1.0f + (y - 1.0f) / (1.055f * (1.0f / 2.4f)) + : pow ((y + 0.055f) * (1.0f / 1.055f), 2.4f); +} - 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]; +void invert3x3(const double (*A)[3], double (*B)[3]) +{ + const double& a00 = A[0][0]; + const double& a01 = A[0][1]; + const double& a02 = A[0][2]; + const double& a10 = A[1][0]; + const double& a11 = A[1][1]; + const double& a12 = A[1][2]; + const double& a20 = A[2][0]; + const double& a21 = A[2][1]; + const double& a22 = A[2][2]; + + double temp[3][3]; temp[0][0] = a11 * a22 - a21 * a12; temp[0][1] = a21 * a02 - a01 * a22; @@ -335,30 +379,29 @@ static void Invert3x3(const double (*A)[3], double (*B)[3]) 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]; + const 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 + 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++) { + 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]) +void multiply3x3(const double (*A)[3], const double (*B)[3], double (*C)[3]) { - - // use temp to support having output same as input + // 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++) { + 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++) { + for (int k = 0; k < 3; ++k) { M[i][j] += A[i][k] * B[k][j]; } } @@ -367,14 +410,13 @@ static void Multiply3x3(const double (*A)[3], const double (*B)[3], double (*C)[ memcpy(C, M, 3 * 3 * sizeof(double)); } -static void Multiply3x3_v3(const double (*A)[3], const double B[3], double C[3]) +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] = {}; - // 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++) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { M[i] += A[i][j] * B[j]; } } @@ -382,13 +424,12 @@ static void Multiply3x3_v3(const double (*A)[3], const double B[3], double C[3]) memcpy(C, M, 3 * sizeof(double)); } -static void Mix3x3(const double (*A)[3], double mulA, const double (*B)[3], double mulB, double (*C)[3]) +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++) { + 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; } } @@ -396,18 +437,21 @@ static void Mix3x3(const double (*A)[3], double mulA, const double (*B)[3], doub memcpy(C, M, 3 * 3 * sizeof(double)); } -static void MapWhiteMatrix(const double white1[3], const double white2[3], double (*B)[3]) +void mapWhiteMatrix(const double white1[3], const double white2[3], double (*B)[3]) { + // Code adapted from dng_color_spec::MapWhiteMatrix - // 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 }}; + // Use the linearized Bradford adaptation matrix + const 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); + multiply3x3_v3(Mb, white1, w1); double w2[3]; - Multiply3x3_v3(Mb, white2, w2); + multiply3x3_v3(Mb, white2, w2); // Negative white coordinates are kind of meaningless. w1[0] = std::max(w1[0], 0.0); @@ -418,23 +462,24 @@ static void MapWhiteMatrix(const double white1[3], const double white2[3], doubl 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}}; + double A[3][3] = {}; 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); + invert3x3(Mb, temp); + multiply3x3(temp, A, temp); + multiply3x3(temp, Mb, B); } -static void XYZtoXY(const double XYZ[3], double XY[2]) +void XYZtoXY(const double XYZ[3], double XY[2]) { - double X = XYZ[0]; - double Y = XYZ[1]; - double Z = XYZ[2]; - double total = X + Y + Z; + const double& X = XYZ[0]; + const double& Y = XYZ[1]; + const double& Z = XYZ[2]; + + const double total = X + Y + Z; if (total > 0.0) { XY[0] = X / total; @@ -445,9 +490,10 @@ static void XYZtoXY(const double XYZ[3], double XY[2]) } } -static void XYtoXYZ(const double XY[2], double XYZ[3]) +void XYtoXYZ(const double XY[2], double XYZ[3]) { double temp[2] = { XY[0], XY[1] }; + // Restrict xy coord to someplace inside the range of real xy coordinates. // This prevents math from doing strange things when users specify // extreme temperature/tint coordinates. @@ -465,352 +511,94 @@ static void XYtoXYZ(const double XY[2], double XYZ[3]) XYZ[2] = (1.0 - temp[0] - temp[1]) / temp[1]; } -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) +double calibrationIlluminantToTemperature(int light) { + enum class LightSource { + UNKNOWN = 0, + DAYLIGHT = 1, + FLUORESCENT = 2, + TUNGSTEN = 3, + FLASH = 4, + FINE_WEATHER = 9, + CLOUDY_WEATHER = 10, + SHADE = 11, + DAYLIGHT_FLUORESCENT = 12, // D 5700 - 7100K + DAYWHITE_FLUORESCENT = 13, // N 4600 - 5500K + COOL_WHITE_FLUORESCENT = 14, // W 3800 - 4500K + WHITE_FLUORESCENT = 15, // WW 3250 - 3800K + WARM_WHITE_FLUORESCENT = 16, // L 2600 - 3250K + STANDARD_LIGHT_A = 17, + STANDARD_LIGHT_B = 18, + STANDARD_LIGHT_C = 19, + D55 = 20, + D65 = 21, + D75 = 22, + D50 = 23, + ISO_STUDIO_TUNGSTEN = 24, + OTHER = 255 + }; - // 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(const ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], 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 - - double neutral[3]; // same as the DNG "AsShotNeutral" tag if white balance is Camera's own - { - /* A bit messy matrixing and conversions to get the neutral[] array from RT's own white balance which is stored in - sRGB space, while the DCP code needs multipliers in CameraRGB space */ - double r, g, b; - wb.getMultipliers(r, g, b); - - // camWbMatrix == imatrices.xyz_cam - double cam_xyz[3][3]; - Invert3x3(camWbMatrix, cam_xyz); - double cam_rgb[3][3]; - Multiply3x3(cam_xyz, xyz_sRGB, cam_rgb); - double camwb_red = cam_rgb[0][0] * r + cam_rgb[0][1] * g + cam_rgb[0][2] * b; - double camwb_green = cam_rgb[1][0] * r + cam_rgb[1][1] * g + cam_rgb[1][2] * b; - double camwb_blue = cam_rgb[2][0] * r + cam_rgb[2][1] * g + cam_rgb[2][2] * b; - neutral[0] = camwb_red / pre_mul[0]; - neutral[1] = camwb_green / pre_mul[1]; - neutral[2] = camwb_blue / pre_mul[2]; - double maxentry = 0; - - for (int i = 0; i < 3; i++) { - if (neutral[i] > maxentry) { - maxentry = neutral[i]; - } + // These temperatures are those found in DNG SDK reference code + switch (LightSource(light)) { + case LightSource::STANDARD_LIGHT_A: + case LightSource::TUNGSTEN: { + return 2850.0; } - for (int i = 0; i < 3; i++) { - neutral[i] /= maxentry; - } - } - - /* Calculate what the RGB multipliers corresponds to as a white XY coordinate, based on the - DCP ColorMatrix or ColorMatrices if dual-illuminant. This is the DNG reference code way to - do it, which is a bit different from RT's own white balance model at the time of writing. - When RT's white balance can make use of the DCP color matrices we could use that instead. */ - double white_xy[2]; - dngref_NeutralToXY(neutral, preferredIlluminant, white_xy); - - bool hasFwd1 = hasForwardMatrix1; - bool hasFwd2 = hasForwardMatrix2; - bool hasCol1 = hasColorMatrix1; - bool hasCol2 = hasColorMatrix2; - - if (preferredIlluminant == 1) { - if (hasFwd1) { - hasFwd2 = false; + case LightSource::ISO_STUDIO_TUNGSTEN: { + return 3200.0; } - if (hasCol1) { - hasCol2 = false; - } - } else if (preferredIlluminant == 2) { - if (hasFwd2) { - hasFwd1 = false; + case LightSource::D50: { + return 5000.0; } - if (hasCol2) { - hasCol1 = false; - } - } - - // mix if we have two matrices - double mix = 1.0; - - if ((hasCol1 && hasCol2) || (hasFwd1 && hasFwd2)) { - double wbtemp; - /* DNG ref way to convert XY to temperature, which affect matrix mixing. A different model here - typically does not affect the result too much, ie it's probably not strictly necessary to - use the DNG reference code here, but we do it for now. */ - dngref_XYCoord2Temperature(white_xy, &wbtemp, NULL); - - if (wbtemp <= temperature1) { - mix = 1.0; - } else if (wbtemp >= temperature2) { - mix = 0.0; - } else { - double invT = 1.0 / wbtemp; - mix = (invT - (1.0 / temperature2)) / ((1.0 / temperature1) - (1.0 / temperature2)); - } - } - - // 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)); - } - - /* - The exact position of the white XY coordinate affects the result very much, thus - it's important that the result is very similar or the same as DNG reference code. - Especially important is it that the raw-embedded "AsShot" multipliers is translated - to the same white XY coordinate as the DNG reference code, or else third party DCPs - will show incorrect color. - */ - - double white_xyz[3]; - XYtoXYZ(white_xy, white_xyz); - - double cam_xyz[3][3]; - - 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)); + case LightSource::D55: + case LightSource::DAYLIGHT: + case LightSource::FINE_WEATHER: + case LightSource::FLASH: + case LightSource::STANDARD_LIGHT_B: { + return 5500.0; } - // adapted from dng_color_spec::SetWhiteXY - double CameraWhite[3]; - Multiply3x3_v3(mCol, white_xyz, CameraWhite); - - double whiteDiag[3][3] = {{CameraWhite[0], 0, 0}, {0, CameraWhite[1], 0}, {0, 0, CameraWhite[2]}}; - double whiteDiagInv[3][3]; - Invert3x3(whiteDiag, whiteDiagInv); - - double xyz_cam[3][3]; - Multiply3x3(mFwd, whiteDiagInv, xyz_cam); - Invert3x3(xyz_cam, cam_xyz); - } else { - double whiteMatrix[3][3]; - const double white_d50[3] = { 0.3457, 0.3585, 0.2958 }; // D50 - MapWhiteMatrix(white_d50, white_xyz, whiteMatrix); - Multiply3x3(mCol, whiteMatrix, cam_xyz); - } - - // convert cam_xyz (XYZ D50 to CameraRGB, "PCS to Camera" in DNG terminology) to mXYZCAM - - { - // This block can probably be simplified, seems unnecessary to pass through the sRGB matrix - // (probably dcraw legacy), it does no harm though as we don't clip anything. - int i, j, 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]; - } - - // 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; - } + case LightSource::D65: + case LightSource::STANDARD_LIGHT_C: + case LightSource::CLOUDY_WEATHER: { + return 6500.0; } - double rgb_cam[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; - RawImageSource::inverse33 (cam_rgb, rgb_cam); + case LightSource::D75: + case LightSource::SHADE: { + return 7500.0; + } - for (i = 0; i < 3; i++) - for (j = 0; j < 3; j++) { - mXYZCAM[i][j] = 0; - } + case LightSource::DAYLIGHT_FLUORESCENT: { + return (5700.0 + 7100.0) * 0.5; + } - 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]; - } + case LightSource::DAYWHITE_FLUORESCENT: { + return (4600.0 + 5500.0) * 0.5; + } + + case LightSource::COOL_WHITE_FLUORESCENT: + case LightSource::FLUORESCENT: { + return (3800.0 + 4500.0) * 0.5; + } + + case LightSource::WHITE_FLUORESCENT: { + return (3250.0 + 3800.0) * 0.5; + } + + case LightSource::WARM_WHITE_FLUORESCENT: { + return (2600.0 + 3250.0) * 0.5; + } + + default: { + return 0.0; + } } } -const DCPProfile::HSBModify* DCPProfile::MakeHueSatMap(const 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(const Glib::ustring &fname) @@ -825,20 +613,20 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) const int TagProfileToneCurve = 50940, TagBaselineExposureOffset = 51109; const int TagProfileCopyright = 50942; - aDeltas1 = aDeltas2 = aLookTable = NULL; + aDeltas1 = aDeltas2 = aLookTable = nullptr; FILE *pFile = g_fopen(fname.c_str (), "rb"); TagDirectory *tagDir = ExifManager::parseTIFF(pFile, false); Tag* tag = tagDir->getTag(TagCalibrationIlluminant1); - iLightSource1 = (tag != NULL ? tag->toInt(0, rtexif::SHORT) : -1); + iLightSource1 = (tag != nullptr ? tag->toInt(0, rtexif::SHORT) : -1); tag = tagDir->getTag(TagCalibrationIlluminant2); - iLightSource2 = (tag != NULL ? tag->toInt(0, rtexif::SHORT) : -1); + iLightSource2 = (tag != nullptr ? 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 + bool hasSecondHueSat = tagDir->getTag(TagProfileHueSatMapData2) != nullptr; // some profiles have two matrices, but just one huesat // Fetch Forward Matrices, if any hasForwardMatrix1 = false; @@ -891,13 +679,13 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) tag = tagDir->getTag(TagProfileLookTableDims); - if (tag != NULL) { + if (tag != nullptr) { LookInfo.iHueDivisions = tag->toInt(0); LookInfo.iSatDivisions = tag->toInt(4); LookInfo.iValDivisions = tag->toInt(8); tag = tagDir->getTag(TagProfileLookTableEncoding); - LookInfo.sRGBGamma = tag != NULL && tag->toInt(0); + LookInfo.sRGBGamma = tag != nullptr && tag->toInt(0); tag = tagDir->getTag(TagProfileLookTableData); LookInfo.iArrayCount = tag->getCount() / 3; @@ -923,13 +711,13 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) tag = tagDir->getTag(TagProfileHueSatMapDims); - if (tag != NULL) { + if (tag != nullptr) { DeltaInfo.iHueDivisions = tag->toInt(0); DeltaInfo.iSatDivisions = tag->toInt(4); DeltaInfo.iValDivisions = tag->toInt(8); tag = tagDir->getTag(TagProfileHueSatMapEncoding); - DeltaInfo.sRGBGamma = tag != NULL && tag->toInt(0); + DeltaInfo.sRGBGamma = tag != nullptr && tag->toInt(0); tag = tagDir->getTag(TagProfileHueSatMapData1); DeltaInfo.iArrayCount = tag->getCount() / 3; @@ -959,7 +747,7 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) for (int row = 0; row < 3; row++) { for (int col = 0; col < 3; col++) { - mColorMatrix2[row][col] = (tag != NULL ? (float)tag->toDouble((col + row * 3) * 8) : mColorMatrix1[row][col]); + mColorMatrix2[row][col] = (tag != nullptr ? (float)tag->toDouble((col + row * 3) * 8) : mColorMatrix1[row][col]); } } @@ -988,7 +776,7 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) // Read tone curve points, if any, but disable to RTs own profiles tag = tagDir->getTag(TagProfileToneCurve); - if (tag != NULL) { + if (tag != nullptr) { std::vector cPoints; cPoints.push_back(double(DCT_Spline)); // The first value is the curve type @@ -1014,10 +802,10 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) toneCurve.Set(rawCurve); hasToneCurve = true; } - } else if (tag == NULL) { + } else if (tag == nullptr) { tag = tagDir->getTag(TagProfileCopyright); - if (tag != NULL && tag->valueToString().find("Adobe Systems") != std::string::npos) { + if (tag != nullptr && tag->valueToString().find("Adobe Systems") != std::string::npos) { // an Adobe profile without tone curve is expected to have the Adobe Default Curve, we add that std::vector cPoints; cPoints.push_back(double(DCT_Spline)); @@ -1062,7 +850,7 @@ DCPProfile::DCPProfile(const Glib::ustring &fname) } } - if (pFile != NULL) { + if (pFile != nullptr) { fclose(pFile); } @@ -1076,363 +864,30 @@ DCPProfile::~DCPProfile() delete[] aLookTable; } -void DCPProfile::HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, float &h, float &s, float &v) const +bool DCPProfile::getHasToneCurve() const { - - // Apply the HueSatMap. Ported from Adobes reference implementation - float hueShift, satScale, valScale; - float vEncoded = v; - - if (ti.iValDivisions < 2) { // Optimize most common case of "2.5D" table. - float hScaled = h * ti.pc.hScale; - float sScaled = s * ti.pc.sScale; - - int hIndex0 = max((int)hScaled, 0); - int sIndex0 = max(min((int)sScaled, ti.pc.maxSatIndex0), 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 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 = h * ti.pc.hScale; - float sScaled = s * ti.pc.sScale; - - if (ti.sRGBGamma) { - vEncoded = sRGBGammaForward(v); - } - - float vScaled = vEncoded * 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; - } - - hueShift *= (6.0f / 360.0f); // Convert to internal hue range. - - h += hueShift; - s *= satScale; // no clipping here, we are RT float :-) - - if (ti.sRGBGamma) { - v = sRGBGammaInverse(vEncoded * valScale); - } else { - v *= valScale; - } + return hasToneCurve; } - -struct ruvt { - double r; - double u; - double v; - double t; +bool DCPProfile::getHasLookTable() const +{ + return !!aLookTable; +} +bool DCPProfile::getHasHueSatMap() const +{ + return !!aDeltas1; +} +bool DCPProfile::getHasBaselineExposureOffset() const +{ + return hasBaselineExposureOffset; +} +void DCPProfile::getIlluminants(int &i1, double &temp1, int &i2, double &temp2, bool &willInterpolate_) const +{ + i1 = iLightSource1; + i2 = iLightSource2; + temp1 = temperature1, temp2 = temperature2; + willInterpolate_ = willInterpolate; }; -static const double kTintScale = -3000.0; -static const ruvt kTempTable [] = { - { 0, 0.18006, 0.26352, -0.24341 }, - { 10, 0.18066, 0.26589, -0.25479 }, - { 20, 0.18133, 0.26846, -0.26876 }, - { 30, 0.18208, 0.27119, -0.28539 }, - { 40, 0.18293, 0.27407, -0.30470 }, - { 50, 0.18388, 0.27709, -0.32675 }, - { 60, 0.18494, 0.28021, -0.35156 }, - { 70, 0.18611, 0.28342, -0.37915 }, - { 80, 0.18740, 0.28668, -0.40955 }, - { 90, 0.18880, 0.28997, -0.44278 }, - { 100, 0.19032, 0.29326, -0.47888 }, - { 125, 0.19462, 0.30141, -0.58204 }, - { 150, 0.19962, 0.30921, -0.70471 }, - { 175, 0.20525, 0.31647, -0.84901 }, - { 200, 0.21142, 0.32312, -1.0182 }, - { 225, 0.21807, 0.32909, -1.2168 }, - { 250, 0.22511, 0.33439, -1.4512 }, - { 275, 0.23247, 0.33904, -1.7298 }, - { 300, 0.24010, 0.34308, -2.0637 }, - { 325, 0.24702, 0.34655, -2.4681 }, - { 350, 0.25591, 0.34951, -2.9641 }, - { 375, 0.26400, 0.35200, -3.5814 }, - { 400, 0.27218, 0.35407, -4.3633 }, - { 425, 0.28039, 0.35577, -5.3762 }, - { 450, 0.28863, 0.35714, -6.7262 }, - { 475, 0.29685, 0.35823, -8.5955 }, - { 500, 0.30505, 0.35907, -11.324 }, - { 525, 0.31320, 0.35968, -15.628 }, - { 550, 0.32129, 0.36011, -23.325 }, - { 575, 0.32931, 0.36038, -40.770 }, - { 600, 0.33724, 0.36051, -116.45 } -}; - -void DCPProfile::dngref_XYCoord2Temperature(const double whiteXY[2], double *temp, double *tint) const -{ - double fTemperature = 0; - double fTint = 0; - - // Convert to uv space. - double u = 2.0 * whiteXY[0] / (1.5 - whiteXY[0] + 6.0 * whiteXY[1]); - double v = 3.0 * whiteXY[1] / (1.5 - whiteXY[0] + 6.0 * whiteXY[1]); - - // Search for line pair coordinate is between. - double last_dt = 0.0; - double last_dv = 0.0; - double last_du = 0.0; - - for (uint32_t index = 1; index <= 30; index++) { - // Convert slope to delta-u and delta-v, with length 1. - double du = 1.0; - double dv = kTempTable [index] . t; - double len = sqrt (1.0 + dv * dv); - du /= len; - dv /= len; - - // Find delta from black body point to test coordinate. - double uu = u - kTempTable [index] . u; - double vv = v - kTempTable [index] . v; - - // Find distance above or below line. - double dt = - uu * dv + vv * du; - - // If below line, we have found line pair. - if (dt <= 0.0 || index == 30) { - // Find fractional weight of two lines. - if (dt > 0.0) { - dt = 0.0; - } - - dt = -dt; - double f; - - if (index == 1) { - f = 0.0; - } else { - f = dt / (last_dt + dt); - } - - // Interpolate the temperature. - fTemperature = 1.0E6 / (kTempTable [index - 1] . r * f + - kTempTable [index ] . r * (1.0 - f)); - - // Find delta from black body point to test coordinate. - uu = u - (kTempTable [index - 1] . u * f + - kTempTable [index ] . u * (1.0 - f)); - vv = v - (kTempTable [index - 1] . v * f + - kTempTable [index ] . v * (1.0 - f)); - // Interpolate vectors along slope. - du = du * (1.0 - f) + last_du * f; - dv = dv * (1.0 - f) + last_dv * f; - len = sqrt (du * du + dv * dv); - du /= len; - dv /= len; - - // Find distance along slope. - fTint = (uu * du + vv * dv) * kTintScale; - break; - } - - // Try next line pair. - last_dt = dt; - last_du = du; - last_dv = dv; - } - - if (temp != NULL) { - *temp = fTemperature; - } - - if (tint != NULL) { - *tint = fTint; - } -} - -void DCPProfile::dngref_FindXYZtoCamera(const double whiteXY[2], int preferredIlluminant, double (*xyzToCamera)[3]) const -{ - - bool hasCol1 = hasColorMatrix1; - bool hasCol2 = hasColorMatrix2; - - if (preferredIlluminant == 1) { - if (hasCol1) { - hasCol2 = false; - } - } else if (preferredIlluminant == 2) { - if (hasCol2) { - hasCol1 = false; - } - } - - // mix if we have two matrices - double mix; - - if (hasCol1 && hasCol2) { - double wbtemp; - /* - Note: we're using DNG SDK reference code for XY to temperature translation to get the exact same mix as - the reference code does. - */ - dngref_XYCoord2Temperature(whiteXY, &wbtemp, NULL); - - if (wbtemp <= temperature1) { - mix = 1.0; - } else if (wbtemp >= temperature2) { - mix = 0.0; - } else { - double invT = 1.0 / wbtemp; - mix = (invT - (1.0 / temperature2)) / ((1.0 / temperature1) - (1.0 / temperature2)); - } - } - - // Interpolate the color matrix. - 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)); - } - - memcpy(xyzToCamera, mCol, sizeof(mCol)); -} - -void DCPProfile::dngref_NeutralToXY(double neutral[3], int preferredIlluminant, double XY[2]) const -{ - const int kMaxPasses = 30; - double lastXY[2] = { 0.3457, 0.3585 }; // D50 - - for (int pass = 0; pass < kMaxPasses; pass++) { - double xyzToCamera[3][3]; - dngref_FindXYZtoCamera(lastXY, preferredIlluminant, xyzToCamera); - - double invM[3][3], nextXYZ[3], nextXY[2]; - Invert3x3(xyzToCamera, invM); - Multiply3x3_v3(invM, neutral, nextXYZ); - XYZtoXY(nextXYZ, nextXY); - - if (fabs(nextXY[0] - lastXY[0]) + - fabs(nextXY[1] - lastXY[1]) < 0.0000001) { - XY[0] = nextXY[0]; - XY[1] = nextXY[1]; - return; - } - - // If we reach the limit without converging, we are most likely - // in a two value oscillation. So take the average of the last - // two estimates and give up. - if (pass == kMaxPasses - 1) { - nextXY[0] = (lastXY[0] + nextXY[0]) * 0.5; - nextXY[1] = (lastXY[1] + nextXY[1]) * 0.5; - } - - lastXY[0] = nextXY[0]; - lastXY[1] = nextXY[1]; - } - - XY[0] = lastXY[0]; - XY[1] = lastXY[1]; -} - void DCPProfile::Apply(Imagefloat *pImg, int preferredIlluminant, const Glib::ustring &workingSpace, const ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], bool useToneCurve, bool applyHueSatMap, bool applyLookTable) const { @@ -1687,101 +1142,696 @@ void DCPProfile::step2ApplyTile(float *rc, float *gc, float *bc, int width, int } } -// Generates as singleton -DCPStore* DCPStore::getInstance() +void DCPProfile::dngref_XYCoord2Temperature(const double whiteXY[2], double *temp, double *tint) const { - static DCPStore instance_; - return &instance_; + constexpr double kTintScale = -3000.0; + + double fTemperature = 0; + double fTint = 0; + + // Convert to uv space. + double u = 2.0 * whiteXY[0] / (1.5 - whiteXY[0] + 6.0 * whiteXY[1]); + double v = 3.0 * whiteXY[1] / (1.5 - whiteXY[0] + 6.0 * whiteXY[1]); + + // Search for line pair coordinate is between. + double last_dt = 0.0; + double last_dv = 0.0; + double last_du = 0.0; + + for (uint32_t index = 1; index <= 30; index++) { + // Convert slope to delta-u and delta-v, with length 1. + double du = 1.0; + double dv = kTempTable [index] . t; + double len = sqrt (1.0 + dv * dv); + du /= len; + dv /= len; + + // Find delta from black body point to test coordinate. + double uu = u - kTempTable [index] . u; + double vv = v - kTempTable [index] . v; + + // Find distance above or below line. + double dt = - uu * dv + vv * du; + + // If below line, we have found line pair. + if (dt <= 0.0 || index == 30) { + // Find fractional weight of two lines. + if (dt > 0.0) { + dt = 0.0; + } + + dt = -dt; + double f; + + if (index == 1) { + f = 0.0; + } else { + f = dt / (last_dt + dt); + } + + // Interpolate the temperature. + fTemperature = 1.0E6 / (kTempTable [index - 1] . r * f + + kTempTable [index ] . r * (1.0 - f)); + + // Find delta from black body point to test coordinate. + uu = u - (kTempTable [index - 1] . u * f + + kTempTable [index ] . u * (1.0 - f)); + vv = v - (kTempTable [index - 1] . v * f + + kTempTable [index ] . v * (1.0 - f)); + // Interpolate vectors along slope. + du = du * (1.0 - f) + last_du * f; + dv = dv * (1.0 - f) + last_dv * f; + len = sqrt (du * du + dv * dv); + du /= len; + dv /= len; + + // Find distance along slope. + fTint = (uu * du + vv * dv) * kTintScale; + break; + } + + // Try next line pair. + last_dt = dt; + last_du = du; + last_dv = dv; + } + + if (temp != nullptr) { + *temp = fTemperature; + } + + if (tint != nullptr) { + *tint = fTint; + } } -// Reads all profiles from the given profiles dir -void DCPStore::init (const Glib::ustring &rtProfileDir) +void DCPProfile::dngref_FindXYZtoCamera(const double whiteXY[2], int preferredIlluminant, double (*xyzToCamera)[3]) const { - MyMutex::MyLock lock(mtx); - fileStdProfiles.clear(); + bool hasCol1 = hasColorMatrix1; + bool hasCol2 = hasColorMatrix2; - if (rtProfileDir != "") { - std::deque qDirs; + if (preferredIlluminant == 1) { + if (hasCol1) { + hasCol2 = false; + } + } else if (preferredIlluminant == 2) { + if (hasCol2) { + hasCol1 = false; + } + } - qDirs.push_front(rtProfileDir); + // mix if we have two matrices + double mix; - while (!qDirs.empty()) { - // process directory - Glib::ustring dirname = qDirs.back(); - qDirs.pop_back(); + if (hasCol1 && hasCol2) { + double wbtemp; + /* + Note: we're using DNG SDK reference code for XY to temperature translation to get the exact same mix as + the reference code does. + */ + dngref_XYCoord2Temperature(whiteXY, &wbtemp, nullptr); - Glib::Dir* dir = NULL; + if (wbtemp <= temperature1) { + mix = 1.0; + } else if (wbtemp >= temperature2) { + mix = 0.0; + } else { + double invT = 1.0 / wbtemp; + mix = (invT - (1.0 / temperature2)) / ((1.0 / temperature1) - (1.0 / temperature2)); + } + } + + // Interpolate the color matrix. + 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)); + } + + memcpy(xyzToCamera, mCol, sizeof(mCol)); +} + +void DCPProfile::dngref_NeutralToXY(double neutral[3], int preferredIlluminant, double XY[2]) const +{ + const int kMaxPasses = 30; + double lastXY[2] = { 0.3457, 0.3585 }; // D50 + + for (int pass = 0; pass < kMaxPasses; pass++) { + double xyzToCamera[3][3]; + dngref_FindXYZtoCamera(lastXY, preferredIlluminant, xyzToCamera); + + double invM[3][3], nextXYZ[3], nextXY[2]; + invert3x3(xyzToCamera, invM); + multiply3x3_v3(invM, neutral, nextXYZ); + XYZtoXY(nextXYZ, nextXY); + + if (fabs(nextXY[0] - lastXY[0]) + + fabs(nextXY[1] - lastXY[1]) < 0.0000001) { + XY[0] = nextXY[0]; + XY[1] = nextXY[1]; + return; + } + + // If we reach the limit without converging, we are most likely + // in a two value oscillation. So take the average of the last + // two estimates and give up. + if (pass == kMaxPasses - 1) { + nextXY[0] = (lastXY[0] + nextXY[0]) * 0.5; + nextXY[1] = (lastXY[1] + nextXY[1]) * 0.5; + } + + lastXY[0] = nextXY[0]; + lastXY[1] = nextXY[1]; + } + + XY[0] = lastXY[0]; + XY[1] = lastXY[1]; +} + +void DCPProfile::MakeXYZCAM(const ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], 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 + + double neutral[3]; // same as the DNG "AsShotNeutral" tag if white balance is Camera's own + { + /* A bit messy matrixing and conversions to get the neutral[] array from RT's own white balance which is stored in + sRGB space, while the DCP code needs multipliers in CameraRGB space */ + double r, g, b; + wb.getMultipliers(r, g, b); + + // camWbMatrix == imatrices.xyz_cam + double cam_xyz[3][3]; + invert3x3(camWbMatrix, cam_xyz); + double cam_rgb[3][3]; + multiply3x3(cam_xyz, xyz_sRGB, cam_rgb); + double camwb_red = cam_rgb[0][0] * r + cam_rgb[0][1] * g + cam_rgb[0][2] * b; + double camwb_green = cam_rgb[1][0] * r + cam_rgb[1][1] * g + cam_rgb[1][2] * b; + double camwb_blue = cam_rgb[2][0] * r + cam_rgb[2][1] * g + cam_rgb[2][2] * b; + neutral[0] = camwb_red / pre_mul[0]; + neutral[1] = camwb_green / pre_mul[1]; + neutral[2] = camwb_blue / pre_mul[2]; + double maxentry = 0; + + for (int i = 0; i < 3; i++) { + if (neutral[i] > maxentry) { + maxentry = neutral[i]; + } + } + + for (int i = 0; i < 3; i++) { + neutral[i] /= maxentry; + } + } + + /* Calculate what the RGB multipliers corresponds to as a white XY coordinate, based on the + DCP ColorMatrix or ColorMatrices if dual-illuminant. This is the DNG reference code way to + do it, which is a bit different from RT's own white balance model at the time of writing. + When RT's white balance can make use of the DCP color matrices we could use that instead. */ + double white_xy[2]; + dngref_NeutralToXY(neutral, preferredIlluminant, white_xy); + + 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 = 1.0; + + if ((hasCol1 && hasCol2) || (hasFwd1 && hasFwd2)) { + double wbtemp; + /* DNG ref way to convert XY to temperature, which affect matrix mixing. A different model here + typically does not affect the result too much, ie it's probably not strictly necessary to + use the DNG reference code here, but we do it for now. */ + dngref_XYCoord2Temperature(white_xy, &wbtemp, nullptr); + + if (wbtemp <= temperature1) { + mix = 1.0; + } else if (wbtemp >= temperature2) { + mix = 0.0; + } else { + double invT = 1.0 / wbtemp; + mix = (invT - (1.0 / temperature2)) / ((1.0 / temperature1) - (1.0 / temperature2)); + } + } + + // 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)); + } + + /* + The exact position of the white XY coordinate affects the result very much, thus + it's important that the result is very similar or the same as DNG reference code. + Especially important is it that the raw-embedded "AsShot" multipliers is translated + to the same white XY coordinate as the DNG reference code, or else third party DCPs + will show incorrect color. + */ + + double white_xyz[3]; + XYtoXYZ(white_xy, white_xyz); + + double cam_xyz[3][3]; + + 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)); + } + + // adapted from dng_color_spec::SetWhiteXY + double CameraWhite[3]; + multiply3x3_v3(mCol, white_xyz, CameraWhite); + + double whiteDiag[3][3] = {{CameraWhite[0], 0, 0}, {0, CameraWhite[1], 0}, {0, 0, CameraWhite[2]}}; + double whiteDiagInv[3][3]; + invert3x3(whiteDiag, whiteDiagInv); + + double xyz_cam[3][3]; + multiply3x3(mFwd, whiteDiagInv, xyz_cam); + invert3x3(xyz_cam, cam_xyz); + } else { + double whiteMatrix[3][3]; + const double white_d50[3] = { 0.3457, 0.3585, 0.2958 }; // D50 + mapWhiteMatrix(white_d50, white_xyz, whiteMatrix); + multiply3x3(mCol, whiteMatrix, cam_xyz); + } + + // convert cam_xyz (XYZ D50 to CameraRGB, "PCS to Camera" in DNG terminology) to mXYZCAM + + { + // This block can probably be simplified, seems unnecessary to pass through the sRGB matrix + // (probably dcraw legacy), it does no harm though as we don't clip anything. + int i, j, 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]; + } + + // 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); + + 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]; + } + } +} + +const DCPProfile::HSBModify* DCPProfile::MakeHueSatMap(const ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const +{ + + *deleteHandle = nullptr; + + if (!aDeltas1) { + return nullptr; + } + + 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; +} + +void DCPProfile::HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, float &h, float &s, float &v) const +{ + + // Apply the HueSatMap. Ported from Adobes reference implementation + float hueShift, satScale, valScale; + float vEncoded = v; + + if (ti.iValDivisions < 2) { // Optimize most common case of "2.5D" table. + float hScaled = h * ti.pc.hScale; + float sScaled = s * ti.pc.sScale; + + int hIndex0 = max((int)hScaled, 0); + int sIndex0 = max(min((int)sScaled, ti.pc.maxSatIndex0), 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 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 = h * ti.pc.hScale; + float sScaled = s * ti.pc.sScale; + + if (ti.sRGBGamma) { + vEncoded = sRGBGammaForward(v); + } + + float vScaled = vEncoded * 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; + } + + hueShift *= (6.0f / 360.0f); // Convert to internal hue range. + + h += hueShift; + s *= satScale; // no clipping here, we are RT float :-) + + if (ti.sRGBGamma) { + v = sRGBGammaInverse(vEncoded * valScale); + } else { + v *= valScale; + } +} + +DCPStore* DCPStore::getInstance() +{ + static DCPStore instance; + return &instance; +} + +void DCPStore::init(const Glib::ustring& rt_profile_dir) +{ + MyMutex::MyLock lock(mutex); + + file_std_profiles.clear(); + + if (!rt_profile_dir.empty()) { + std::deque dirs = { + rt_profile_dir + }; + + while (!dirs.empty()) { + // Process directory + Glib::ustring dirname = dirs.back(); + dirs.pop_back(); + + std::unique_ptr dir; try { - if (!Glib::file_test (dirname, Glib::FILE_TEST_IS_DIR)) { + if (!Glib::file_test(dirname, Glib::FILE_TEST_IS_DIR)) { return; } - dir = new Glib::Dir (dirname); - } catch (Glib::Exception& fe) { + dir.reset(new Glib::Dir(dirname)); + } catch (Glib::Exception& exception) { return; } - dirname = dirname + "/"; + dirname += '/'; - for (Glib::DirIterator i = dir->begin(); i != dir->end(); ++i) { - Glib::ustring fname = dirname + *i; - Glib::ustring sname = *i; + for (const Glib::ustring& sname : *dir) { + const Glib::ustring fname = dirname + sname; - // ignore directories - if (!Glib::file_test (fname, Glib::FILE_TEST_IS_DIR)) { - size_t lastdot = sname.find_last_of ('.'); + if (!Glib::file_test(fname, Glib::FILE_TEST_IS_DIR)) { + // File + const auto lastdot = sname.rfind('.'); - if (lastdot != Glib::ustring::npos && lastdot <= sname.size() - 4 && (!sname.casefold().compare (lastdot, 4, ".dcp"))) { - Glib::ustring camShortName = sname.substr(0, lastdot).uppercase(); - fileStdProfiles[camShortName] = fname; // they will be loaded and cached on demand + if ( + lastdot != Glib::ustring::npos + && lastdot <= sname.size() - 4 + && !sname.casefold().compare(lastdot, 4, ".dcp") + ) { + const Glib::ustring cam_short_name = sname.substr(0, lastdot).uppercase(); + file_std_profiles[cam_short_name] = fname; // They will be loaded and cached on demand } } else { - qDirs.push_front(fname); // for later scanning + // Directory + dirs.push_front(fname); } } - - delete dir; } } } -DCPProfile* DCPStore::getProfile (const Glib::ustring &filename) +bool DCPStore::isValidDCPFileName(const Glib::ustring& filename) const { - MyMutex::MyLock lock(mtx); - - std::map::iterator r = profileCache.find (filename); - - if (r != profileCache.end()) { - return r->second; - } - - // Add profile - profileCache[filename] = new DCPProfile(filename); - - return profileCache[filename]; -} - -DCPProfile* DCPStore::getStdProfile(const Glib::ustring &camShortName) -{ - Glib::ustring name2 = camShortName.uppercase(); - - // 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); - } - - return NULL; -} - -bool DCPStore::isValidDCPFileName(const Glib::ustring &filename) const -{ - if (!Glib::file_test (filename, Glib::FILE_TEST_EXISTS) || Glib::file_test (filename, Glib::FILE_TEST_IS_DIR)) { + if (!Glib::file_test(filename, Glib::FILE_TEST_EXISTS) || Glib::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") || !filename.casefold().compare (pos, 4, ".dng")); + const auto pos = filename.rfind('.'); + return + pos > 0 + && ( + !filename.casefold().compare(pos, 4, ".dcp") + || !filename.casefold().compare(pos, 4, ".dng") + ); +} + +DCPProfile* DCPStore::getProfile(const Glib::ustring& filename) const +{ + MyMutex::MyLock lock(mutex); + + const std::map::iterator r = profile_cache.find(filename); + + if (r != profile_cache.end()) { + return r->second; + } + + DCPProfile* const res = new DCPProfile(filename); + + // Add profile + profile_cache[filename] = res; + + return res; +} + +DCPProfile* DCPStore::getStdProfile(const Glib::ustring& cam_short_name) const +{ + const Glib::ustring name = cam_short_name.uppercase(); + + // Warning: do NOT use map.find(), since it does not seem to work reliably here + for (const auto& file_std_profile : file_std_profiles) + if (file_std_profile.first == name) { + return getProfile(file_std_profile.second); + } + + return nullptr; } diff --git a/rtengine/dcp.h b/rtengine/dcp.h index 4b70445b2..2e47375c3 100644 --- a/rtengine/dcp.h +++ b/rtengine/dcp.h @@ -17,22 +17,47 @@ * along with RawTherapee. If not, see . */ -#ifndef _DCP_ -#define _DCP_ +#pragma once + +#include + +#include #include "imagefloat.h" #include "curves.h" #include "colortemp.h" + #include "../rtgui/threadutils.h" -#include -#include -//#include namespace rtengine { -class DCPProfile +class DCPProfile final { +public: + struct dcpApplyState{ + double m2ProPhoto[3][3]; + double m2Work[3][3]; + bool alreadyProPhoto; + bool useToneCurve; + bool applyLookTable; + float blScale; + }; + + DCPProfile(const Glib::ustring &fname); + ~DCPProfile(); + + bool getHasToneCurve() const; + bool getHasLookTable() const; + bool getHasHueSatMap() const; + bool getHasBaselineExposureOffset() const; + void getIlluminants(int &i1, double &temp1, int &i2, double &temp2, bool &willInterpolate_) const; + + void Apply(Imagefloat *pImg, int preferredIlluminant, const Glib::ustring &workingSpace, const ColorTemp &wb, double pre_mul[3], double camMatrix[3][3], bool useToneCurve = false, bool applyHueSatMap = true, bool applyLookTable = false) const; + void setStep2ApplyState(const Glib::ustring &workingSpace, bool useToneCurve, bool applyLookTable, bool applyBaselineExposure, dcpApplyState &asOut); + void step2ApplyTile(float *r, float *g, float *b, int width, int height, int tileWidth, const dcpApplyState &asIn) const; + +private: struct HSBModify { float fHueShift; float fSatScale; @@ -49,6 +74,13 @@ class DCPProfile } pc; }; + void dngref_XYCoord2Temperature(const double whiteXY[2], double *temp, double *tint) const; + void dngref_FindXYZtoCamera(const double whiteXY[2], int preferredIlluminant, double (*xyzToCamera)[3]) const; + void dngref_NeutralToXY(double neutral[3], int preferredIlluminant, double XY[2]) const; + void MakeXYZCAM(const ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], int preferredIlluminant, double (*mXYZCAM)[3]) const; + const HSBModify* MakeHueSatMap(const ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const; + void HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, float &h, float &s, float &v) const; + double mColorMatrix1[3][3], mColorMatrix2[3][3]; bool hasColorMatrix1, hasColorMatrix2, hasForwardMatrix1, hasForwardMatrix2, hasToneCurve, hasBaselineExposureOffset, willInterpolate; double mForwardMatrix1[3][3], mForwardMatrix2[3][3]; @@ -59,76 +91,33 @@ class DCPProfile short iLightSource1, iLightSource2; AdobeToneCurve toneCurve; - - void dngref_XYCoord2Temperature(const double whiteXY[2], double *temp, double *tint) const; - void dngref_FindXYZtoCamera(const double whiteXY[2], int preferredIlluminant, double (*xyzToCamera)[3]) const; - void dngref_NeutralToXY(double neutral[3], int preferredIlluminant, double XY[2]) const; - void MakeXYZCAM(const ColorTemp &wb, double pre_mul[3], double camWbMatrix[3][3], int preferredIlluminant, double (*mXYZCAM)[3]) const; - const HSBModify* MakeHueSatMap(const ColorTemp &wb, int preferredIlluminant, HSBModify **deleteHandle) const; - void HSDApply(const HSDTableInfo &ti, const HSBModify *tableBase, float &h, float &s, float &v) const; - -public: - struct dcpApplyState{ - double m2ProPhoto[3][3]; - double m2Work[3][3]; - bool alreadyProPhoto; - bool useToneCurve; - bool applyLookTable; - float blScale; - }; - - DCPProfile(const Glib::ustring &fname); - ~DCPProfile(); - - bool getHasToneCurve() const - { - return hasToneCurve; - } - bool getHasLookTable() const - { - return !!aLookTable; - } - bool getHasHueSatMap() const - { - return !!aDeltas1; - } - bool getHasBaselineExposureOffset() const - { - return hasBaselineExposureOffset; - } - void getIlluminants(int &i1, double &temp1, int &i2, double &temp2, bool &willInterpolate_) const - { - i1 = iLightSource1; - i2 = iLightSource2; - temp1 = temperature1, temp2 = temperature2; - willInterpolate_ = willInterpolate; - }; - void Apply(Imagefloat *pImg, int preferredIlluminant, const Glib::ustring &workingSpace, const ColorTemp &wb, double pre_mul[3], double camMatrix[3][3], bool useToneCurve = false, bool applyHueSatMap = true, bool applyLookTable = false) const; - void setStep2ApplyState(const Glib::ustring &workingSpace, bool useToneCurve, bool applyLookTable, bool applyBaselineExposure, dcpApplyState &asOut); - void step2ApplyTile(float *r, float *g, float *b, int width, int height, int tileWidth, const dcpApplyState &asIn) const; }; -class DCPStore +class DCPStore final { - MyMutex mtx; +public: + static DCPStore* getInstance(); + + DCPStore(const DCPStore& other) = delete; + DCPStore& operator =(const DCPStore& other) = delete; + + void init(const Glib::ustring& rt_profile_dir); + + bool isValidDCPFileName(const Glib::ustring& filename) const; + + DCPProfile* getProfile(const Glib::ustring& filename) const; + DCPProfile* getStdProfile(const Glib::ustring& camShortName) const; + +private: + DCPStore() = default; + + mutable MyMutex mutex; // these contain standard profiles from RT. keys are all in uppercase, file path is value - std::map fileStdProfiles; + std::map file_std_profiles; // Maps file name to profile as cache - std::map profileCache; - -public: - void init(const Glib::ustring &rtProfileDir); - - bool isValidDCPFileName(const Glib::ustring &filename) const; - - DCPProfile* getProfile(const Glib::ustring &filename); - DCPProfile* getStdProfile(const Glib::ustring &camShortName); - - static DCPStore* getInstance(); + mutable std::map profile_cache; }; -#define dcpStore DCPStore::getInstance() } -#endif diff --git a/rtengine/init.cc b/rtengine/init.cc index d2509b620..3b2c5fbdc 100644 --- a/rtengine/init.cc +++ b/rtengine/init.cc @@ -42,7 +42,7 @@ int init (const Settings* s, Glib::ustring baseDir, Glib::ustring userSettingsDi settings = s; iccStore->init (s->iccDirectory, baseDir + "/iccprofiles"); iccStore->findDefaultMonitorProfile(); - dcpStore->init (baseDir + "/dcpprofiles"); + DCPStore::getInstance()->init (baseDir + "/dcpprofiles"); CameraConstantsStore::getInstance ()->init (baseDir, userSettingsDir); profileStore.init (); diff --git a/rtengine/rawimagesource.cc b/rtengine/rawimagesource.cc index 7085e315f..400fdb3fe 100644 --- a/rtengine/rawimagesource.cc +++ b/rtengine/rawimagesource.cc @@ -898,7 +898,7 @@ DCPProfile *RawImageSource::getDCP(const ColorManagementParams &cmp, ColorTemp & if (dcpProf == NULL) { return NULL; } - + dcpProf->setStep2ApplyState(cmp.working, cmp.toneCurve, cmp.applyLookTable, cmp.applyBaselineExposureOffset, as); return dcpProf; } @@ -4085,7 +4085,7 @@ bool RawImageSource::findInputProfile(Glib::ustring inProfile, cmsHPROFILE embed in = embedded; } else if (inProfile == "(cameraICC)") { // DCPs have higher quality, so use them first - *dcpProf = dcpStore->getStdProfile(camName); + *dcpProf = DCPStore::getInstance()->getStdProfile(camName); if (*dcpProf == NULL) { in = iccStore->getStdProfile(camName); @@ -4097,8 +4097,8 @@ bool RawImageSource::findInputProfile(Glib::ustring inProfile, cmsHPROFILE embed normalName = inProfile.substr(5); } - if (dcpStore->isValidDCPFileName(normalName)) { - *dcpProf = dcpStore->getProfile(normalName); + if (DCPStore::getInstance()->isValidDCPFileName(normalName)) { + *dcpProf = DCPStore::getInstance()->getProfile(normalName); } if (*dcpProf == NULL) { diff --git a/rtgui/icmpanel.cc b/rtgui/icmpanel.cc index efce343c0..15a171efe 100644 --- a/rtgui/icmpanel.cc +++ b/rtgui/icmpanel.cc @@ -361,9 +361,9 @@ void ICMPanel::updateDCP (int dcpIlluminant, Glib::ustring dcp_name) DCPProfile* dcp = NULL; if(dcp_name == "(cameraICC)") { - dcp = dcpStore->getStdProfile(camName); - } else if (ifromfile->get_active() && dcpStore->isValidDCPFileName(dcp_name)) { - dcp = dcpStore->getProfile(dcp_name); + dcp = DCPStore::getInstance()->getStdProfile(camName); + } else if (ifromfile->get_active() && DCPStore::getInstance()->isValidDCPFileName(dcp_name)) { + dcp = DCPStore::getInstance()->getProfile(dcp_name); } if (dcp) { @@ -631,10 +631,10 @@ void ICMPanel::write (ProcParams* pp, ParamsEdited* pedited) DCPProfile* dcp = NULL; - if (ifromfile->get_active() && pp->icm.input.substr(0, 5) == "file:" && dcpStore->isValidDCPFileName(pp->icm.input.substr(5))) { - dcp = dcpStore->getProfile(pp->icm.input.substr(5)); + if (ifromfile->get_active() && pp->icm.input.substr(0, 5) == "file:" && DCPStore::getInstance()->isValidDCPFileName(pp->icm.input.substr(5))) { + dcp = DCPStore::getInstance()->getProfile(pp->icm.input.substr(5)); } else if(icameraICC->get_active()) { - dcp = dcpStore->getStdProfile(camName); + dcp = DCPStore::getInstance()->getStdProfile(camName); } if (dcp) { @@ -920,7 +920,7 @@ void ICMPanel::setRawMeta (bool raw, const rtengine::ImageData* pMeta) iembedded->set_active (!raw); icamera->set_sensitive (raw); camName = pMeta->getCamera(); - icameraICC->set_sensitive (raw && (iccStore->getStdProfile(pMeta->getCamera()) != NULL || dcpStore->getStdProfile(pMeta->getCamera()) != NULL)); + icameraICC->set_sensitive (raw && (iccStore->getStdProfile(pMeta->getCamera()) != NULL || DCPStore::getInstance()->getStdProfile(pMeta->getCamera()) != NULL)); iembedded->set_sensitive (!raw); enableListener ();