Merge pull request #4461 from Beep6581/custom-working-profiles

added possibility to specify extra working spaces via a json file
This commit is contained in:
Alberto Griggio
2018-03-27 08:34:58 +02:00
committed by GitHub
7 changed files with 409 additions and 53 deletions

View File

@@ -286,7 +286,7 @@ void rtengine::HaldCLUT::splitClutFilename(
profile_name = "sRGB";
if (!name.empty()) {
for (const auto& working_profile : rtengine::ICCStore::getWorkingProfiles()) {
for (const auto& working_profile : rtengine::ICCStore::getInstance()->getWorkingProfiles()) {
if (
!working_profile.empty() // This isn't strictly needed, but an empty wp name should be skipped anyway
&& std::search(name.rbegin(), name.rend(), working_profile.rbegin(), working_profile.rend()) == name.rbegin()

View File

@@ -37,30 +37,36 @@ namespace
DCPProfile::Matrix invert3x3(const DCPProfile::Matrix& a)
{
const double res00 = a[1][1] * a[2][2] - a[2][1] * a[1][2];
const double res10 = a[2][0] * a[1][2] - a[1][0] * a[2][2];
const double res20 = a[1][0] * a[2][1] - a[2][0] * a[1][1];
const double det = a[0][0] * res00 + a[0][1] * res10 + a[0][2] * res20;
if (std::fabs(det) < 1.0e-10) {
DCPProfile::Matrix res = a;
if (!invertMatrix(a, res)) {
std::cerr << "DCP matrix cannot be inverted! Expect weird output." << std::endl;
return a;
}
DCPProfile::Matrix res;
res[0][0] = res00 / det;
res[0][1] = (a[2][1] * a[0][2] - a[0][1] * a[2][2]) / det;
res[0][2] = (a[0][1] * a[1][2] - a[1][1] * a[0][2]) / det;
res[1][0] = res10 / det;
res[1][1] = (a[0][0] * a[2][2] - a[2][0] * a[0][2]) / det;
res[1][2] = (a[1][0] * a[0][2] - a[0][0] * a[1][2]) / det;
res[2][0] = res20 / det;
res[2][1] = (a[2][0] * a[0][1] - a[0][0] * a[2][1]) / det;
res[2][2] = (a[0][0] * a[1][1] - a[1][0] * a[0][1]) / det;
return res;
// const double res00 = a[1][1] * a[2][2] - a[2][1] * a[1][2];
// const double res10 = a[2][0] * a[1][2] - a[1][0] * a[2][2];
// const double res20 = a[1][0] * a[2][1] - a[2][0] * a[1][1];
// const double det = a[0][0] * res00 + a[0][1] * res10 + a[0][2] * res20;
// if (std::fabs(det) < 1.0e-10) {
// std::cerr << "DCP matrix cannot be inverted! Expect weird output." << std::endl;
// return a;
// }
// DCPProfile::Matrix res;
// res[0][0] = res00 / det;
// res[0][1] = (a[2][1] * a[0][2] - a[0][1] * a[2][2]) / det;
// res[0][2] = (a[0][1] * a[1][2] - a[1][1] * a[0][2]) / det;
// res[1][0] = res10 / det;
// res[1][1] = (a[0][0] * a[2][2] - a[2][0] * a[0][2]) / det;
// res[1][2] = (a[1][0] * a[0][2] - a[0][0] * a[1][2]) / det;
// res[2][0] = res20 / det;
// res[2][1] = (a[2][0] * a[0][1] - a[0][0] * a[2][1]) / det;
// res[2][2] = (a[0][0] * a[1][1] - a[1][0] * a[0][1]) / det;
// return res;
}
DCPProfile::Matrix multiply3x3(const DCPProfile::Matrix& a, const DCPProfile::Matrix& b)

View File

@@ -27,6 +27,8 @@
#include <netinet/in.h>
#endif
#include <iostream>
#include "iccstore.h"
#include "iccmatrices.h"
@@ -35,6 +37,8 @@
#include "../rtgui/options.h"
#include "../rtgui/threadutils.h"
#include "cJSON.h"
namespace rtengine
{
@@ -274,7 +278,7 @@ public:
for (int i = 0; i < N; ++i) {
wProfiles[wpnames[i]] = createFromMatrix(wprofiles[i]);
wProfilesGamma[wpnames[i]] = createFromMatrix(wprofiles[i], true);
// wProfilesGamma[wpnames[i]] = createFromMatrix(wprofiles[i], true);
wMatrices[wpnames[i]] = wprofiles[i];
iwMatrices[wpnames[i]] = iwprofiles[i];
}
@@ -287,11 +291,11 @@ public:
cmsCloseProfile(p.second);
}
}
for (auto &p : wProfilesGamma) {
if (p.second) {
cmsCloseProfile(p.second);
}
}
// for (auto &p : wProfilesGamma) {
// if (p.second) {
// cmsCloseProfile(p.second);
// }
// }
for (auto &p : fileProfiles) {
if(p.second) {
cmsCloseProfile(p.second);
@@ -334,6 +338,9 @@ public:
defaultMonitorProfile = settings->monitorProfile;
loadWorkingSpaces(rtICCDir);
loadWorkingSpaces(userICCDir);
// initialize the alarm colours for lcms gamut checking -- we use bright green
cmsUInt16Number cms_alarm_codes[cmsMAXCHANNELS] = { 0, 65535, 65535 };
cmsSetAlarmCodes(cms_alarm_codes);
@@ -349,16 +356,16 @@ public:
: wProfiles.find("sRGB")->second;
}
cmsHPROFILE workingSpaceGamma(const Glib::ustring& name) const
{
// cmsHPROFILE workingSpaceGamma(const Glib::ustring& name) const
// {
const ProfileMap::const_iterator r = wProfilesGamma.find(name);
// const ProfileMap::const_iterator r = wProfilesGamma.find(name);
return
r != wProfilesGamma.end()
? r->second
: wProfilesGamma.find("sRGB")->second;
}
// return
// r != wProfilesGamma.end()
// ? r->second
// : wProfilesGamma.find("sRGB")->second;
// }
TMatrix workingSpaceMatrix(const Glib::ustring& name) const
{
@@ -582,17 +589,276 @@ public:
defaultMonitorProfile = name;
}
std::vector<Glib::ustring> getWorkingProfiles()
{
std::vector<Glib::ustring> res;
// for (unsigned int i = 0; i < sizeof(wpnames) / sizeof(wpnames[0]); i++) {
// res.push_back(wpnames[i]);
// }
for (const auto &p : wProfiles) {
res.push_back(p.first);
}
return res;
}
private:
using CVector = std::array<double, 3>;
using CMatrix = std::array<CVector, 3>;
struct PMatrix {
double matrix[3][3];
PMatrix(): matrix{} {}
PMatrix(const CMatrix &m)
{
set(m);
}
CMatrix toMatrix() const
{
CMatrix ret;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
ret[i][j] = matrix[i][j];
}
}
return ret;
}
void set(const CMatrix &m)
{
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
matrix[i][j] = m[i][j];
}
}
}
};
bool computeWorkingSpaceMatrix(const Glib::ustring &path, const Glib::ustring &filename, PMatrix &out)
{
Glib::ustring fullpath = filename;
if (!Glib::path_is_absolute(fullpath)) {
fullpath = Glib::build_filename(path, filename);
}
ProfileContent content(fullpath);
cmsHPROFILE prof = content.toProfile();
if (!prof) {
return false;
}
if (cmsGetColorSpace(prof) != cmsSigRgbData) {
cmsCloseProfile(prof);
return false;
}
if (!cmsIsMatrixShaper(prof)) {
cmsCloseProfile(prof);
return false;
}
cmsCIEXYZ *white = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigMediaWhitePointTag));
cmsCIEXYZ *red = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigRedMatrixColumnTag));
cmsCIEXYZ *green = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigGreenMatrixColumnTag));
cmsCIEXYZ *blue = static_cast<cmsCIEXYZ *>(cmsReadTag(prof, cmsSigBlueMatrixColumnTag));
if (!white || !red || !green || !blue) {
cmsCloseProfile(prof);
return false;
}
// do the Bradford adaptation to D50
// matrices from Bruce Lindbloom's webpage
static constexpr CMatrix bradford_MA = {
CVector({0.8951000, 0.2664000, -0.1614000}),
CVector({-0.7502000, 1.7135000, 0.0367000}),
CVector({0.0389000, -0.0685000, 1.0296000})
};
static constexpr CMatrix bradford_MA_inv = {
CVector({0.9869929, -0.1470543, 0.1599627}),
CVector({0.4323053, 0.5183603, 0.0492912}),
CVector({-0.0085287, 0.0400428, 0.9684867})
};
static constexpr CVector bradford_MA_dot_D50 = {
0.99628443, 1.02042736, 0.81864437
};
CVector srcw = dotProduct(bradford_MA, CVector({ white->X, white->Y, white->Z }));
CMatrix m = {
CVector({ bradford_MA_dot_D50[0]/srcw[0], 0.0, 0.0 }),
CVector({ 0.0, bradford_MA_dot_D50[1]/srcw[1], 0.0 }),
CVector({ 0.0, 0.0, bradford_MA_dot_D50[2]/srcw[2] })
};
CMatrix adapt = dotProduct(dotProduct(bradford_MA_inv, m), bradford_MA);
m[0][0] = red->X; m[0][1] = green->X; m[0][2] = blue->X;
m[1][0] = red->Y; m[1][1] = green->Y; m[1][2] = blue->Y;
m[2][0] = red->Z; m[2][1] = green->Z; m[2][2] = blue->Z;
m = dotProduct(adapt, m);
out.set(m);
cmsCloseProfile(prof);
return true;
}
bool loadWorkingSpaces(const Glib::ustring &path)
{
Glib::ustring fileName = Glib::build_filename(path, "workingspaces.json");
FILE* const f = g_fopen(fileName.c_str(), "r");
if (settings->verbose) {
std::cout << "trying to load extra working spaces from " << fileName << std::flush;
}
if (!f) {
if (settings->verbose) {
std::cout << " FAIL" << std::endl;
}
return false;
}
fseek(f, 0, SEEK_END);
long length = ftell(f);
if (length <= 0) {
if (settings->verbose) {
std::cout << " FAIL" << std::endl;
}
fclose(f);
return false;
}
char *buf = new char[length + 1];
fseek(f, 0, SEEK_SET);
length = fread(buf, 1, length, f);
buf[length] = 0;
fclose(f);
cJSON_Minify(buf);
cJSON *root = cJSON_Parse(buf);
if (!root) {
if (settings->verbose) {
std::cout << " FAIL" << std::endl;
}
return false;
}
delete[] buf;
cJSON *js = cJSON_GetObjectItem(root, "working_spaces");
if (!js) {
goto parse_error;
}
for (js = js->child; js != nullptr; js = js->next) {
cJSON *ji = cJSON_GetObjectItem(js, "name");
std::unique_ptr<PMatrix> m(new PMatrix);
std::string name;
if (!ji || ji->type != cJSON_String) {
goto parse_error;
}
name = ji->valuestring;
if (wProfiles.find(name) != wProfiles.end()) {
continue; // already there -- ignore
}
bool found_matrix = false;
ji = cJSON_GetObjectItem(js, "matrix");
if (ji) {
if (ji->type != cJSON_Array) {
goto parse_error;
}
ji = ji->child;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j, ji = ji->next) {
if (!ji || ji->type != cJSON_Number) {
goto parse_error;
}
m->matrix[i][j] = ji->valuedouble;
}
}
if (ji) {
goto parse_error;
}
found_matrix = true;
} else {
ji = cJSON_GetObjectItem(js, "file");
if (!ji || ji->type != cJSON_String) {
goto parse_error;
}
found_matrix = computeWorkingSpaceMatrix(path, ji->valuestring, *m);
}
if (!found_matrix) {
if (settings->verbose) {
std::cout << "Could not find suitable matrix for working space: " << name << std::endl;
}
continue;
}
pMatrices.emplace_back(std::move(m));
TMatrix w = pMatrices.back()->matrix;
CMatrix b = {};
if (!rtengine::invertMatrix(pMatrices.back()->toMatrix(), b)) {
if (settings->verbose) {
std::cout << "Matrix for working space: " << name << " is not invertible, skipping" << std::endl;
}
pMatrices.pop_back();
} else {
wMatrices[name] = w;
pMatrices.emplace_back(new PMatrix(b));
TMatrix iw = pMatrices.back()->matrix;
iwMatrices[name] = iw;
wProfiles[name] = createFromMatrix(w);
if (settings->verbose) {
std::cout << "Added working space: " << name << std::endl;
std::cout << " matrix: [";
for (int i = 0; i < 3; ++i) {
std::cout << " [";
for (int j = 0; j < 3; ++j) {
std::cout << " " << w[i][j];
}
std::cout << "]";
}
std::cout << " ]" << std::endl;
}
}
}
cJSON_Delete(root);
if (settings->verbose) {
std::cout << " OK" << std::endl;
}
return true;
parse_error:
if (settings->verbose) {
std::cout << " ERROR in parsing " << fileName << std::endl;
}
cJSON_Delete(root);
return false;
}
using ProfileMap = std::map<Glib::ustring, cmsHPROFILE>;
using MatrixMap = std::map<Glib::ustring, TMatrix>;
using ContentMap = std::map<Glib::ustring, ProfileContent>;
using NameMap = std::map<Glib::ustring, Glib::ustring>;
ProfileMap wProfiles;
ProfileMap wProfilesGamma;
// ProfileMap wProfilesGamma;
MatrixMap wMatrices;
MatrixMap iwMatrices;
std::vector<std::unique_ptr<PMatrix>> pMatrices;
// These contain profiles from user/system directory(supplied on init)
Glib::ustring profilesDir;
Glib::ustring userICCDir;
@@ -630,10 +896,10 @@ cmsHPROFILE rtengine::ICCStore::workingSpace(const Glib::ustring& name) const
return implementation->workingSpace(name);
}
cmsHPROFILE rtengine::ICCStore::workingSpaceGamma(const Glib::ustring& name) const
{
return implementation->workingSpaceGamma(name);
}
// cmsHPROFILE rtengine::ICCStore::workingSpaceGamma(const Glib::ustring& name) const
// {
// return implementation->workingSpaceGamma(name);
// }
rtengine::TMatrix rtengine::ICCStore::workingSpaceMatrix(const Glib::ustring& name) const
{
@@ -736,14 +1002,7 @@ rtengine::ICCStore::~ICCStore() = default;
std::vector<Glib::ustring> rtengine::ICCStore::getWorkingProfiles()
{
std::vector<Glib::ustring> res;
for (unsigned int i = 0; i < sizeof(wpnames) / sizeof(wpnames[0]); i++) {
res.push_back(wpnames[i]);
}
return res;
return implementation->getWorkingProfiles();
}
std::vector<Glib::ustring> rtengine::ICCStore::getGamma()

View File

@@ -69,7 +69,7 @@ public:
void init(const Glib::ustring& usrICCDir, const Glib::ustring& stdICCDir, bool loadAll);
cmsHPROFILE workingSpace(const Glib::ustring& name) const;
cmsHPROFILE workingSpaceGamma(const Glib::ustring& name) const;
// cmsHPROFILE workingSpaceGamma(const Glib::ustring& name) const;
TMatrix workingSpaceMatrix(const Glib::ustring& name) const;
TMatrix workingSpaceInverseMatrix(const Glib::ustring& name) const;
@@ -95,7 +95,7 @@ public:
std::uint8_t getOutputIntents(const Glib::ustring& name) const;
std::uint8_t getProofIntents(const Glib::ustring& name) const;
static std::vector<Glib::ustring> getWorkingProfiles();
/*static*/ std::vector<Glib::ustring> getWorkingProfiles();
static std::vector<Glib::ustring> getGamma();
static void getGammaArray(const procparams::ColorManagementParams& icm, GammaValues& ga);

View File

@@ -3997,7 +3997,7 @@ void RawImageSource::colorSpaceConversion_ (Imagefloat* im, const ColorManagemen
}
} else {
const bool working_space_is_prophoto = (cmp.working == "ProPhoto");
bool working_space_is_prophoto = (cmp.working == "ProPhoto");
// use supplied input profile
@@ -4066,6 +4066,33 @@ void RawImageSource::colorSpaceConversion_ (Imagefloat* im, const ColorManagemen
cmsHPROFILE prophoto = ICCStore::getInstance()->workingSpace("ProPhoto"); // We always use Prophoto to apply the ICC profile to minimize problems with clipping in LUT conversion.
bool transform_via_pcs_lab = false;
bool separate_pcs_lab_highlights = false;
// check if the working space is fully contained in prophoto
if (!working_space_is_prophoto) {
TMatrix toxyz = ICCStore::getInstance()->workingSpaceMatrix(cmp.working);
TMatrix torgb = ICCStore::getInstance()->workingSpaceInverseMatrix("ProPhoto");
float rgb[3] = {0.f, 0.f, 0.f};
for (int i = 0; i < 2 && !working_space_is_prophoto; ++i) {
rgb[i] = 1.f;
float x, y, z;
Color::rgbxyz(rgb[0], rgb[1], rgb[2], x, y, z, toxyz);
Color::xyz2rgb(x, y, z, rgb[0], rgb[1], rgb[2], torgb);
for (int j = 0; j < 2; ++j) {
if (rgb[j] < 0.f || rgb[j] > 1.f) {
working_space_is_prophoto = true;
prophoto = ICCStore::getInstance()->workingSpace(cmp.working);
if (settings->verbose) {
std::cout << "colorSpaceConversion_: converting directly to " << cmp.working << " instead of passing through ProPhoto" << std::endl;
}
break;
}
rgb[j] = 0.f;
}
}
}
lcmsMutex->lock ();
switch (camera_icc_type) {

View File

@@ -4,6 +4,7 @@
#include <limits>
#include <cmath>
#include <cstdint>
#include <array>
namespace rtengine
{
@@ -137,4 +138,67 @@ constexpr std::uint8_t uint16ToUint8Rounded(std::uint16_t i)
return ((i + 128) - ((i + 128) >> 8)) >> 8;
}
template <typename T>
bool invertMatrix(const std::array<std::array<T, 3>, 3> &in, std::array<std::array<T, 3>, 3> &out)
{
const T res00 = in[1][1] * in[2][2] - in[2][1] * in[1][2];
const T res10 = in[2][0] * in[1][2] - in[1][0] * in[2][2];
const T res20 = in[1][0] * in[2][1] - in[2][0] * in[1][1];
const T det = in[0][0] * res00 + in[0][1] * res10 + in[0][2] * res20;
if (std::abs(det) < 1.0e-10) {
return false;
}
out[0][0] = res00 / det;
out[0][1] = (in[2][1] * in[0][2] - in[0][1] * in[2][2]) / det;
out[0][2] = (in[0][1] * in[1][2] - in[1][1] * in[0][2]) / det;
out[1][0] = res10 / det;
out[1][1] = (in[0][0] * in[2][2] - in[2][0] * in[0][2]) / det;
out[1][2] = (in[1][0] * in[0][2] - in[0][0] * in[1][2]) / det;
out[2][0] = res20 / det;
out[2][1] = (in[2][0] * in[0][1] - in[0][0] * in[2][1]) / det;
out[2][2] = (in[0][0] * in[1][1] - in[1][0] * in[0][1]) / det;
return true;
}
template <typename T>
std::array<std::array<T, 3>, 3> dotProduct(const std::array<std::array<T, 3>, 3> &a, const std::array<std::array<T, 3>, 3> &b)
{
std::array<std::array<T, 3>, 3> res;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
res[i][j] = 0;
for (int k = 0; k < 3; ++k) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}
template <typename T>
std::array<T, 3> dotProduct(const std::array<std::array<T, 3>, 3> &a, const std::array<T, 3> &b)
{
std::array<T, 3> res;
for (int i = 0; i < 3; ++i) {
res[i] = 0;
for (int k = 0; k < 3; ++k) {
res[i] += a[i][k] * b[k];
}
}
return res;
}
}